Compare commits
648 Commits
v1.8.5
...
backport/4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9207f567b1 | ||
|
|
24d4d86aa4 | ||
|
|
c30bf3c936 | ||
|
|
e4e5c9a651 | ||
|
|
9e813f322a | ||
|
|
70144b9061 | ||
|
|
27af65731e | ||
|
|
2778e5940c | ||
|
|
e20f2eac97 | ||
|
|
a91262b153 | ||
|
|
084c3649b2 | ||
|
|
c6584b93f3 | ||
|
|
57ed57ce23 | ||
|
|
77a81d273a | ||
|
|
b2e3d75b75 | ||
|
|
4de693a402 | ||
|
|
08febcb29a | ||
|
|
c1b6cc1149 | ||
|
|
2f43aabb2e | ||
|
|
50293ffbb4 | ||
|
|
a52bc5dc3f | ||
|
|
5acf52370a | ||
|
|
85e46f2513 | ||
|
|
db485aa713 | ||
|
|
e8aaa7c165 | ||
|
|
281d6c2af7 | ||
|
|
c8714ec821 | ||
|
|
84b2036374 | ||
|
|
5c65bf9b00 | ||
|
|
237f832ad1 | ||
|
|
d019d80efa | ||
|
|
42a2ef39db | ||
|
|
ed7c9fbf05 | ||
|
|
4f4627a600 | ||
|
|
7900c73d90 | ||
|
|
0030b8c20c | ||
|
|
fbc7d3135d | ||
|
|
7350ae2ef3 | ||
|
|
9cebe7ac56 | ||
|
|
bfe45c5af4 | ||
|
|
c3c8faffea | ||
|
|
96508580fd | ||
|
|
ee7fcefd73 | ||
|
|
ac31ff6efe | ||
|
|
af6111b51e | ||
|
|
6700c903eb | ||
|
|
10e48aded1 | ||
|
|
e80ba82d77 | ||
|
|
48202e6d98 | ||
|
|
e3ba870577 | ||
|
|
56fae37144 | ||
|
|
af9177584c | ||
|
|
d850c744cd | ||
|
|
cd60f4bf80 | ||
|
|
eddb6fc460 | ||
|
|
6684023424 | ||
|
|
43bec159ff | ||
|
|
27d960340e | ||
|
|
3cda8eab77 | ||
|
|
aac5c1ff72 | ||
|
|
65c0fcfdc9 | ||
|
|
c71d54a26e | ||
|
|
0224da2127 | ||
|
|
3114e1a477 | ||
|
|
375c78271d | ||
|
|
2b903ecf7f | ||
|
|
9e256921ba | ||
|
|
d7e85ac4da | ||
|
|
4495fc3554 | ||
|
|
6996c862ce | ||
|
|
f81d98fb4b | ||
|
|
cd942a1dfb | ||
|
|
ddd15e7451 | ||
|
|
21908ef534 | ||
|
|
426f1a33dd | ||
|
|
713271d525 | ||
|
|
a43efce576 | ||
|
|
07f2fb7c28 | ||
|
|
72563feaf0 | ||
|
|
763a474d11 | ||
|
|
3977d71892 | ||
|
|
25f3da3603 | ||
|
|
2920ca8c86 | ||
|
|
da7e075ee9 | ||
|
|
c7341bed78 | ||
|
|
d9014903ac | ||
|
|
5dad6bb0b0 | ||
|
|
e7529e2d74 | ||
|
|
c0e07dc202 | ||
|
|
2619219618 | ||
|
|
87a0a4ed4f | ||
|
|
df01d8ef79 | ||
|
|
9f38e51d9b | ||
|
|
3f2e343541 | ||
|
|
ea6006bec0 | ||
|
|
1e625d3955 | ||
|
|
4b0a27d6b5 | ||
|
|
9cc38000fd | ||
|
|
3574abe0cb | ||
|
|
fb5aed2143 | ||
|
|
3506ac2a42 | ||
|
|
1f2f8fe001 | ||
|
|
97d9c4cc2c | ||
|
|
b169ecd0fe | ||
|
|
912376a99d | ||
|
|
3ec2ad99b1 | ||
|
|
a4c7a65ffa | ||
|
|
8a67125503 | ||
|
|
f7fc54e628 | ||
|
|
28ea6ed03e | ||
|
|
6c4a12707d | ||
|
|
8fb7bb83a9 | ||
|
|
fbb410667a | ||
|
|
f69868ae26 | ||
|
|
e41627d763 | ||
|
|
4a89db6d67 | ||
|
|
a2ffdb41af | ||
|
|
9fe79ed135 | ||
|
|
a198a4eef4 | ||
|
|
62752f8b72 | ||
|
|
2c96108b2e | ||
|
|
1beff8945b | ||
|
|
ff2ad61b6d | ||
|
|
05941396c0 | ||
|
|
6d037e7d94 | ||
|
|
0768955559 | ||
|
|
faf35a98a3 | ||
|
|
815cc605a8 | ||
|
|
3d7410c30c | ||
|
|
3921bd7f60 | ||
|
|
91ae931461 | ||
|
|
fc4ccf4010 | ||
|
|
b3cc1da02d | ||
|
|
e4bd1efe00 | ||
|
|
45f3a64ae4 | ||
|
|
52940ed4a1 | ||
|
|
2ba1412ae1 | ||
|
|
b42ff90e0c | ||
|
|
de66f47ac9 | ||
|
|
f5b1e89a9c | ||
|
|
4e9a00c3a2 | ||
|
|
0af2fd45e7 | ||
|
|
b4eece879d | ||
|
|
c03d067464 | ||
|
|
437f5c9ab5 | ||
|
|
2e6b20d71d | ||
|
|
41d8867bdd | ||
|
|
322ee92573 | ||
|
|
9674c344ea | ||
|
|
33912454ae | ||
|
|
15c1c9ddc4 | ||
|
|
8bb106b327 | ||
|
|
43c1a3bbc7 | ||
|
|
5e1b6d248c | ||
|
|
28a9c66143 | ||
|
|
b4de6a8f96 | ||
|
|
46df19a3a6 | ||
|
|
b19b7794bc | ||
|
|
29d21e05e8 | ||
|
|
afcd226be8 | ||
|
|
4b319d8d23 | ||
|
|
8ec8a91cab | ||
|
|
96d1e14390 | ||
|
|
c01e542044 | ||
|
|
133e3f3140 | ||
|
|
c1e29ab8cb | ||
|
|
af21282468 | ||
|
|
ba3cab1036 | ||
|
|
81c0d96357 | ||
|
|
ea8b7999f7 | ||
|
|
7bfbbee6e8 | ||
|
|
23813b7a03 | ||
|
|
2542b6ed16 | ||
|
|
0878adb124 | ||
|
|
2d91c8200f | ||
|
|
1aa4da2fab | ||
|
|
46b01e905c | ||
|
|
6872e96b2d | ||
|
|
f2f5ec6163 | ||
|
|
44f0156663 | ||
|
|
5342102d95 | ||
|
|
2f40583c50 | ||
|
|
2ffecd81d4 | ||
|
|
997e479a69 | ||
|
|
7575bd0bf7 | ||
|
|
99f5e8b76e | ||
|
|
308bf80de0 | ||
|
|
4388d898ae | ||
|
|
3a730cf38f | ||
|
|
6ae61368a7 | ||
|
|
4c729530ce | ||
|
|
d43277a5b2 | ||
|
|
006ab80a42 | ||
|
|
fa480003d9 | ||
|
|
a386ad654a | ||
|
|
33d0d2bbd3 | ||
|
|
fc324f611a | ||
|
|
e1afa830f2 | ||
|
|
9ee8da97a5 | ||
|
|
8db953f7d7 | ||
|
|
8eca148a6e | ||
|
|
403a4dc294 | ||
|
|
f775728802 | ||
|
|
514673f6e7 | ||
|
|
bbd6f0c26c | ||
|
|
b4d477dc05 | ||
|
|
9658ccd843 | ||
|
|
264be93a74 | ||
|
|
5830dbd467 | ||
|
|
58a014d401 | ||
|
|
7c6e0dbdfb | ||
|
|
1ed7f48797 | ||
|
|
7a72687cbc | ||
|
|
6d42c72dc5 | ||
|
|
6205699174 | ||
|
|
0f6fb1fb2c | ||
|
|
f63b6775de | ||
|
|
d532383f02 | ||
|
|
d0d02a7f51 | ||
|
|
6f79f7f817 | ||
|
|
b685f14b3b | ||
|
|
cf3bb646e8 | ||
|
|
095481d24f | ||
|
|
aa922f8fce | ||
|
|
301605d6fc | ||
|
|
89ecb65d33 | ||
|
|
8825a75574 | ||
|
|
c962fd1335 | ||
|
|
2f58f9c1c5 | ||
|
|
1e9060b876 | ||
|
|
1ad6375f73 | ||
|
|
c9f539bf31 | ||
|
|
0e52215f30 | ||
|
|
b71b20bd0d | ||
|
|
a3b6728738 | ||
|
|
27a626f8a5 | ||
|
|
2913730bd8 | ||
|
|
1a1b96345f | ||
|
|
40c7f58491 | ||
|
|
249a30f949 | ||
|
|
56a1f29298 | ||
|
|
65901f58db | ||
|
|
7da5eef913 | ||
|
|
4b3f2cc114 | ||
|
|
94b0a9ecf4 | ||
|
|
aec8c516a8 | ||
|
|
cd023d338e | ||
|
|
0417c436c9 | ||
|
|
0bb98b364a | ||
|
|
b9032489dc | ||
|
|
850dddb2ed | ||
|
|
e458dca2b9 | ||
|
|
268edbefd8 | ||
|
|
a71a98a794 | ||
|
|
260650f787 | ||
|
|
1cf25f8c24 | ||
|
|
4c2a9c7614 | ||
|
|
cd3590c6eb | ||
|
|
d7f4585916 | ||
|
|
4ab5d013bb | ||
|
|
01c2ae23aa | ||
|
|
fd90218a8c | ||
|
|
f24bb1439c | ||
|
|
7f7603e3b0 | ||
|
|
063e6a1520 | ||
|
|
1efd06f2b6 | ||
|
|
5ec251e8cd | ||
|
|
6d9a2c1833 | ||
|
|
96e9571ae4 | ||
|
|
a1093683a6 | ||
|
|
04a624b0d9 | ||
|
|
ee96c2196e | ||
|
|
79dac0c1e2 | ||
|
|
c42ac086a1 | ||
|
|
5b41617435 | ||
|
|
f1a44dbef9 | ||
|
|
5868f37dc2 | ||
|
|
528bb867b4 | ||
|
|
f09208fc76 | ||
|
|
2cceb58826 | ||
|
|
6e4ba1aedf | ||
|
|
5c744fd226 | ||
|
|
0eec3969fd | ||
|
|
d31bb87581 | ||
|
|
9f936879ff | ||
|
|
ac2da9f23a | ||
|
|
a8c3f5d196 | ||
|
|
f37f740940 | ||
|
|
83089f6d18 | ||
|
|
08572192c2 | ||
|
|
129bc7370e | ||
|
|
9ff685ad4d | ||
|
|
f88d8bd7b0 | ||
|
|
9cffc9735d | ||
|
|
b1a4a0b836 | ||
|
|
93d8023bdc | ||
|
|
59e8a87baf | ||
|
|
dd6fbb7fb4 | ||
|
|
05590b3550 | ||
|
|
8ba4b74e2c | ||
|
|
43817fdaeb | ||
|
|
f46ecc0b3b | ||
|
|
aecfdc857d | ||
|
|
c84caca341 | ||
|
|
b4a6ac2c97 | ||
|
|
c0aaef3c4b | ||
|
|
5a97b0e858 | ||
|
|
d892979efa | ||
|
|
a1d2e695c6 | ||
|
|
a466b76946 | ||
|
|
e89cd98419 | ||
|
|
f03521e2a1 | ||
|
|
541ee13780 | ||
|
|
6d86ec70ef | ||
|
|
ccf73736c4 | ||
|
|
5386e6e24f | ||
|
|
0ea384d1e5 | ||
|
|
d7919497b6 | ||
|
|
b114520d85 | ||
|
|
8007a06c94 | ||
|
|
e1e29feb7b | ||
|
|
54b390010c | ||
|
|
a674b5b5b2 | ||
|
|
2d465a707f | ||
|
|
43a1b1a2e5 | ||
|
|
c82a5a1228 | ||
|
|
6684730dd7 | ||
|
|
ed125e9439 | ||
|
|
0272b2d52f | ||
|
|
0aede224ec | ||
|
|
af134959ce | ||
|
|
b3d4ac5218 | ||
|
|
cd7fb9a4bd | ||
|
|
dd307fa353 | ||
|
|
567b9cc66e | ||
|
|
38aed97d69 | ||
|
|
6bfb54e2b3 | ||
|
|
7dc64de2de | ||
|
|
fd6e15b58c | ||
|
|
fcfbcc63b4 | ||
|
|
0c69404ac9 | ||
|
|
f6826f7746 | ||
|
|
8b94750e85 | ||
|
|
4ab70617a5 | ||
|
|
9cc199dc47 | ||
|
|
0f71a525c0 | ||
|
|
d4e6537745 | ||
|
|
77b2945fff | ||
|
|
5d4612738b | ||
|
|
5958632534 | ||
|
|
2be2afba16 | ||
|
|
17b6c2a967 | ||
|
|
411972c812 | ||
|
|
584675db21 | ||
|
|
50ca4de351 | ||
|
|
d8c627178c | ||
|
|
f817025dfe | ||
|
|
238127c3aa | ||
|
|
2c03b30aee | ||
|
|
5ab34716c3 | ||
|
|
a96fe36795 | ||
|
|
c739d543c2 | ||
|
|
7f2cdd2d7a | ||
|
|
6ed19bd2b0 | ||
|
|
d25c7feb13 | ||
|
|
5eed353d5a | ||
|
|
00b95fe6ce | ||
|
|
e256cf23ce | ||
|
|
b6756b4617 | ||
|
|
88a9fe2161 | ||
|
|
d3cad6adf0 | ||
|
|
145ac69d1e | ||
|
|
df454f4fcf | ||
|
|
313c05cdad | ||
|
|
c89fdad893 | ||
|
|
ad065b4a9a | ||
|
|
e34a1a06b6 | ||
|
|
293f56924e | ||
|
|
220cbe18bb | ||
|
|
9a6d62e16a | ||
|
|
8ee7b1f1e8 | ||
|
|
bf68dd7edc | ||
|
|
67adda5f84 | ||
|
|
1f8fc6661c | ||
|
|
97e7d2b2d7 | ||
|
|
84c96a00b2 | ||
|
|
bf5cd72361 | ||
|
|
2f0c89e513 | ||
|
|
59e816124a | ||
|
|
632d3c1ef2 | ||
|
|
a6d39488a4 | ||
|
|
d998a30235 | ||
|
|
8432efddde | ||
|
|
a70bf65032 | ||
|
|
68361c517b | ||
|
|
8bfdd72e37 | ||
|
|
71afaaefc3 | ||
|
|
15f8353ecd | ||
|
|
6907d87d32 | ||
|
|
35413d5154 | ||
|
|
9bde070b9b | ||
|
|
610ea1d36b | ||
|
|
21c5383f6f | ||
|
|
b58807363b | ||
|
|
04a6c83d2b | ||
|
|
c1b4708a74 | ||
|
|
286605ff01 | ||
|
|
790ecaac67 | ||
|
|
40b4ed2e1c | ||
|
|
844c4d8d9b | ||
|
|
1aefc990a5 | ||
|
|
329ebc1aec | ||
|
|
5fca656e50 | ||
|
|
c453721d75 | ||
|
|
06939a7362 | ||
|
|
b2e7709092 | ||
|
|
d2c2e0bd11 | ||
|
|
2b7e8d683b | ||
|
|
356c8fe518 | ||
|
|
316bc29d70 | ||
|
|
b6dc2cb1bf | ||
|
|
01ab56bada | ||
|
|
0d66944a5f | ||
|
|
4d91364028 | ||
|
|
a839a95821 | ||
|
|
103e86499e | ||
|
|
c52a7a42c4 | ||
|
|
b366eea8eb | ||
|
|
89a24151ee | ||
|
|
1438127c7b | ||
|
|
c781d29065 | ||
|
|
9094d611ae | ||
|
|
70be562fe3 | ||
|
|
761f2421ba | ||
|
|
6d68c0d0e3 | ||
|
|
3f8895077d | ||
|
|
42e756150d | ||
|
|
cbb9269920 | ||
|
|
a42f0a9b26 | ||
|
|
4ccfc6c1d1 | ||
|
|
a1834f97a8 | ||
|
|
86145c6095 | ||
|
|
3a839da89a | ||
|
|
baf381965c | ||
|
|
51231ac971 | ||
|
|
93fb32904b | ||
|
|
2d1d38804a | ||
|
|
ab12b0fab1 | ||
|
|
a8aa1a44e7 | ||
|
|
dd2e47bb0f | ||
|
|
e01fb76151 | ||
|
|
7288168dd7 | ||
|
|
2a48b4606b | ||
|
|
fd3fef9ccf | ||
|
|
847e2da3c7 | ||
|
|
56935b708a | ||
|
|
2ba8fb9afc | ||
|
|
76fd901062 | ||
|
|
b42076ccb8 | ||
|
|
4239571455 | ||
|
|
afc23d3f2c | ||
|
|
e0a02609af | ||
|
|
78039f8fcb | ||
|
|
1a654925e8 | ||
|
|
071cfca241 | ||
|
|
e93006ded0 | ||
|
|
4bc7a61f8f | ||
|
|
e55b3a0a26 | ||
|
|
0c59a85c9c | ||
|
|
09e0d2e143 | ||
|
|
0fd9e62340 | ||
|
|
ccf669bc85 | ||
|
|
5eb0091c35 | ||
|
|
6ea0e24d44 | ||
|
|
cd5753f247 | ||
|
|
467a3a454e | ||
|
|
0ae73b57d5 | ||
|
|
95837d0626 | ||
|
|
8698e2ce3f | ||
|
|
19f466fd4d | ||
|
|
07bc1456cd | ||
|
|
6bd89da255 | ||
|
|
c3c886ed7e | ||
|
|
f28b25c6ba | ||
|
|
fba496ea68 | ||
|
|
71ca523745 | ||
|
|
56f9d8db1d | ||
|
|
95b1f4ed5e | ||
|
|
f0bf0d448b | ||
|
|
ed282479a1 | ||
|
|
b648947ad7 | ||
|
|
723fb807da | ||
|
|
7a23f3b0d2 | ||
|
|
8fc4238bd9 | ||
|
|
d5dfd2c80c | ||
|
|
c32a6070f5 | ||
|
|
1405220d81 | ||
|
|
ae73912343 | ||
|
|
d8e423b45e | ||
|
|
805f185191 | ||
|
|
a231061043 | ||
|
|
1f66c66ad3 | ||
|
|
39c59a3bd6 | ||
|
|
bfec6f5ad4 | ||
|
|
f35a9d08e5 | ||
|
|
bb8b8c7bb7 | ||
|
|
10495e5fe1 | ||
|
|
9caacda036 | ||
|
|
7c960cbc5f | ||
|
|
df10c7b2fe | ||
|
|
96c7fe3b94 | ||
|
|
f7717aa02a | ||
|
|
1598896157 | ||
|
|
54f70bc5d0 | ||
|
|
a740c7ce0d | ||
|
|
26ff25af72 | ||
|
|
31da745700 | ||
|
|
566f0f923a | ||
|
|
e78fb49cf9 | ||
|
|
2c7878b910 | ||
|
|
43d28eb68a | ||
|
|
03832ee93e | ||
|
|
bbac134b0f | ||
|
|
f13c260e9b | ||
|
|
eb4c572226 | ||
|
|
1c75744f4a | ||
|
|
e5959fa8da | ||
|
|
b47c7aca34 | ||
|
|
ca91e7a2ed | ||
|
|
b92fddaf65 | ||
|
|
b7de565bac | ||
|
|
9c8e1a9f6e | ||
|
|
7e479b0505 | ||
|
|
8f65ec2ede | ||
|
|
6912ae09cf | ||
|
|
eaa3315348 | ||
|
|
5669bd73cc | ||
|
|
57989384fa | ||
|
|
74c04b2d71 | ||
|
|
74565debbb | ||
|
|
a2744823c1 | ||
|
|
a918459105 | ||
|
|
1217b37b19 | ||
|
|
f9acf7778f | ||
|
|
8537bd00c7 | ||
|
|
4b62c34cc3 | ||
|
|
94d84f2b16 | ||
|
|
14e7c33886 | ||
|
|
60ee9f77ca | ||
|
|
3eaba6fe1a | ||
|
|
cef14ed254 | ||
|
|
0828ae6017 | ||
|
|
3af54d7186 | ||
|
|
f01a4433ed | ||
|
|
4f5eeffdf9 | ||
|
|
86ab64160c | ||
|
|
b64b29cf6e | ||
|
|
d93b431554 | ||
|
|
d00bd159d2 | ||
|
|
816a8c08f0 | ||
|
|
2c3113334a | ||
|
|
857a0797b4 | ||
|
|
efc3511e15 | ||
|
|
aaccd2e7d2 | ||
|
|
1921143bfd | ||
|
|
07b7df8e68 | ||
|
|
c4804cfcb7 | ||
|
|
6bc5f1df47 | ||
|
|
f8a8cc691c | ||
|
|
a4b2a137e5 | ||
|
|
96b5c3da1d | ||
|
|
a716c0968a | ||
|
|
48c8333ed1 | ||
|
|
8224ff13c3 | ||
|
|
e150cd25e8 | ||
|
|
6206a0cdd1 | ||
|
|
d66b54dd13 | ||
|
|
5491c9444e | ||
|
|
0fc5708253 | ||
|
|
918342eeb7 | ||
|
|
ec66e0f291 | ||
|
|
d76a1f539b | ||
|
|
c81501c2ea | ||
|
|
d62bd4e99a | ||
|
|
863ce50a27 | ||
|
|
a9c4e626ac | ||
|
|
982df96c3c | ||
|
|
bc070cbeb9 | ||
|
|
dbac5f82d3 | ||
|
|
58c7786cf7 | ||
|
|
14db941950 | ||
|
|
36a531c204 | ||
|
|
0fa1f74699 | ||
|
|
9189723970 | ||
|
|
d7bcdeb5a7 | ||
|
|
4d9e81e22f | ||
|
|
466c39d12a | ||
|
|
86e7d04c80 | ||
|
|
a6b5ae3fde | ||
|
|
bb3f41dd69 | ||
|
|
b2bdf4a49d | ||
|
|
91268cdd4c | ||
|
|
6b621d2faa | ||
|
|
93db4fd2d8 | ||
|
|
f166552de0 | ||
|
|
aab3352624 | ||
|
|
26f33e5bc6 | ||
|
|
da4db42380 | ||
|
|
aa46b49ab7 | ||
|
|
b70e893364 | ||
|
|
a6bb454df5 | ||
|
|
099e76aeab | ||
|
|
309b00f2d9 | ||
|
|
acd181133c | ||
|
|
86f8999d30 | ||
|
|
661a64656e | ||
|
|
3380103a8f | ||
|
|
7ebf6e4150 | ||
|
|
05fdd9765c | ||
|
|
72a8c4dd83 | ||
|
|
423630bea6 | ||
|
|
2c9bcbae0b | ||
|
|
f70747ea15 | ||
|
|
b4c1525958 | ||
|
|
ed5df26704 | ||
|
|
d8253d41fd | ||
|
|
4eb7988e2c | ||
|
|
4f68aed812 | ||
|
|
322d01d3e1 | ||
|
|
aa34a0cce3 | ||
|
|
93a988f8f1 | ||
|
|
630fa5b07e | ||
|
|
77f623d261 | ||
|
|
30a4e88cff | ||
|
|
01b897bd8b | ||
|
|
be36e6de6a | ||
|
|
96b7a7ccb3 | ||
|
|
d8dd65d7ee | ||
|
|
aecdcb847d | ||
|
|
a383d389c4 | ||
|
|
565b2edfdd | ||
|
|
c1b4beeb64 | ||
|
|
9d489564d6 | ||
|
|
24a4260e55 | ||
|
|
71e5c0d743 | ||
|
|
48d28dc317 | ||
|
|
ad007bd51a | ||
|
|
6ed7f672fc |
@@ -9,6 +9,6 @@ module.exports = {
|
|||||||
'jsdoc/check-param-names': ['off'],
|
'jsdoc/check-param-names': ['off'],
|
||||||
'jsdoc/no-undefined-types': ['off'],
|
'jsdoc/no-undefined-types': ['off'],
|
||||||
'jsdoc/require-property-description': ['off'],
|
'jsdoc/require-property-description': ['off'],
|
||||||
'import/no-named-as-default-member': ['off']
|
'import/no-named-as-default-member': ['off'],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
37
.github/dependabot.yml
vendored
37
.github/dependabot.yml
vendored
@@ -2,7 +2,7 @@ version: 2
|
|||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: "/"
|
||||||
target-branch: "master"
|
target-branch: "main"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: saturday
|
day: saturday
|
||||||
@@ -11,6 +11,41 @@ updates:
|
|||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
reviewers:
|
reviewers:
|
||||||
- juliushaertl
|
- juliushaertl
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
target-branch: stable25
|
||||||
|
versioning-strategy: lockfile-only
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
day: saturday
|
||||||
|
time: "03:00"
|
||||||
|
timezone: Europe/Paris
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
open-pull-requests-limit: 30
|
||||||
|
labels:
|
||||||
|
- 3. to review
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
target-branch: stable24
|
||||||
|
versioning-strategy: lockfile-only
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
day: saturday
|
||||||
|
time: "03:00"
|
||||||
|
timezone: Europe/Paris
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
open-pull-requests-limit: 30
|
||||||
|
labels:
|
||||||
|
- 3. to review
|
||||||
|
- dependencies
|
||||||
|
|
||||||
- package-ecosystem: composer
|
- package-ecosystem: composer
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
* Resolves: # <!-- related github issue -->
|
* Resolves: # <!-- related github issue -->
|
||||||
* Target version: master
|
* Target version: main
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/appbuild.yml
vendored
6
.github/workflows/appbuild.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
|||||||
- name: Set up npm7
|
- name: Set up npm7
|
||||||
run: npm i -g npm@7
|
run: npm i -g npm@7
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: '7.4'
|
php-version: '7.4'
|
||||||
tools: composer
|
tools: composer
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb
|
||||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
sudo dpkg -i krankerl_0.14.0_amd64.deb
|
||||||
- name: package
|
- name: package
|
||||||
run: |
|
run: |
|
||||||
uname -a
|
uname -a
|
||||||
|
|||||||
55
.github/workflows/appstore-build-publish.yml
vendored
55
.github/workflows/appstore-build-publish.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PHP_VERSION: 7.4
|
PHP_VERSION: 8.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_publish:
|
build_and_publish:
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check actor permission
|
- name: Check actor permission
|
||||||
uses: skjnldsv/check-actor-permission@v2
|
uses: skjnldsv/check-actor-permission@e591dbfe838300c007028e1219ca82cc26e8d7c5 # v2.1
|
||||||
with:
|
with:
|
||||||
require: write
|
require: write
|
||||||
|
|
||||||
@@ -32,31 +32,31 @@ jobs:
|
|||||||
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.APP_NAME }}
|
path: ${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Get appinfo data
|
- name: Get appinfo data
|
||||||
id: appinfo
|
id: appinfo
|
||||||
uses: skjnldsv/xpath-action@master
|
uses: skjnldsv/xpath-action@7e6a7c379d0e9abc8acaef43df403ab4fc4f770c # master
|
||||||
with:
|
with:
|
||||||
filename: ${{ env.APP_NAME }}/appinfo/info.xml
|
filename: ${{ env.APP_NAME }}/appinfo/info.xml
|
||||||
expression: "//info//dependencies//nextcloud/@min-version"
|
expression: "//info//dependencies//nextcloud/@min-version"
|
||||||
|
|
||||||
- name: Read package.json node and npm engines version
|
- name: Read package.json node and npm engines version
|
||||||
uses: skjnldsv/read-package-engines-version-actions@v1.2
|
uses: skjnldsv/read-package-engines-version-actions@1bdcee71fa343c46b18dc6aceffb4cd1e35209c6 # v1.2
|
||||||
id: versions
|
id: versions
|
||||||
# Continue if no package.json
|
# Continue if no package.json
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
path: ${{ env.APP_NAME }}
|
path: ${{ env.APP_NAME }}
|
||||||
fallbackNode: "^12"
|
fallbackNode: "^16"
|
||||||
fallbackNpm: "^6"
|
fallbackNpm: "^7"
|
||||||
|
|
||||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
# Skip if no package.json
|
# Skip if no package.json
|
||||||
if: ${{ steps.versions.outputs.nodeVersion }}
|
if: ${{ steps.versions.outputs.nodeVersion }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
|
||||||
@@ -66,14 +66,16 @@ jobs:
|
|||||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||||
|
|
||||||
- name: Set up php ${{ env.PHP_VERSION }}
|
- name: Set up php ${{ env.PHP_VERSION }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0 # v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ env.PHP_VERSION }}
|
php-version: ${{ env.PHP_VERSION }}
|
||||||
coverage: none
|
coverage: none
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Check composer.json
|
- name: Check composer.json
|
||||||
id: check_composer
|
id: check_composer
|
||||||
uses: andstor/file-existence-action@v1
|
uses: andstor/file-existence-action@20b4d2e596410855db8f9ca21e96fbe18e12930b # v2
|
||||||
with:
|
with:
|
||||||
files: "${{ env.APP_NAME }}/composer.json"
|
files: "${{ env.APP_NAME }}/composer.json"
|
||||||
|
|
||||||
@@ -91,16 +93,29 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Install Krankerl
|
- name: Check Krankerl config
|
||||||
run: |
|
id: krankerl
|
||||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.13.0/krankerl_0.13.0_amd64.deb
|
uses: andstor/file-existence-action@20b4d2e596410855db8f9ca21e96fbe18e12930b # v2
|
||||||
sudo dpkg -i krankerl_0.13.0_amd64.deb
|
with:
|
||||||
|
files: ${{ env.APP_NAME }}/krankerl.toml
|
||||||
|
|
||||||
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }}
|
- name: Install Krankerl
|
||||||
# Try krankerl, fallback to makefile
|
if: steps.krankerl.outputs.files_exists == 'true'
|
||||||
|
run: |
|
||||||
|
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb
|
||||||
|
sudo dpkg -i krankerl_0.14.0_amd64.deb
|
||||||
|
|
||||||
|
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl
|
||||||
|
if: steps.krankerl.outputs.files_exists == 'true'
|
||||||
run: |
|
run: |
|
||||||
cd ${{ env.APP_NAME }}
|
cd ${{ env.APP_NAME }}
|
||||||
krankerl package || make appstore
|
krankerl package
|
||||||
|
|
||||||
|
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with makefile
|
||||||
|
if: steps.krankerl.outputs.files_exists != 'true'
|
||||||
|
run: |
|
||||||
|
cd ${{ env.APP_NAME }}
|
||||||
|
make appstore
|
||||||
|
|
||||||
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
|
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -111,7 +126,7 @@ jobs:
|
|||||||
unzip latest-$NCVERSION.zip
|
unzip latest-$NCVERSION.zip
|
||||||
|
|
||||||
- name: Checkout server master fallback
|
- name: Checkout server master fallback
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
if: ${{ steps.server-checkout.outcome != 'success' }}
|
if: ${{ steps.server-checkout.outcome != 'success' }}
|
||||||
with:
|
with:
|
||||||
repository: nextcloud/server
|
repository: nextcloud/server
|
||||||
@@ -133,7 +148,7 @@ jobs:
|
|||||||
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
|
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Attach tarball to github release
|
- name: Attach tarball to github release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@7319e4733ec7a184d739a6f412c40ffc339b69c7 # v2
|
||||||
id: attach_to_release
|
id: attach_to_release
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -143,7 +158,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: Upload app to Nextcloud appstore
|
- name: Upload app to Nextcloud appstore
|
||||||
uses: nextcloud-releases/nextcloud-appstore-push-action@v1
|
uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1
|
||||||
with:
|
with:
|
||||||
app_name: ${{ env.APP_NAME }}
|
app_name: ${{ env.APP_NAME }}
|
||||||
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
|
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
|
||||||
|
|||||||
8
.github/workflows/command-rebase.yml
vendored
8
.github/workflows/command-rebase.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Add reaction on start
|
- name: Add reaction on start
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
repository: ${{ github.event.repository.full_name }}
|
repository: ${{ github.event.repository.full_name }}
|
||||||
@@ -31,18 +31,18 @@ jobs:
|
|||||||
reaction-type: "+1"
|
reaction-type: "+1"
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
|
|
||||||
- name: Automatic Rebase
|
- name: Automatic Rebase
|
||||||
uses: cirrus-actions/rebase@1.7
|
uses: cirrus-actions/rebase@6e572f08c244e2f04f9beb85a943eb618218714d # 1.7
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
|
|
||||||
- name: Add reaction on failure
|
- name: Add reaction on failure
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
|
|||||||
26
.github/workflows/cypress.yml
vendored
26
.github/workflows/cypress.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- stable*
|
- stable*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -21,9 +21,9 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [14.x]
|
||||||
# containers: [1, 2, 3]
|
# containers: [1, 2, 3]
|
||||||
php-versions: [ '7.4' ]
|
php-versions: [ '8.0' ]
|
||||||
databases: [ 'sqlite' ]
|
databases: [ 'sqlite' ]
|
||||||
server-versions: [ 'stable25' ]
|
server-versions: [ 'stable26' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
@@ -33,6 +33,11 @@ jobs:
|
|||||||
- name: Set up npm7
|
- name: Set up npm7
|
||||||
run: npm i -g npm@7
|
run: npm i -g npm@7
|
||||||
|
|
||||||
|
- name: Register text Git reference
|
||||||
|
run: |
|
||||||
|
text_app_ref="$(if [ "${{ matrix.server-versions }}" = "master" ]; then echo -n "main"; else echo -n "${{ matrix.server-versions }}"; fi)"
|
||||||
|
echo "text_app_ref=$text_app_ref" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout server
|
- name: Checkout server
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -51,8 +56,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: apps/${{ env.APP_NAME }}
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Checkout text
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: nextcloud/text
|
||||||
|
ref: ${{ env.text_app_ref }}
|
||||||
|
path: apps/text
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
|
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
|
||||||
@@ -84,7 +96,7 @@ jobs:
|
|||||||
curl -v http://localhost:8081/index.php/login
|
curl -v http://localhost:8081/index.php/login
|
||||||
|
|
||||||
- name: Cypress run
|
- name: Cypress run
|
||||||
uses: cypress-io/github-action@v4
|
uses: cypress-io/github-action@v5
|
||||||
with:
|
with:
|
||||||
record: true
|
record: true
|
||||||
parallel: false
|
parallel: false
|
||||||
@@ -96,7 +108,7 @@ jobs:
|
|||||||
npm_package_name: ${{ env.APP_NAME }}
|
npm_package_name: ${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Upload test failure screenshots
|
- name: Upload test failure screenshots
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: Upload screenshots
|
name: Upload screenshots
|
||||||
@@ -104,7 +116,7 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
- name: Upload nextcloud logs
|
- name: Upload nextcloud logs
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: Upload nextcloud log
|
name: Upload nextcloud log
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: dependabot-approve-merge-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-approve-merge:
|
auto-approve-merge:
|
||||||
if: github.actor == 'dependabot[bot]'
|
if: github.actor == 'dependabot[bot]'
|
||||||
@@ -25,12 +29,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Github actions bot approve
|
# Github actions bot approve
|
||||||
- uses: hmarr/auto-approve-action@v2
|
- uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# Nextcloud bot approve and merge request
|
# Nextcloud bot approve and merge request
|
||||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
- uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2
|
||||||
with:
|
with:
|
||||||
target: minor
|
target: minor
|
||||||
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
||||||
|
|||||||
17
.github/workflows/fixup.yml
vendored
17
.github/workflows/fixup.yml
vendored
@@ -5,16 +5,29 @@
|
|||||||
|
|
||||||
name: Pull request checks
|
name: Pull request checks
|
||||||
|
|
||||||
on: pull_request
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, ready_for_review, reopened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: fixup-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
commit-message-check:
|
commit-message-check:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
name: Block fixup and squash commits
|
name: Block fixup and squash commits
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Run check
|
- name: Run check
|
||||||
uses: xt0rted/block-autosquash-commits-action@v2
|
uses: xt0rted/block-autosquash-commits-action@79880c36b4811fe549cfffe20233df88876024e7 # v2
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
68
.github/workflows/integration.yml
vendored
68
.github/workflows/integration.yml
vendored
@@ -12,6 +12,7 @@ on:
|
|||||||
- 'composer.lock'
|
- 'composer.lock'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- main
|
||||||
- master
|
- master
|
||||||
- stable*
|
- stable*
|
||||||
|
|
||||||
@@ -25,9 +26,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4']
|
php-versions: ['8.1']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['stable25']
|
server-versions: ['stable26']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
@@ -70,16 +71,17 @@ jobs:
|
|||||||
path: apps/${{ env.APP_NAME }}
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
tools: phpunit
|
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu
|
||||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
|
ini-values:
|
||||||
|
apc.enable_cli=on
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Set up PHPUnit
|
- name: Set up dependencies
|
||||||
working-directory: apps/${{ env.APP_NAME }}
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
run: composer i
|
run: composer i --no-dev
|
||||||
|
|
||||||
- name: Set up Nextcloud
|
- name: Set up Nextcloud
|
||||||
run: |
|
run: |
|
||||||
@@ -90,11 +92,63 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
mkdir data
|
mkdir data
|
||||||
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||||
|
./occ config:system:set hashing_default_password --value=true --type=boolean
|
||||||
|
./occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
|
||||||
|
./occ config:system:set memcache.distributed --value="\\OC\\Memcache\\APCu"
|
||||||
cat config/config.php
|
cat config/config.php
|
||||||
./occ user:list
|
./occ user:list
|
||||||
./occ app:enable --force ${{ env.APP_NAME }}
|
./occ app:enable --force ${{ env.APP_NAME }}
|
||||||
|
./occ config:system:set query_log_file --value '/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log'
|
||||||
php -S localhost:8080 &
|
php -S localhost:8080 &
|
||||||
|
|
||||||
- name: Run behat
|
- name: Run behat
|
||||||
working-directory: apps/${{ env.APP_NAME }}/tests/integration
|
working-directory: apps/${{ env.APP_NAME }}/tests/integration
|
||||||
run: ./run.sh
|
run: ./run.sh
|
||||||
|
|
||||||
|
- name: Query count
|
||||||
|
if: ${{ matrix.databases == 'mysql' }}
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
let myOutput = ''
|
||||||
|
let myError = ''
|
||||||
|
|
||||||
|
const options = {}
|
||||||
|
options.listeners = {
|
||||||
|
stdout: (data) => {
|
||||||
|
myOutput += data.toString()
|
||||||
|
},
|
||||||
|
stderr: (data) => {
|
||||||
|
myError += data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await exec.exec(`/bin/bash -c "cat /home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log | wc -l"`, [], options)
|
||||||
|
msg = myOutput
|
||||||
|
const queryCount = parseInt(myOutput, 10)
|
||||||
|
|
||||||
|
myOutput = ''
|
||||||
|
await exec.exec('cat', ['/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/apps/${{ env.APP_NAME }}/tests/integration/base-query-count.txt'], options)
|
||||||
|
const baseCount = parseInt(myOutput, 10)
|
||||||
|
|
||||||
|
const absoluteIncrease = queryCount - baseCount
|
||||||
|
const relativeIncrease = baseCount <= 0 ? 100 : (parseInt((absoluteIncrease / baseCount * 10000), 10) / 100)
|
||||||
|
|
||||||
|
if (absoluteIncrease >= 100 || relativeIncrease > 5) {
|
||||||
|
const comment = `🐢 Performance warning.\nIt looks like the query count of the integration tests increased with this PR.\nDatabase query count is now ` + queryCount + ' was ' + baseCount + ' (+' + relativeIncrease + '%)\nPlease check your code again. If you added a new test this can be expected and the base value in tests/integration/base-query-count.txt can be increased.'
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: comment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (queryCount < 100) {
|
||||||
|
const comment = `🐈 Performance messuring seems broken. Failed to get query count.`
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: comment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
62
.github/workflows/lint-eslint.yml
vendored
Normal file
62
.github/workflows/lint-eslint.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 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
|
||||||
|
#
|
||||||
|
# Use lint-eslint together with lint-eslint-when-unrelated to make eslint a required check for GitHub actions
|
||||||
|
# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/**'
|
||||||
|
- 'src/**'
|
||||||
|
- 'appinfo/info.xml'
|
||||||
|
- 'package.json'
|
||||||
|
- 'package-lock.json'
|
||||||
|
- 'tsconfig.json'
|
||||||
|
- '.eslintrc.*'
|
||||||
|
- '.eslintignore'
|
||||||
|
- '**.js'
|
||||||
|
- '**.ts'
|
||||||
|
- '**.vue'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lint-eslint-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: eslint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|
||||||
|
- name: Read package.json node and npm engines version
|
||||||
|
uses: skjnldsv/read-package-engines-version-actions@1bdcee71fa343c46b18dc6aceffb4cd1e35209c6 # v1.2
|
||||||
|
id: versions
|
||||||
|
with:
|
||||||
|
fallbackNode: '^16'
|
||||||
|
fallbackNpm: '^7'
|
||||||
|
|
||||||
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
|
||||||
|
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||||
|
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
39
.github/workflows/lint-php-cs.yml
vendored
Normal file
39
.github/workflows/lint-php-cs.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 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: Lint
|
||||||
|
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lint-php-cs-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: php-cs
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|
||||||
|
- name: Set up php
|
||||||
|
uses: shivammathur/setup-php@2.24.0 # v2
|
||||||
|
with:
|
||||||
|
php-version: 8.1
|
||||||
|
coverage: none
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer i
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
|
||||||
59
.github/workflows/lint-php.yml
vendored
Normal file
59
.github/workflows/lint-php.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# 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: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lint-php-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: [ "8.0", "8.1", "8.2" ]
|
||||||
|
|
||||||
|
name: php-lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@2.24.0 # v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
coverage: none
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: composer run lint
|
||||||
|
|
||||||
|
summary:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: php-lint
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: php-lint-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.php-lint.result != 'success' && needs.php-lint.result != 'skipped' }}; then exit 1; fi
|
||||||
46
.github/workflows/lint-stylelint.yml
vendored
Normal file
46
.github/workflows/lint-stylelint.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 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: Lint
|
||||||
|
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lint-stylelint-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: stylelint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|
||||||
|
- name: Read package.json node and npm engines version
|
||||||
|
uses: skjnldsv/read-package-engines-version-actions@1bdcee71fa343c46b18dc6aceffb4cd1e35209c6 # v1.2
|
||||||
|
id: versions
|
||||||
|
with:
|
||||||
|
fallbackNode: '^16'
|
||||||
|
fallbackNpm: '^7'
|
||||||
|
|
||||||
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
|
||||||
|
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||||
|
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run stylelint
|
||||||
88
.github/workflows/lint.yml
vendored
88
.github/workflows/lint.yml
vendored
@@ -1,88 +0,0 @@
|
|||||||
name: Lint
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- stable*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
php:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-versions: ['7.4', '8.0', '8.1']
|
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }} lint
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set up php${{ matrix.php-versions }}
|
|
||||||
uses: shivammathur/setup-php@2.21.2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-versions }}
|
|
||||||
coverage: none
|
|
||||||
- name: Lint
|
|
||||||
run: composer run lint
|
|
||||||
|
|
||||||
php-cs-fixer:
|
|
||||||
name: php-cs check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up php
|
|
||||||
uses: shivammathur/setup-php@2.21.2
|
|
||||||
with:
|
|
||||||
php-version: 7.4
|
|
||||||
coverage: none
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer i
|
|
||||||
- name: Run coding standards check
|
|
||||||
run: composer run cs:check
|
|
||||||
|
|
||||||
node:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use node ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Set up npm7
|
|
||||||
run: npm i -g npm@7
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: ESLint
|
|
||||||
run: npm run lint
|
|
||||||
|
|
||||||
stylelint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
name: stylelint node${{ matrix.node-version }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up node ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Set up npm7
|
|
||||||
run: npm i -g npm@7
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: npm run stylelint
|
|
||||||
6
.github/workflows/nightly.yml
vendored
6
.github/workflows/nightly.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Set up npm7
|
- name: Set up npm7
|
||||||
run: npm i -g npm@7
|
run: npm i -g npm@7
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: '7.4'
|
php-version: '7.4'
|
||||||
tools: composer
|
tools: composer
|
||||||
@@ -44,14 +44,14 @@ jobs:
|
|||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
git tag -f nightly
|
git tag -f nightly
|
||||||
- name: Push tag
|
- name: Push tag
|
||||||
uses: juliushaertl/github-push-action@master
|
uses: juliushaertl/github-push-action@main
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tags: true
|
tags: true
|
||||||
force: true
|
force: true
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: juliushaertl/action-release@master
|
uses: juliushaertl/action-release@main
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: nightly
|
tag: nightly
|
||||||
|
|||||||
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@@ -26,9 +26,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4', '8.0', '8.1']
|
php-versions: ['8.0', '8.1', '8.2']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['stable25']
|
server-versions: ['stable26']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
path: apps/${{ env.APP_NAME }}
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
tools: phpunit
|
tools: phpunit
|
||||||
|
|||||||
12
.github/workflows/psalm.yml
vendored
12
.github/workflows/psalm.yml
vendored
@@ -13,6 +13,10 @@ on:
|
|||||||
- main
|
- main
|
||||||
- stable*
|
- stable*
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: psalm-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
static-analysis:
|
static-analysis:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -20,13 +24,15 @@ jobs:
|
|||||||
name: Nextcloud
|
name: Nextcloud
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|
||||||
- name: Set up php
|
- name: Set up php
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@2.24.0 # v2
|
||||||
with:
|
with:
|
||||||
php-version: 7.4
|
php-version: 8.1
|
||||||
coverage: none
|
coverage: none
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer i
|
run: composer i
|
||||||
|
|||||||
19
.github/workflows/update-nextcloud-ocp.yml
vendored
19
.github/workflows/update-nextcloud-ocp.yml
vendored
@@ -17,22 +17,24 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
branches: ["master", "stable25", "stable24", "stable23"]
|
branches: ["master", "stable25", "stable24"]
|
||||||
|
|
||||||
name: update-nextcloud-ocp-${{ matrix.branches }}
|
name: update-nextcloud-ocp-${{ matrix.branches }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ matrix.branches }}
|
ref: ${{ matrix.branches }}
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Set up php7.4
|
- name: Set up php8.1
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@2.24.0 # v2
|
||||||
with:
|
with:
|
||||||
php-version: 7.4
|
php-version: 8.1
|
||||||
extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||||
coverage: none
|
coverage: none
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Composer install
|
- name: Composer install
|
||||||
run: composer install
|
run: composer install
|
||||||
@@ -45,14 +47,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git clean -f 3rdparty
|
git clean -f 3rdparty
|
||||||
git clean -f vendor
|
git clean -f vendor
|
||||||
git checkout 3rdparty vendor
|
git clean -f vendor-bin
|
||||||
|
git checkout 3rdparty vendor vendor-bin
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v3
|
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
commit-message: Update psalm baseline
|
commit-message: "chore(dev-deps): Bump nextcloud/ocp package"
|
||||||
committer: GitHub <noreply@github.com>
|
committer: GitHub <noreply@github.com>
|
||||||
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
|
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
|
||||||
signoff: true
|
signoff: true
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,9 +3,11 @@ js/
|
|||||||
build/
|
build/
|
||||||
css/style.css
|
css/style.css
|
||||||
css/vendor.css
|
css/vendor.css
|
||||||
|
cypress/videos/
|
||||||
tests/integration/vendor/
|
tests/integration/vendor/
|
||||||
tests/integration/composer.lock
|
tests/integration/composer.lock
|
||||||
tests/.phpunit.result.cache
|
tests/.phpunit.result.cache
|
||||||
vendor/
|
vendor/
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
\.idea/
|
\.idea/
|
||||||
|
settings.json
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja
|
lang_map = hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi
|
||||||
|
|
||||||
[o:nextcloud:p:nextcloud:r:deck]
|
[o:nextcloud:p:nextcloud:r:deck]
|
||||||
file_filter = translationfiles/<lang>/deck.po
|
file_filter = translationfiles/<lang>/deck.po
|
||||||
|
|||||||
102
CHANGELOG.md
102
CHANGELOG.md
@@ -1,54 +1,66 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## 1.8.5
|
## 1.9.2
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- fix: Properly overwrite z-index of datepicker above modal [#4667](https://github.com/nextcloud/deck/pull/4667)
|
- fix: Properly overwrite z-index of datepicker above modal [#4665](https://github.com/nextcloud/deck/pull/4665)
|
||||||
|
|
||||||
|
|
||||||
## 1.8.4
|
## 1.9.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- fix: Use passed userid when getting attachment folder [#4540](https://github.com/nextcloud/deck/pull/4540)
|
- Gracefully handle not found card for a share [#4567](https://github.com/nextcloud/deck/pull/4567)
|
||||||
- fix: Adapt NcEmptyContent usages to new slots [#4563](https://github.com/nextcloud/deck/pull/4563)
|
- fix: Adapt NcEmptyContent usages to new slots [#4562](https://github.com/nextcloud/deck/pull/4562)
|
||||||
- Gracefully handle not found card for a share [#4568](https://github.com/nextcloud/deck/pull/4568)
|
- allow user to toggle visibility of the calendar for a deck board [#4625](https://github.com/nextcloud/deck/pull/4625)
|
||||||
- allow user to toggle visibility of the calendar for a deck board [#4626](https://github.com/nextcloud/deck/pull/4626)
|
- fix: Append datetime picker to body to avoid cut off [#4644](https://github.com/nextcloud/deck/pull/4644)
|
||||||
- fix: Append datetime picker to body to avoid cut off [#4645](https://github.com/nextcloud/deck/pull/4645)
|
- chore: Remove unused @nextcloud/vue-dashboard @juliushaertl [#4650](https://github.com/nextcloud/deck/pull/4650)
|
||||||
- Fix : Overlapping expiry dates on tags [#4536](https://github.com/nextcloud/deck/pull/4536)
|
- fix: Bring back overdue column by removing faulty condition [#4662](https://github.com/nextcloud/deck/pull/4662)
|
||||||
- Better display of card dates (creation and change dates) [#4620](https://github.com/nextcloud/deck/pull/4620)
|
- Fix : Overlapping expiry dates on tags [#4537](https://github.com/nextcloud/deck/pull/4537)
|
||||||
- Dependency updates
|
- Better display of card dates (creation and change dates) [#4619](https://github.com/nextcloud/deck/pull/4619)
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
## 1.8.3
|
## 1.9.0
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix component renaming so that acl works on shares again [#4328](https://github.com/nextcloud/deck/pull/4328)
|
|
||||||
- Permanently delete deck cards marked as deleted after 5 min in a cron job [#4301](https://github.com/nextcloud/deck/pull/4301)
|
|
||||||
- Dependency updates
|
|
||||||
|
|
||||||
|
|
||||||
## 1.8.2
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- minor style fixes [#4201](https://github.com/nextcloud/deck/pull/4201)
|
|
||||||
- feat: add validators to check values in services [#4174](https://github.com/nextcloud/deck/pull/4174)
|
|
||||||
- Add integration test for attachment handling on cards [#4179](https://github.com/nextcloud/deck/pull/4179)
|
|
||||||
- Add missing userId property [#4198](https://github.com/nextcloud/deck/pull/4198)
|
|
||||||
|
|
||||||
## 1.8.1
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix Duedate activity @nickvergessen [#4155](https://github.com/nextcloud/deck/pull/4155)
|
|
||||||
|
|
||||||
## 1.8.0
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Live updates on board collaboration using notify_push @alangecker [#4273](https://github.com/nextcloud/deck/pull/4273)
|
||||||
|
- Basic notify_push usage with session handling @alangecker [#3876](https://github.com/nextcloud/deck/pull/3876)
|
||||||
|
- Use text as editor if available [#4399](https://github.com/nextcloud/deck/pull/4399)
|
||||||
|
- Improve reference provider and add reference widgets @julien-nc [#4422](https://github.com/nextcloud/deck/pull/4422)
|
||||||
|
- Tag creation from card view @juliushaertl [#4344](https://github.com/nextcloud/deck/pull/4344)
|
||||||
|
- Optimize query performance with larger board or card count @[#4452](https://github.com/nextcloud/deck/pull/4452)
|
||||||
|
- Export Board as CSV @david-loe [#3065](https://github.com/nextcloud/deck/pull/3065)
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- fix(cards): Fix card sizing by limiting too wide style rules [#4521](https://github.com/nextcloud/deck/pull/4521)
|
||||||
|
- fix(references): Mute NoPermissionException as it is expected to happen for references [#4516](https://github.com/nextcloud/deck/pull/4516)
|
||||||
|
- fix(API): Fix board API details parameter to work as expected [#4519](https://github.com/nextcloud/deck/pull/4519)
|
||||||
|
- fix(sessions): Do not send close request without token [#4525](https://github.com/nextcloud/deck/pull/4525)
|
||||||
|
- fix: Avoid mutating the due date when calculating days @juliushaertl [#4488](https://github.com/nextcloud/deck/pull/4488)
|
||||||
|
- fix: Pass user id along to properly check permissions in background jobs @juliushaertl [#4485](https://github.com/nextcloud/deck/pull/4485)
|
||||||
|
- fix: Use passed userid when getting attachment folder @juliushaertl [#4487](https://github.com/nextcloud/deck/pull/4487)
|
||||||
|
- fix: Use proper z-index for text menubar @juliushaertl [#4490](https://github.com/nextcloud/deck/pull/4490)
|
||||||
|
- fix(dashboard): Fix undefined array index @marcelklehr [#4492](https://github.com/nextcloud/deck/pull/4492)
|
||||||
|
- fix: Always return sorted index array to make sure a json array is the result @juliushaertl [#4493](https://github.com/nextcloud/deck/pull/4493)
|
||||||
|
- Fix component renaming so that acl works on shares again @small1 [#4315](https://github.com/nextcloud/deck/pull/4315)
|
||||||
|
- fix(Sidebar): Only close sidebar on v-click-outside for specific targets @juliushaertl [#4350](https://github.com/nextcloud/deck/pull/4350)
|
||||||
|
- add basic e2e tests for stack title @shoetten [#4206](https://github.com/nextcloud/deck/pull/4206)
|
||||||
|
- App metadata: add links to user and developer documentation @p-bo [#4356](https://github.com/nextcloud/deck/pull/4356)
|
||||||
|
- Update signature of Entity::markFieldUpdated @nickvergessen [#4398](https://github.com/nextcloud/deck/pull/4398)
|
||||||
|
- Remove updated nightly information @xf- [#4419](https://github.com/nextcloud/deck/pull/4419)
|
||||||
|
- perf: Register notifier and resource listener lazy @juliushaertl [#4439](https://github.com/nextcloud/deck/pull/4439)
|
||||||
|
- perf: Lazy load dashboard components @juliushaertl [#4440](https://github.com/nextcloud/deck/pull/4440)
|
||||||
|
- Optimise upcomming overview creation @Raudius [#3793](https://github.com/nextcloud/deck/pull/3793)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.0-beta.1
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
- Nextcloud 25 compatibility
|
- Nextcloud 25 compatibility
|
||||||
- Performance improvements
|
- Performance improvements
|
||||||
- Use capped memory cache for board permissions @juliushaertl [#3980](https://github.com/nextcloud/deck/pull/3980)
|
- Use capped memory cache for board permissions @juliushaertl [#3980](https://github.com/nextcloud/deck/pull/3980)
|
||||||
@@ -59,26 +71,10 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Improve filter popover accessibility @juliushaertl [#3820](https://github.com/nextcloud/deck/pull/3820)
|
- Improve filter popover accessibility @juliushaertl [#3820](https://github.com/nextcloud/deck/pull/3820)
|
||||||
- Set ids to skip to content/navigation @juliushaertl [#3924](https://github.com/nextcloud/deck/pull/3924)
|
- Set ids to skip to content/navigation @juliushaertl [#3924](https://github.com/nextcloud/deck/pull/3924)
|
||||||
- Invert icons properly in dark mode @juliushaertl [#3939](https://github.com/nextcloud/deck/pull/3939)
|
- Invert icons properly in dark mode @juliushaertl [#3939](https://github.com/nextcloud/deck/pull/3939)
|
||||||
- Implement card reference widget @eneiluj [#4031](https://github.com/nextcloud/deck/pull/4031)
|
- Bump dependencies
|
||||||
- Implement new dashboard widget interfaces @eneiluj [#4033](https://github.com/nextcloud/deck/pull/4033)
|
|
||||||
- Add related resources panel to board sharing tab sidebar @Pytal [#4000](https://github.com/nextcloud/deck/pull/4000)
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix sorting stacks [#4116](https://github.com/nextcloud/deck/pull/4116)
|
|
||||||
- Fix issue with duedate format [#4140](https://github.com/nextcloud/deck/pull/4140)
|
|
||||||
- Fix missing icon for activity rendering [#4090](https://github.com/nextcloud/deck/pull/4090)
|
|
||||||
- disables autocomplete on card creation [#4142](https://github.com/nextcloud/deck/pull/4142)
|
|
||||||
- Set event link also for notifications that get emitted from activities [#4117](https://github.com/nextcloud/deck/pull/4117)
|
|
||||||
- Fix attachment creator name: show display name @eneiluj [#4036](https://github.com/nextcloud/deck/pull/4036)
|
|
||||||
- Fix reference provider when caching @eneiluj [#4056](https://github.com/nextcloud/deck/pull/4056)
|
|
||||||
- Use global import for nextcloud-vue [#4072](https://github.com/nextcloud/deck/pull/4072)
|
|
||||||
- Disable Create card button while no stack is chosen @icewind1991 [#4014](https://github.com/nextcloud/deck/pull/4014)
|
|
||||||
- Adjust testing matrix for Nextcloud 25 on stable25 @nickvergessen [#4068](https://github.com/nextcloud/deck/pull/4068)
|
|
||||||
- Fix Card menu not displaying when description is not set @marcelklehr [#4105](https://github.com/nextcloud/deck/pull/4105)
|
|
||||||
- Reference widget adjustments for Text [#4075](https://github.com/nextcloud/deck/pull/4075)
|
|
||||||
- use OCP\Collaboration\Reference\Reference [#4078](https://github.com/nextcloud/deck/pull/4078)
|
|
||||||
- Cache user membership for circles [#4141](https://github.com/nextcloud/deck/pull/4141)
|
|
||||||
- set last modified when the card was found. Fixes #3763 @ylebre [#3796](https://github.com/nextcloud/deck/pull/3796)
|
- set last modified when the card was found. Fixes #3763 @ylebre [#3796](https://github.com/nextcloud/deck/pull/3796)
|
||||||
- Increase file count after sharing @luka-nextcloud [#3682](https://github.com/nextcloud/deck/pull/3682)
|
- Increase file count after sharing @luka-nextcloud [#3682](https://github.com/nextcloud/deck/pull/3682)
|
||||||
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 @Ben-Ro [#3811](https://github.com/nextcloud/deck/pull/3811)
|
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 @Ben-Ro [#3811](https://github.com/nextcloud/deck/pull/3811)
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -30,6 +30,16 @@ build: clean-dist install-deps build-js
|
|||||||
|
|
||||||
release: clean-dist install-deps-nodev build-js
|
release: clean-dist install-deps-nodev build-js
|
||||||
|
|
||||||
|
lint: lint-js lint-php
|
||||||
|
|
||||||
|
lint-js:
|
||||||
|
npm run lint
|
||||||
|
npm run stylelint
|
||||||
|
|
||||||
|
lint-php:
|
||||||
|
composer run lint 1>/dev/null
|
||||||
|
composer run cs:check
|
||||||
|
|
||||||
build-js: install-deps-js
|
build-js: install-deps-js
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Deck
|
# Deck
|
||||||
|
|
||||||
[](https://travis-ci.org/nextcloud/deck) [](https://codecov.io/github/nextcloud/deck) [](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/deck&utm_campaign=Badge_Grade) [](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=master) [](https://webchat.freenode.net/?channels=nextcloud-deck)
|
[](https://travis-ci.org/nextcloud/deck) [](https://codecov.io/github/nextcloud/deck) [](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/deck&utm_campaign=Badge_Grade) [](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=main) [](https://webchat.freenode.net/?channels=nextcloud-deck)
|
||||||
|
|
||||||
|
|
||||||
Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
|
Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
|
||||||
@@ -20,6 +20,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
|
|||||||
### Mobile apps
|
### Mobile apps
|
||||||
|
|
||||||
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
|
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
|
||||||
|
- [deck NG for Android and iOS](https://github.com/meltzow/deck-ng) - It is available in [Google Play Store](https://play.google.com/store/apps/details?id=net.meltzow.deckng) and [Apple App Store](https://apps.apple.com/us/app/deck-ng/id6443334702)
|
||||||
|
|
||||||
### 3rd-Party Integrations
|
### 3rd-Party Integrations
|
||||||
|
|
||||||
@@ -48,10 +49,6 @@ make build
|
|||||||
|
|
||||||
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl, composer`
|
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl, composer`
|
||||||
|
|
||||||
### Install the nightly builds
|
|
||||||
|
|
||||||
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
|
||||||
|
|
||||||
## Performance limitations
|
## Performance limitations
|
||||||
|
|
||||||
Deck is not yet ready for intensive usage.
|
Deck is not yet ready for intensive usage.
|
||||||
@@ -65,13 +62,18 @@ Improvements on Nextcloud server and Deck itself will improve the situation.
|
|||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
|
### Nextcloud environment
|
||||||
|
|
||||||
|
You need to setup a [development environment](https://docs.nextcloud.com/server/latest/developer_manual//getting_started/devenv.html) of the current nextcloud version. You can also alternatively install & run the [nextcloud docker container](https://github.com/juliushaertl/nextcloud-docker-dev).
|
||||||
|
After the finished installation, you can clone the deck project directly in the `/[nextcloud-docker-dev-dir]/workspace/server/apps/` folder.
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
|
|
||||||
Nothing to prepare, just dig into the code.
|
Nothing to prepare, just dig into the code.
|
||||||
|
|
||||||
### JavaScript
|
### JavaScript
|
||||||
|
|
||||||
This requires at least Node 14 and npm 7 to be installed.
|
This requires at least Node 16 and npm 7 to be installed.
|
||||||
|
|
||||||
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
|
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Your report should include:
|
|||||||
- Reproduction steps
|
- Reproduction steps
|
||||||
|
|
||||||
A member of the security team will confirm the vulnerability, determine its impact, and develop a fix.
|
A member of the security team will confirm the vulnerability, determine its impact, and develop a fix.
|
||||||
The fix will be applied to the master branch, tested, and packaged in the next security release.
|
The fix will be applied to the main branch, tested, and packaged in the next security release.
|
||||||
The vulnerability will be publicly announced after the release. Finally, your name will be added
|
The vulnerability will be publicly announced after the release. Finally, your name will be added
|
||||||
to the [hall of fame](https://hackerone.com/nextcloud/thanks) as a thank you from the entire Nextcloud community. Note our
|
to the [hall of fame](https://hackerone.com/nextcloud/thanks) as a thank you from the entire Nextcloud community. Note our
|
||||||
[threat model](https://nextcloud.com/security/threat-model) to know what is expected behavior.
|
[threat model](https://nextcloud.com/security/threat-model) to know what is expected behavior.
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
- 🚀 Get your project organized
|
- 🚀 Get your project organized
|
||||||
|
|
||||||
</description>
|
</description>
|
||||||
<version>1.8.5</version>
|
<version>1.9.2</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>Julius Härtl</author>
|
<author>Julius Härtl</author>
|
||||||
|
<documentation>
|
||||||
|
<user>https://deck.readthedocs.io/en/latest/User_documentation_en/</user>
|
||||||
|
<developer>https://deck.readthedocs.io/en/latest/API/</developer>
|
||||||
|
</documentation>
|
||||||
<namespace>Deck</namespace>
|
<namespace>Deck</namespace>
|
||||||
<types>
|
<types>
|
||||||
<dav/>
|
<dav/>
|
||||||
@@ -34,13 +38,19 @@
|
|||||||
<database min-version="9.4">pgsql</database>
|
<database min-version="9.4">pgsql</database>
|
||||||
<database>sqlite</database>
|
<database>sqlite</database>
|
||||||
<database min-version="8.0">mysql</database>
|
<database min-version="8.0">mysql</database>
|
||||||
<nextcloud min-version="25" max-version="25"/>
|
<nextcloud min-version="26" max-version="26"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<background-jobs>
|
<background-jobs>
|
||||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||||
<job>OCA\Deck\Cron\ScheduledNotifications</job>
|
<job>OCA\Deck\Cron\ScheduledNotifications</job>
|
||||||
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
|
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
|
||||||
|
<job>OCA\Deck\Cron\SessionsCleanup</job>
|
||||||
</background-jobs>
|
</background-jobs>
|
||||||
|
<repair-steps>
|
||||||
|
<live-migration>
|
||||||
|
<step>OCA\Deck\Migration\DeletedCircleCleanup</step>
|
||||||
|
</live-migration>
|
||||||
|
</repair-steps>
|
||||||
<commands>
|
<commands>
|
||||||
<command>OCA\Deck\Command\UserExport</command>
|
<command>OCA\Deck\Command\UserExport</command>
|
||||||
<command>OCA\Deck\Command\BoardImport</command>
|
<command>OCA\Deck\Command\BoardImport</command>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ return [
|
|||||||
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||||
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
||||||
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
|
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
|
||||||
|
['name' => 'board#export', 'url' => '/boards/{boardId}/export', 'verb' => 'GET'],
|
||||||
|
|
||||||
// stacks
|
// stacks
|
||||||
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
|
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
|
||||||
@@ -149,5 +150,10 @@ return [
|
|||||||
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
|
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'search#search', 'url' => '/api/v{apiVersion}/search', 'verb' => 'GET'],
|
['name' => 'search#search', 'url' => '/api/v{apiVersion}/search', 'verb' => 'GET'],
|
||||||
|
|
||||||
|
// sessions
|
||||||
|
['name' => 'Session#create', 'url' => '/api/v{apiVersion}/session/create', 'verb' => 'PUT'],
|
||||||
|
['name' => 'Session#sync', 'url' => '/api/v{apiVersion}/session/sync', 'verb' => 'POST'],
|
||||||
|
['name' => 'Session#close', 'url' => '/api/v{apiVersion}/session/close', 'verb' => 'POST'],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
"phpunit/phpunit": "^9",
|
"phpunit/phpunit": "^9",
|
||||||
"nextcloud/coding-standard": "^1.0.0",
|
"nextcloud/coding-standard": "^1.0.0",
|
||||||
"symfony/event-dispatcher": "^4.0",
|
"symfony/event-dispatcher": "^4.0",
|
||||||
"vimeo/psalm": "^4.3",
|
"vimeo/psalm": "^5.4",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
"nextcloud/ocp": "dev-stable25"
|
"nextcloud/ocp": "dev-stable26"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"composer/package-versions-deprecated": true
|
"composer/package-versions-deprecated": true
|
||||||
},
|
},
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.4"
|
"php": "8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"@test:unit",
|
"@test:unit",
|
||||||
"@test:integration"
|
"@test:integration"
|
||||||
],
|
],
|
||||||
"test:unit": "phpunit -c tests/phpunit.xml",
|
"test:unit": "vendor/bin/phpunit -c tests/phpunit.xml",
|
||||||
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
"test:integration": "vendor/bin/phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
611
composer.lock
generated
611
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@ module.exports = defineConfig({
|
|||||||
return require('./cypress/plugins/index.js')(on, config)
|
return require('./cypress/plugins/index.js')(on, config)
|
||||||
},
|
},
|
||||||
baseUrl: 'http://nextcloud.local/index.php',
|
baseUrl: 'http://nextcloud.local/index.php',
|
||||||
experimentalSessionAndOrigin: true,
|
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
5
cypress/.eslintrc.js
Normal file
5
cypress/.eslintrc.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'plugin:cypress/recommended',
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { randHash } from '../utils'
|
import { randUser } from '../utils/index.js'
|
||||||
const randUser = randHash()
|
const user = randUser()
|
||||||
|
const recipient = randUser()
|
||||||
|
|
||||||
describe('Board', function() {
|
describe('Board', function() {
|
||||||
const password = 'pass123'
|
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
cy.createUser(user)
|
||||||
|
cy.createUser(recipient)
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
cy.login(randUser, password)
|
cy.login(user)
|
||||||
|
cy.visit('/apps/deck')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can create a board', function() {
|
it('Can create a board', function() {
|
||||||
@@ -21,7 +23,6 @@ describe('Board', function() {
|
|||||||
}).as('createBoardRequest')
|
}).as('createBoardRequest')
|
||||||
|
|
||||||
// Click "Add board"
|
// Click "Add board"
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||||
.eq(3).find('a').first().click({ force: true })
|
.eq(3).find('a').first().click({ force: true })
|
||||||
|
|
||||||
@@ -38,4 +39,18 @@ describe('Board', function() {
|
|||||||
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
|
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
|
||||||
.contains(board).should('be.visible')
|
.contains(board).should('be.visible')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Shows and hides the navigation', () => {
|
||||||
|
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||||
|
.contains('Upcoming cards')
|
||||||
|
.should('be.visible')
|
||||||
|
cy.openLeftSidebar()
|
||||||
|
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||||
|
.contains('Upcoming cards')
|
||||||
|
.should('not.be.visible')
|
||||||
|
cy.openLeftSidebar()
|
||||||
|
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||||
|
.contains('Upcoming cards')
|
||||||
|
.should('be.visible')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,53 +1,47 @@
|
|||||||
import { randHash } from '../utils'
|
import { randUser } from '../utils/index.js'
|
||||||
const randUser = randHash()
|
import { sampleBoard } from '../utils/sampleBoard'
|
||||||
|
|
||||||
const testBoardData = {
|
const user = randUser()
|
||||||
title: 'MyBoardTest',
|
const boardData = sampleBoard()
|
||||||
color: '00ff00',
|
|
||||||
stacks: [
|
const auth = {
|
||||||
{
|
user: user.userId,
|
||||||
title: 'TestList',
|
password: user.password,
|
||||||
cards: [
|
}
|
||||||
{
|
|
||||||
title: 'Hello world',
|
const useModal = (useModal) => {
|
||||||
},
|
return cy.request({
|
||||||
],
|
method: 'POST',
|
||||||
},
|
url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/deck/api/v1.0/config/cardDetailsInModal?format=json`,
|
||||||
],
|
auth,
|
||||||
|
body: { value: useModal },
|
||||||
|
}).then((response) => {
|
||||||
|
expect(response.status).to.eq(200)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Card', function() {
|
describe('Card', function() {
|
||||||
|
let boardId
|
||||||
before(function() {
|
before(function() {
|
||||||
cy.nextcloudCreateUser(randUser, randUser)
|
cy.createUser(user)
|
||||||
|
cy.login(user)
|
||||||
cy.createExampleBoard({
|
cy.createExampleBoard({
|
||||||
user: randUser,
|
user,
|
||||||
password: randUser,
|
board: boardData,
|
||||||
board: testBoardData,
|
}).then((board) => {
|
||||||
|
boardId = board.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
cy.login(randUser, randUser)
|
cy.login(user)
|
||||||
})
|
|
||||||
|
|
||||||
it('Can show card details modal', function() {
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.getNavigationEntry(testBoardData.title)
|
|
||||||
.first().click({ force: true })
|
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).within(() => {
|
|
||||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get('.modal__card').should('be.visible')
|
|
||||||
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can add a card', function() {
|
it('Can add a card', function() {
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
const newCardTitle = 'Write some cypress tests'
|
const newCardTitle = 'Write some cypress tests'
|
||||||
|
|
||||||
cy.openLeftSidebar()
|
cy.getNavigationEntry(boardData.title)
|
||||||
cy.getNavigationEntry(testBoardData.title)
|
|
||||||
.first().click({ force: true })
|
.first().click({ force: true })
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).within(() => {
|
cy.get('.board .stack').eq(0).within(() => {
|
||||||
@@ -64,4 +58,72 @@ describe('Card', function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Modal', () => {
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.login(user)
|
||||||
|
useModal(true).then(() => {
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can show card details modal', function() {
|
||||||
|
cy.getNavigationEntry(boardData.title)
|
||||||
|
.first().click({ force: true })
|
||||||
|
|
||||||
|
cy.get('.board .stack').eq(0).within(() => {
|
||||||
|
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.modal__card').should('be.visible')
|
||||||
|
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Attachment from files app', () => {
|
||||||
|
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||||
|
cy.get('.modal__card').should('be.visible')
|
||||||
|
cy.get('.app-sidebar-tabs__tab [data-id="attachments"]').click()
|
||||||
|
cy.get('button.icon-upload').should('be.visible')
|
||||||
|
cy.get('button.icon-folder').should('be.visible')
|
||||||
|
.click()
|
||||||
|
cy.get('.oc-dialog #picker-filestable tr[data-entryname="welcome.txt"] td.filename').should('be.visible')
|
||||||
|
.click()
|
||||||
|
cy.get('.oc-dialog button.primary').click()
|
||||||
|
cy.get('.attachment-list .basename').contains('welcome.txt')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows the modal with the editor', () => {
|
||||||
|
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||||
|
cy.intercept({ method: 'PUT', url: '**/apps/deck/cards/*' }).as('save')
|
||||||
|
cy.get('.modal__card').should('be.visible')
|
||||||
|
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
||||||
|
cy.get('.modal__card .ProseMirror h1').contains('Hello world').should('be.visible')
|
||||||
|
cy.get('.modal__card .ProseMirror h1')
|
||||||
|
.click()
|
||||||
|
.type(' writing more text{enter}- List item{enter}with entries{enter}{enter}Paragraph')
|
||||||
|
cy.wait('@save', { timeout: 7000 })
|
||||||
|
|
||||||
|
cy.reload()
|
||||||
|
cy.get('.modal__card').should('be.visible')
|
||||||
|
cy.get('.modal__card .ProseMirror h1').contains('Hello world writing more text').should('be.visible')
|
||||||
|
cy.get('.modal__card .ProseMirror li').eq(0).contains('List item').should('be.visible')
|
||||||
|
cy.get('.modal__card .ProseMirror li').eq(1).contains('with entries').should('be.visible')
|
||||||
|
cy.get('.modal__card .ProseMirror p').contains('Paragraph').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Sidebar', () => {
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.login(user)
|
||||||
|
useModal(false).then(() => {
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Show the sidebar', () => {
|
||||||
|
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||||
|
cy.get('#app-sidebar-vue')
|
||||||
|
.find('.ProseMirror h1').contains('Hello world writing more text').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { randHash } from '../utils'
|
import { randUser } from '../utils/index.js'
|
||||||
const randUser = randHash()
|
const user = randUser()
|
||||||
|
|
||||||
describe('Deck dashboard', function() {
|
describe('Deck dashboard', function() {
|
||||||
const password = 'pass123'
|
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
cy.createUser(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
cy.login(randUser, password)
|
cy.login(user)
|
||||||
|
cy.visit('/apps/deck')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can show the right title on the dashboard', function() {
|
it('Can show the right title on the dashboard', function() {
|
||||||
@@ -21,7 +20,6 @@ describe('Deck dashboard', function() {
|
|||||||
it('Can see the default "Personal Board" created for user by default', function() {
|
it('Can see the default "Personal Board" created for user by default', function() {
|
||||||
const defaultBoard = 'Personal'
|
const defaultBoard = 'Personal'
|
||||||
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
||||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
|
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
|
||||||
.first()
|
.first()
|
||||||
|
|||||||
50
cypress/e2e/sharingFeatures.js
Normal file
50
cypress/e2e/sharingFeatures.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { randUser } from '../utils/index.js'
|
||||||
|
import { sampleBoard } from '../utils/sampleBoard'
|
||||||
|
const user = randUser()
|
||||||
|
const recipient = randUser()
|
||||||
|
|
||||||
|
describe('Board', function() {
|
||||||
|
before(function() {
|
||||||
|
cy.createUser(user)
|
||||||
|
cy.createUser(recipient)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.login(user)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Share a board to a user', function() {
|
||||||
|
const board = sampleBoard('Read only board')
|
||||||
|
cy.createExampleBoard({ user, board }).then((board) => {
|
||||||
|
const boardId = board.id
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
cy.get('.board-title').contains(board.title)
|
||||||
|
|
||||||
|
cy.shareBoardWithUi(recipient.userId)
|
||||||
|
|
||||||
|
cy.login(recipient)
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
cy.get('.board-title').contains(board.title)
|
||||||
|
cy.get('.button-vue[aria-label*="Add card"]')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Share a board to a user as writable', function() {
|
||||||
|
const board = sampleBoard('Editable board')
|
||||||
|
cy.createExampleBoard({ user, board }).then((board) => {
|
||||||
|
const boardId = board.id
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
cy.get('.board-title').contains(board.title)
|
||||||
|
|
||||||
|
cy.shareBoardWithUi(recipient.userId)
|
||||||
|
cy.get(`[data-cy="acl-participant:${recipient.userId}"]`).find('[data-cy="action:permission-edit"]').click()
|
||||||
|
|
||||||
|
cy.login(recipient)
|
||||||
|
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||||
|
cy.get('.board-title').contains(board.title)
|
||||||
|
cy.get('.button-vue[aria-label*="Add card"]')
|
||||||
|
.first().click()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,30 +1,68 @@
|
|||||||
import { randHash } from '../utils'
|
import { randUser } from '../utils/index.js'
|
||||||
const randUser = randHash()
|
const user = randUser()
|
||||||
|
|
||||||
|
const boardTitle = 'TestBoard'
|
||||||
|
const testBoardData = {
|
||||||
|
title: boardTitle,
|
||||||
|
stacks: [
|
||||||
|
{ title: 'Existing Stack1' },
|
||||||
|
{ title: 'Existing Stack2' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
describe('Stack', function() {
|
describe('Stack', function() {
|
||||||
const board = 'TestBoard'
|
|
||||||
const password = 'pass123'
|
|
||||||
const stack = 'List 1'
|
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
cy.createUser(user)
|
||||||
cy.deckCreateBoard({ user: randUser, password }, board)
|
cy.login(user)
|
||||||
|
cy.createExampleBoard({
|
||||||
|
user,
|
||||||
|
board: testBoardData,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
cy.logout()
|
cy.login(user)
|
||||||
cy.login(randUser, password)
|
cy.visit('/apps/deck')
|
||||||
|
|
||||||
|
cy.openLeftSidebar()
|
||||||
|
cy.getNavigationEntry(boardTitle)
|
||||||
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can create a stack', function() {
|
it('Can create a stack', function() {
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.getNavigationEntry(board)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get('#stack-add button').first().click()
|
cy.get('#stack-add button').first().click()
|
||||||
cy.get('#stack-add form input#new-stack-input-main').type(stack)
|
cy.focused().type('List 1')
|
||||||
cy.get('#stack-add form input[type=submit]').first().click()
|
cy.get('#stack-add form input[type=submit]').first().click()
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).contains(stack).should('be.visible')
|
cy.contains('List 1').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can edit a stack title', function() {
|
||||||
|
cy.contains('Existing Stack1')
|
||||||
|
cy.get('[data-cy-stack="Existing Stack1"]').within(() => {
|
||||||
|
cy.contains('Existing Stack1').click()
|
||||||
|
cy.focused().type(' renamed')
|
||||||
|
cy.get('[data-cy="editStackTitleForm"] input[type="submit"]').click()
|
||||||
|
})
|
||||||
|
cy.contains('Existing Stack1 renamed').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can abort a stack title edit via esc', function() {
|
||||||
|
cy.contains('Existing Stack2').click()
|
||||||
|
cy.focused().type(' with a new title, maybe?')
|
||||||
|
cy.focused().type('{esc}')
|
||||||
|
|
||||||
|
cy.contains('Existing Stack2').should('be.visible')
|
||||||
|
cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can abort a stack title edit via click outside', function() {
|
||||||
|
cy.contains('Existing Stack2').click()
|
||||||
|
cy.focused().type(' with a new title, maybe?')
|
||||||
|
cy.get('[data-cy-stack="Existing Stack2"]').click('bottom')
|
||||||
|
|
||||||
|
cy.contains('Existing Stack2').should('be.visible')
|
||||||
|
cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,61 +20,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { addCommands } from '@nextcloud/cypress'
|
||||||
|
|
||||||
|
addCommands()
|
||||||
|
|
||||||
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
||||||
Cypress.env('baseUrl', url)
|
Cypress.env('baseUrl', url)
|
||||||
|
|
||||||
Cypress.Commands.add('login', (user, password, route = '/apps/deck/') => {
|
|
||||||
const session = `${user}-${Date.now()}`
|
|
||||||
cy.session(session, function() {
|
|
||||||
cy.visit(route)
|
|
||||||
cy.get('input[name=user]').type(user)
|
|
||||||
cy.get('input[name=password]').type(password)
|
|
||||||
cy.get('form[name=login] [type=submit]').click()
|
|
||||||
cy.url().should('include', route)
|
|
||||||
})
|
|
||||||
cy.visit(route)
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('logout', (route = '/') => {
|
|
||||||
cy.session('_guest', function() {})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
|
|
||||||
cy.clearCookies()
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
|
|
||||||
form: true,
|
|
||||||
body: {
|
|
||||||
userid: user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
auth: { user: 'admin', pass: 'admin' },
|
|
||||||
headers: {
|
|
||||||
'OCS-ApiRequest': 'true',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
}).then((response) => {
|
|
||||||
cy.log(`Created user ${user}`, response.status)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('nextcloudUpdateUser', (user, password, key, value) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'PUT',
|
|
||||||
url: `${Cypress.env('baseUrl')}/ocs/v2.php/cloud/users/${user}`,
|
|
||||||
form: true,
|
|
||||||
body: { key, value },
|
|
||||||
auth: { user, pass: password },
|
|
||||||
headers: {
|
|
||||||
'OCS-ApiRequest': 'true',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
}).then((response) => {
|
|
||||||
cy.log(`Updated user ${user} ${key} to ${value}`, response.status)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('openLeftSidebar', () => {
|
Cypress.Commands.add('openLeftSidebar', () => {
|
||||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
cy.get('.app-navigation button.app-navigation-toggle').click()
|
||||||
})
|
})
|
||||||
@@ -111,14 +63,15 @@ Cypress.Commands.add('deckCreateList', ({ user, password }, title) => {
|
|||||||
cy.get('#stack-add form input[type=submit]').first().click()
|
cy.get('#stack-add form input[type=submit]').first().click()
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('createExampleBoard', ({ user, password, board }) => {
|
Cypress.Commands.add('createExampleBoard', ({ user, board }) => {
|
||||||
|
const auth = {
|
||||||
|
user: user.userId,
|
||||||
|
password: user.password,
|
||||||
|
}
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards`,
|
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards`,
|
||||||
auth: {
|
auth,
|
||||||
user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: board.title, color: board.color ?? 'ff0000' },
|
body: { title: board.title, color: board.color ?? 'ff0000' },
|
||||||
}).then((boardResponse) => {
|
}).then((boardResponse) => {
|
||||||
expect(boardResponse.status).to.eq(200)
|
expect(boardResponse.status).to.eq(200)
|
||||||
@@ -128,10 +81,7 @@ Cypress.Commands.add('createExampleBoard', ({ user, password, board }) => {
|
|||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks`,
|
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks`,
|
||||||
auth: {
|
auth,
|
||||||
user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: stack.title, order: 0 },
|
body: { title: stack.title, order: 0 },
|
||||||
}).then((stackResponse) => {
|
}).then((stackResponse) => {
|
||||||
const stackData = stackResponse.body
|
const stackData = stackResponse.body
|
||||||
@@ -140,15 +90,13 @@ Cypress.Commands.add('createExampleBoard', ({ user, password, board }) => {
|
|||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks/${stackData.id}/cards`,
|
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks/${stackData.id}/cards`,
|
||||||
auth: {
|
auth,
|
||||||
user,
|
body: { title: card.title, description: card.description ?? '' },
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: card.title },
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
cy.wrap(boardData)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -157,3 +105,13 @@ Cypress.Commands.add('getNavigationEntry', (boardTitle) => {
|
|||||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
|
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
|
||||||
.find('a.app-navigation-entry-link')
|
.find('a.app-navigation-entry-link')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('shareBoardWithUi', (userId) => {
|
||||||
|
cy.get('[aria-label="Open details"]').click()
|
||||||
|
cy.get('.app-sidebar').should('be.visible')
|
||||||
|
cy.get('.multiselect__input').type(`${userId}`)
|
||||||
|
cy.get('.multiselect__content .multiselect__element').first().contains(userId)
|
||||||
|
cy.get('.multiselect__input').type('{enter}')
|
||||||
|
|
||||||
|
cy.get('.shareWithList').contains(userId)
|
||||||
|
})
|
||||||
|
|||||||
12
cypress/support/component-index.html
Normal file
12
cypress/support/component-index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Components App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-cy-root></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
cypress/support/component.js
Normal file
27
cypress/support/component.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/component.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
|
|
||||||
|
import { mount } from 'cypress/vue2'
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount)
|
||||||
|
|
||||||
|
// Example use:
|
||||||
|
// cy.mount(MyComponent)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
// Import commands.js using ES2015 syntax:
|
||||||
import './commands'
|
import './commands.js'
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
// Alternatively you can use CommonJS syntax:
|
||||||
// require('./commands')
|
// require('./commands')
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
|
import { User } from '@nextcloud/cypress'
|
||||||
|
|
||||||
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
||||||
|
export const randUser = () => new User(randHash(), randHash())
|
||||||
|
|||||||
38
cypress/utils/sampleBoard.js
Normal file
38
cypress/utils/sampleBoard.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const sampleBoard = (title = 'MyTestBoard') => {
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
color: '00ff00',
|
||||||
|
stacks: [
|
||||||
|
{
|
||||||
|
title: 'TestList',
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
title: 'Hello world',
|
||||||
|
description: '# Hello world',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
110
docs/API.md
110
docs/API.md
@@ -1066,6 +1066,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
|
| 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) |
|
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
|
||||||
|
| cardIdBadge | Determines if the ID badges are displayed on cards (boolean) |
|
||||||
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1079,6 +1080,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
|||||||
"data": {
|
"data": {
|
||||||
"calendar": true,
|
"calendar": true,
|
||||||
"cardDetailsInModal": true,
|
"cardDetailsInModal": true,
|
||||||
|
"cardIdBadge": true,
|
||||||
"groupLimit": [
|
"groupLimit": [
|
||||||
{
|
{
|
||||||
"id": "admin",
|
"id": "admin",
|
||||||
@@ -1109,6 +1111,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
|||||||
| notify-due | `off`, `assigned` or `all` |
|
| notify-due | `off`, `assigned` or `all` |
|
||||||
| calendar | Boolean |
|
| calendar | Boolean |
|
||||||
| cardDetailsInModal | Boolean |
|
| cardDetailsInModal | Boolean |
|
||||||
|
| cardIdBadge | Boolean |
|
||||||
|
|
||||||
#### Example request
|
#### Example request
|
||||||
|
|
||||||
@@ -1391,3 +1394,110 @@ A bad request response is returned if invalid input values are provided. The res
|
|||||||
A not found response might be returned if:
|
A not found response might be returned if:
|
||||||
- The card for the given cardId could not be found
|
- The card for the given cardId could not be found
|
||||||
- The comment could not be found
|
- The comment could not be found
|
||||||
|
|
||||||
|
|
||||||
|
## Sessions
|
||||||
|
|
||||||
|
### PUT /session/create - creates a new session
|
||||||
|
|
||||||
|
#### Request parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ------- | ---------------------------------------------------- |
|
||||||
|
| boardId | Integer | The id of the opened board |
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/create' \
|
||||||
|
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||||
|
-H 'Content-Type: application/json;charset=utf-8' \
|
||||||
|
--data '{"boardId":1}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
##### 200 Success
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ocs": {
|
||||||
|
"meta": {
|
||||||
|
"status": "ok",
|
||||||
|
"statuscode": 200,
|
||||||
|
"message": "OK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"token": "+zcJHf4rC6dobVSbuNa3delkCSfTW8OvYWTyLFvSpIv80FjtgLIj0ARlxspsazNQ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### POST /session/sync - notifies the server, that the session is still open
|
||||||
|
|
||||||
|
#### Request body
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ------- | ---------------------------------------------------- |
|
||||||
|
| boardId | Integer | The id of the opened board |
|
||||||
|
| token | String | The session token from the /sessions/create response |
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/create' \
|
||||||
|
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||||
|
-H 'Content-Type: application/json;charset=utf-8' \
|
||||||
|
--data '{"boardId":1, "token":"X3DyyoFslArF0t0NBZXzZXzcy8feoX/OEytSNXZtPg9TpUgO5wrkJ38IW3T/FfpV"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
##### 200 Success
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ocs": {
|
||||||
|
"meta": {
|
||||||
|
"status": "ok",
|
||||||
|
"statuscode": 200,
|
||||||
|
"message": "OK"
|
||||||
|
},
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 404 Not Found
|
||||||
|
the provided token is invalid or expired
|
||||||
|
|
||||||
|
|
||||||
|
### POST /session/close - closes the session
|
||||||
|
|
||||||
|
#### Request body
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ------- | ---------------------------------------------------- |
|
||||||
|
| boardId | Integer | The id of the opened board |
|
||||||
|
| token | String | The session token from the /sessions/create response |
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/close' \
|
||||||
|
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||||
|
-H 'Content-Type: application/json;charset=utf-8' \
|
||||||
|
--data '{"boardId":1, "token":"X3DyyoFslArF0t0NBZXzZXzcy8feoX/OEytSNXZtPg9TpUgO5wrkJ38IW3T/FfpV"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
##### 200 Success
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ocs": {
|
||||||
|
"meta": {
|
||||||
|
"status": "ok",
|
||||||
|
"statuscode": 200,
|
||||||
|
"message": "OK"
|
||||||
|
},
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Steps:
|
|||||||
* Create the configuration file
|
* Create the configuration file
|
||||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||||
|
|
||||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||||
|
|
||||||
Example configuration file:
|
Example configuration file:
|
||||||
```json
|
```json
|
||||||
@@ -120,7 +120,7 @@ https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&field
|
|||||||
This ID you will use in the configuration file in the `board` property
|
This ID you will use in the configuration file in the `board` property
|
||||||
* Create the configuration file
|
* Create the configuration file
|
||||||
|
|
||||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||||
|
|
||||||
Example configuration file:
|
Example configuration file:
|
||||||
```json
|
```json
|
||||||
|
|||||||
3
img/card.svg
Normal file
3
img/card.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="height: 240px; width: 240px;" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12,15H10V13H12V15M18,15H14V13H18V15M8,11H6V9H8V11M18,11H10V9H18V11M20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M4,6V18H20V6H4Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 298 B |
50
l10n/ast.js
50
l10n/ast.js
@@ -1,50 +0,0 @@
|
|||||||
OC.L10N.register(
|
|
||||||
"deck",
|
|
||||||
{
|
|
||||||
"Deck" : "Deck",
|
|
||||||
"Personal" : "Personal",
|
|
||||||
"%s on %s" : "%s en %s",
|
|
||||||
"Finished" : "Finó",
|
|
||||||
"Action needed" : "Precísase aición",
|
|
||||||
"Later" : "Más sero",
|
|
||||||
"Done" : "Fecho",
|
|
||||||
"The file was uploaded" : "Xubióse'l ficheru",
|
|
||||||
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "El ficheru xubíu perpasa la direutiva de xuba upload_max_filesize en php.ini",
|
|
||||||
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "El ficheru xubíu perpasa la direutiva \"MAX_FILE_SIZE\" especificada nel formulariu HTML",
|
|
||||||
"No file was uploaded" : "Nun se xubieron fichjeros",
|
|
||||||
"Missing a temporary folder" : "Falta un direutoriu temporal",
|
|
||||||
"Could not write file to disk" : "Nun pudo escribise nel discu'l ficheru",
|
|
||||||
"A PHP extension stopped the file upload" : "Una estensión de PHP paró la xuba de ficheros",
|
|
||||||
"Invalid date, date format must be YYYY-MM-DD" : "Data non válida, el formatu ha ser AAAA-MM-DD",
|
|
||||||
"Cancel" : "Encaboxar",
|
|
||||||
"Close" : "Zarrar",
|
|
||||||
"File already exists" : "Yá esiste'l ficheru",
|
|
||||||
"Show archived cards" : "Amosar tarxetes archivaes",
|
|
||||||
"Details" : "Detalles",
|
|
||||||
"Sharing" : "Compartiendo",
|
|
||||||
"Tags" : "Etiquetes",
|
|
||||||
"Undo" : "Desfacer",
|
|
||||||
"Can edit" : "Can edit",
|
|
||||||
"Can share" : "Can share",
|
|
||||||
"Owner" : "Owner",
|
|
||||||
"Delete" : "Desaniciar",
|
|
||||||
"Edit" : "Editar",
|
|
||||||
"Members" : "Miembros",
|
|
||||||
"Download" : "Baxar",
|
|
||||||
"Attachments" : "Axuntos",
|
|
||||||
"Comments" : "Comentarios",
|
|
||||||
"Modified" : "Modificóse'l",
|
|
||||||
"Created" : "Creóse",
|
|
||||||
"Today" : "Güei",
|
|
||||||
"Tomorrow" : "Mañana",
|
|
||||||
"Save" : "Guardar",
|
|
||||||
"Reply" : "Rempuesta",
|
|
||||||
"Update" : "Anovar",
|
|
||||||
"Description" : "Descripción",
|
|
||||||
"(group)" : "(grupu)",
|
|
||||||
"seconds ago" : "hai segundos",
|
|
||||||
"Shared with you" : "Shared with you",
|
|
||||||
"No notifications" : "Ensin avisos",
|
|
||||||
"Share" : "Share"
|
|
||||||
},
|
|
||||||
"nplurals=2; plural=(n != 1);");
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{ "translations": {
|
|
||||||
"Deck" : "Deck",
|
|
||||||
"Personal" : "Personal",
|
|
||||||
"%s on %s" : "%s en %s",
|
|
||||||
"Finished" : "Finó",
|
|
||||||
"Action needed" : "Precísase aición",
|
|
||||||
"Later" : "Más sero",
|
|
||||||
"Done" : "Fecho",
|
|
||||||
"The file was uploaded" : "Xubióse'l ficheru",
|
|
||||||
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "El ficheru xubíu perpasa la direutiva de xuba upload_max_filesize en php.ini",
|
|
||||||
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "El ficheru xubíu perpasa la direutiva \"MAX_FILE_SIZE\" especificada nel formulariu HTML",
|
|
||||||
"No file was uploaded" : "Nun se xubieron fichjeros",
|
|
||||||
"Missing a temporary folder" : "Falta un direutoriu temporal",
|
|
||||||
"Could not write file to disk" : "Nun pudo escribise nel discu'l ficheru",
|
|
||||||
"A PHP extension stopped the file upload" : "Una estensión de PHP paró la xuba de ficheros",
|
|
||||||
"Invalid date, date format must be YYYY-MM-DD" : "Data non válida, el formatu ha ser AAAA-MM-DD",
|
|
||||||
"Cancel" : "Encaboxar",
|
|
||||||
"Close" : "Zarrar",
|
|
||||||
"File already exists" : "Yá esiste'l ficheru",
|
|
||||||
"Show archived cards" : "Amosar tarxetes archivaes",
|
|
||||||
"Details" : "Detalles",
|
|
||||||
"Sharing" : "Compartiendo",
|
|
||||||
"Tags" : "Etiquetes",
|
|
||||||
"Undo" : "Desfacer",
|
|
||||||
"Can edit" : "Can edit",
|
|
||||||
"Can share" : "Can share",
|
|
||||||
"Owner" : "Owner",
|
|
||||||
"Delete" : "Desaniciar",
|
|
||||||
"Edit" : "Editar",
|
|
||||||
"Members" : "Miembros",
|
|
||||||
"Download" : "Baxar",
|
|
||||||
"Attachments" : "Axuntos",
|
|
||||||
"Comments" : "Comentarios",
|
|
||||||
"Modified" : "Modificóse'l",
|
|
||||||
"Created" : "Creóse",
|
|
||||||
"Today" : "Güei",
|
|
||||||
"Tomorrow" : "Mañana",
|
|
||||||
"Save" : "Guardar",
|
|
||||||
"Reply" : "Rempuesta",
|
|
||||||
"Update" : "Anovar",
|
|
||||||
"Description" : "Descripción",
|
|
||||||
"(group)" : "(grupu)",
|
|
||||||
"seconds ago" : "hai segundos",
|
|
||||||
"Shared with you" : "Shared with you",
|
|
||||||
"No notifications" : "Ensin avisos",
|
|
||||||
"Share" : "Share"
|
|
||||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
|
||||||
}
|
|
||||||
@@ -81,6 +81,7 @@ OC.L10N.register(
|
|||||||
"Deck board" : "Доска",
|
"Deck board" : "Доска",
|
||||||
"Owned by %1$s" : "Владелец: %1$s",
|
"Owned by %1$s" : "Владелец: %1$s",
|
||||||
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
|
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
|
||||||
|
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Из %1$s, в %2$s/%3$s, принадлежит %4$s",
|
||||||
"Card comments" : "Комментарии карточки",
|
"Card comments" : "Комментарии карточки",
|
||||||
"%s on %s" : "%s на %s",
|
"%s on %s" : "%s на %s",
|
||||||
"Deck boards and cards" : "Доски и карточки",
|
"Deck boards and cards" : "Доски и карточки",
|
||||||
@@ -156,6 +157,7 @@ OC.L10N.register(
|
|||||||
"Toggle compact mode" : "Выбор компактного или обычного режима просмотра",
|
"Toggle compact mode" : "Выбор компактного или обычного режима просмотра",
|
||||||
"Open details" : "Открыть подробности",
|
"Open details" : "Открыть подробности",
|
||||||
"Details" : "Свойства",
|
"Details" : "Свойства",
|
||||||
|
"Currently present people" : "Присутствующие в настоящее время люди",
|
||||||
"Loading board" : "Загрузка доски",
|
"Loading board" : "Загрузка доски",
|
||||||
"No lists available" : "Нет ни одного списка",
|
"No lists available" : "Нет ни одного списка",
|
||||||
"Create a new list to add cards to this board" : "Создайте список чтобы добавить карточки на эту доску",
|
"Create a new list to add cards to this board" : "Создайте список чтобы добавить карточки на эту доску",
|
||||||
@@ -296,10 +298,12 @@ OC.L10N.register(
|
|||||||
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
|
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
|
||||||
"{stack} in {board}" : "«{stack}» с доски «{board}»",
|
"{stack} in {board}" : "«{stack}» с доски «{board}»",
|
||||||
"Click to expand description" : "Нажмите, чтобы развернуть поле описания",
|
"Click to expand description" : "Нажмите, чтобы развернуть поле описания",
|
||||||
|
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Создано {created}\n* Последнее изменение {lastMod}\n* {nbAttachments} вложений\n* {nbComments} комментариев",
|
||||||
"{nbCards} cards" : "карточек: {nbCards}",
|
"{nbCards} cards" : "карточек: {nbCards}",
|
||||||
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
|
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
|
||||||
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
|
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
|
||||||
"upcoming cards" : "карточки, ожидающие выполнения",
|
"upcoming cards" : "карточки, ожидающие выполнения",
|
||||||
|
"New card" : "Новая карточка",
|
||||||
"Due on {date}" : "Дата исполнения: {date}",
|
"Due on {date}" : "Дата исполнения: {date}",
|
||||||
"Link to a board" : "Ссылка на доску",
|
"Link to a board" : "Ссылка на доску",
|
||||||
"Link to a card" : "Ссылка на карточку",
|
"Link to a card" : "Ссылка на карточку",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
"Deck board" : "Доска",
|
"Deck board" : "Доска",
|
||||||
"Owned by %1$s" : "Владелец: %1$s",
|
"Owned by %1$s" : "Владелец: %1$s",
|
||||||
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
|
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
|
||||||
|
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Из %1$s, в %2$s/%3$s, принадлежит %4$s",
|
||||||
"Card comments" : "Комментарии карточки",
|
"Card comments" : "Комментарии карточки",
|
||||||
"%s on %s" : "%s на %s",
|
"%s on %s" : "%s на %s",
|
||||||
"Deck boards and cards" : "Доски и карточки",
|
"Deck boards and cards" : "Доски и карточки",
|
||||||
@@ -154,6 +155,7 @@
|
|||||||
"Toggle compact mode" : "Выбор компактного или обычного режима просмотра",
|
"Toggle compact mode" : "Выбор компактного или обычного режима просмотра",
|
||||||
"Open details" : "Открыть подробности",
|
"Open details" : "Открыть подробности",
|
||||||
"Details" : "Свойства",
|
"Details" : "Свойства",
|
||||||
|
"Currently present people" : "Присутствующие в настоящее время люди",
|
||||||
"Loading board" : "Загрузка доски",
|
"Loading board" : "Загрузка доски",
|
||||||
"No lists available" : "Нет ни одного списка",
|
"No lists available" : "Нет ни одного списка",
|
||||||
"Create a new list to add cards to this board" : "Создайте список чтобы добавить карточки на эту доску",
|
"Create a new list to add cards to this board" : "Создайте список чтобы добавить карточки на эту доску",
|
||||||
@@ -294,10 +296,12 @@
|
|||||||
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
|
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
|
||||||
"{stack} in {board}" : "«{stack}» с доски «{board}»",
|
"{stack} in {board}" : "«{stack}» с доски «{board}»",
|
||||||
"Click to expand description" : "Нажмите, чтобы развернуть поле описания",
|
"Click to expand description" : "Нажмите, чтобы развернуть поле описания",
|
||||||
|
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Создано {created}\n* Последнее изменение {lastMod}\n* {nbAttachments} вложений\n* {nbComments} комментариев",
|
||||||
"{nbCards} cards" : "карточек: {nbCards}",
|
"{nbCards} cards" : "карточек: {nbCards}",
|
||||||
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
|
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
|
||||||
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
|
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
|
||||||
"upcoming cards" : "карточки, ожидающие выполнения",
|
"upcoming cards" : "карточки, ожидающие выполнения",
|
||||||
|
"New card" : "Новая карточка",
|
||||||
"Due on {date}" : "Дата исполнения: {date}",
|
"Due on {date}" : "Дата исполнения: {date}",
|
||||||
"Link to a board" : "Ссылка на доску",
|
"Link to a board" : "Ссылка на доску",
|
||||||
"Link to a card" : "Ссылка на карточку",
|
"Link to a card" : "Ссылка на карточку",
|
||||||
|
|||||||
14
l10n/sv.js
14
l10n/sv.js
@@ -78,7 +78,7 @@ OC.L10N.register(
|
|||||||
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
||||||
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
||||||
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
||||||
"Deck board" : "Deck-plank",
|
"Deck board" : "Deck-tavla",
|
||||||
"Owned by %1$s" : "Ägd av %1$s",
|
"Owned by %1$s" : "Ägd av %1$s",
|
||||||
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
||||||
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
||||||
@@ -92,7 +92,7 @@ OC.L10N.register(
|
|||||||
"Later" : "Senare",
|
"Later" : "Senare",
|
||||||
"copy" : "kopiera",
|
"copy" : "kopiera",
|
||||||
"To do" : "Att göra",
|
"To do" : "Att göra",
|
||||||
"Doing" : "Gör",
|
"Doing" : "Pågående",
|
||||||
"Done" : "Klart",
|
"Done" : "Klart",
|
||||||
"Example Task 3" : "Exempeluppgift 3",
|
"Example Task 3" : "Exempeluppgift 3",
|
||||||
"Example Task 2" : "Exempeluppgift 2",
|
"Example Task 2" : "Exempeluppgift 2",
|
||||||
@@ -249,7 +249,7 @@ OC.L10N.register(
|
|||||||
"Write a description …" : "Ange en beskrivning ...",
|
"Write a description …" : "Ange en beskrivning ...",
|
||||||
"Choose attachment" : "Välj bilaga",
|
"Choose attachment" : "Välj bilaga",
|
||||||
"(group)" : " (grupp)",
|
"(group)" : " (grupp)",
|
||||||
"Todo items" : "Todo saker",
|
"Todo items" : "Att göra saker",
|
||||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||||
"Edit card title" : "Ändra korttitel",
|
"Edit card title" : "Ändra korttitel",
|
||||||
"Assign to me" : "Tilldela till mig",
|
"Assign to me" : "Tilldela till mig",
|
||||||
@@ -289,7 +289,7 @@ OC.L10N.register(
|
|||||||
"Only assigned cards" : "Bara tilldelade kort",
|
"Only assigned cards" : "Bara tilldelade kort",
|
||||||
"No reminder" : "Ingen påminnelse",
|
"No reminder" : "Ingen påminnelse",
|
||||||
"An error occurred" : "Ett fel uppstod",
|
"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.",
|
"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 tavla {title}? Detta kommer radera all data som tillhör tavlan inklusive arkiverade kort.",
|
||||||
"Delete the board?" : "Ta bort tavlan?",
|
"Delete the board?" : "Ta bort tavlan?",
|
||||||
"Loading filtered view" : "Laddar filtrerad vy",
|
"Loading filtered view" : "Laddar filtrerad vy",
|
||||||
"No due" : "Inget slut",
|
"No due" : "Inget slut",
|
||||||
@@ -316,9 +316,9 @@ OC.L10N.register(
|
|||||||
"Share with a Deck card" : "Dela med ett Deck-kort",
|
"Share with a Deck card" : "Dela med ett Deck-kort",
|
||||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||||
"Share" : "Dela",
|
"Share" : "Dela",
|
||||||
"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}?",
|
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra tavla {title} för {user}?",
|
||||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
"Transfer the board for {user} successfully" : "Överförde tavlan för {user}",
|
||||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
"Failed to transfer the board for {user}" : "Misslyckades med att överföra tavlan för {user}",
|
||||||
"Add a new list" : "Lägg till en ny lista",
|
"Add a new list" : "Lägg till en ny lista",
|
||||||
"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."
|
"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."
|
||||||
},
|
},
|
||||||
|
|||||||
14
l10n/sv.json
14
l10n/sv.json
@@ -76,7 +76,7 @@
|
|||||||
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
||||||
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
||||||
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
||||||
"Deck board" : "Deck-plank",
|
"Deck board" : "Deck-tavla",
|
||||||
"Owned by %1$s" : "Ägd av %1$s",
|
"Owned by %1$s" : "Ägd av %1$s",
|
||||||
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
||||||
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"Later" : "Senare",
|
"Later" : "Senare",
|
||||||
"copy" : "kopiera",
|
"copy" : "kopiera",
|
||||||
"To do" : "Att göra",
|
"To do" : "Att göra",
|
||||||
"Doing" : "Gör",
|
"Doing" : "Pågående",
|
||||||
"Done" : "Klart",
|
"Done" : "Klart",
|
||||||
"Example Task 3" : "Exempeluppgift 3",
|
"Example Task 3" : "Exempeluppgift 3",
|
||||||
"Example Task 2" : "Exempeluppgift 2",
|
"Example Task 2" : "Exempeluppgift 2",
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
"Write a description …" : "Ange en beskrivning ...",
|
"Write a description …" : "Ange en beskrivning ...",
|
||||||
"Choose attachment" : "Välj bilaga",
|
"Choose attachment" : "Välj bilaga",
|
||||||
"(group)" : " (grupp)",
|
"(group)" : " (grupp)",
|
||||||
"Todo items" : "Todo saker",
|
"Todo items" : "Att göra saker",
|
||||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||||
"Edit card title" : "Ändra korttitel",
|
"Edit card title" : "Ändra korttitel",
|
||||||
"Assign to me" : "Tilldela till mig",
|
"Assign to me" : "Tilldela till mig",
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
"Only assigned cards" : "Bara tilldelade kort",
|
"Only assigned cards" : "Bara tilldelade kort",
|
||||||
"No reminder" : "Ingen påminnelse",
|
"No reminder" : "Ingen påminnelse",
|
||||||
"An error occurred" : "Ett fel uppstod",
|
"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.",
|
"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 tavla {title}? Detta kommer radera all data som tillhör tavlan inklusive arkiverade kort.",
|
||||||
"Delete the board?" : "Ta bort tavlan?",
|
"Delete the board?" : "Ta bort tavlan?",
|
||||||
"Loading filtered view" : "Laddar filtrerad vy",
|
"Loading filtered view" : "Laddar filtrerad vy",
|
||||||
"No due" : "Inget slut",
|
"No due" : "Inget slut",
|
||||||
@@ -314,9 +314,9 @@
|
|||||||
"Share with a Deck card" : "Dela med ett Deck-kort",
|
"Share with a Deck card" : "Dela med ett Deck-kort",
|
||||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||||
"Share" : "Dela",
|
"Share" : "Dela",
|
||||||
"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}?",
|
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra tavla {title} för {user}?",
|
||||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
"Transfer the board for {user} successfully" : "Överförde tavlan för {user}",
|
||||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
"Failed to transfer the board for {user}" : "Misslyckades med att överföra tavlan för {user}",
|
||||||
"Add a new list" : "Lägg till en ny lista",
|
"Add a new list" : "Lägg till en ny lista",
|
||||||
"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."
|
"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."
|
||||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||||
|
|||||||
@@ -325,7 +325,6 @@ class ActivityManager {
|
|||||||
*/
|
*/
|
||||||
$eventType = 'deck';
|
$eventType = 'deck';
|
||||||
$subjectParams = [];
|
$subjectParams = [];
|
||||||
$message = null;
|
|
||||||
switch ($subject) {
|
switch ($subject) {
|
||||||
// No need to enhance parameters since entity already contains the required data
|
// No need to enhance parameters since entity already contains the required data
|
||||||
case self::SUBJECT_BOARD_CREATE:
|
case self::SUBJECT_BOARD_CREATE:
|
||||||
@@ -434,10 +433,6 @@ class ActivityManager {
|
|||||||
->setSubject($subject, $subjectParams)
|
->setSubject($subject, $subjectParams)
|
||||||
->setTimestamp(time());
|
->setTimestamp(time());
|
||||||
|
|
||||||
if ($message !== null) {
|
|
||||||
$event->setMessage($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: We currently require activities for comments even if they are disabled though settings
|
// FIXME: We currently require activities for comments even if they are disabled though settings
|
||||||
// Get rid of this once the frontend fetches comments/activity individually
|
// Get rid of this once the frontend fetches comments/activity individually
|
||||||
if ($eventType === 'deck_comment') {
|
if ($eventType === 'deck_comment') {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class DeckProvider implements IProvider {
|
|||||||
* @throws \InvalidArgumentException Should be thrown if your provider does not know this event
|
* @throws \InvalidArgumentException Should be thrown if your provider does not know this event
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
|
public function parse($language, IEvent $event, IEvent $previousEvent = null): IEvent {
|
||||||
if ($event->getApp() !== 'deck') {
|
if ($event->getApp() !== 'deck') {
|
||||||
throw new \InvalidArgumentException();
|
throw new \InvalidArgumentException();
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ class DeckProvider implements IProvider {
|
|||||||
if (array_key_exists('comment', $subjectParams)) {
|
if (array_key_exists('comment', $subjectParams)) {
|
||||||
/** @var IComment $comment */
|
/** @var IComment $comment */
|
||||||
try {
|
try {
|
||||||
$comment = $this->commentsManager->get((int)$subjectParams['comment']);
|
$comment = $this->commentsManager->get($subjectParams['comment']);
|
||||||
$event->setParsedMessage($comment->getMessage());
|
$event->setParsedMessage($comment->getMessage());
|
||||||
$params['comment'] = [
|
$params['comment'] = [
|
||||||
'type' => 'highlight',
|
'type' => 'highlight',
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class DescriptionSetting extends Setting {
|
|||||||
* @return string Lowercase a-z and underscore only identifier
|
* @return string Lowercase a-z and underscore only identifier
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getIdentifier() {
|
public function getIdentifier(): string {
|
||||||
return 'deck_card_description';
|
return 'deck_card_description';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class DescriptionSetting extends Setting {
|
|||||||
* @return string A translated string
|
* @return string A translated string
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getName() {
|
public function getName(): string {
|
||||||
return $this->l->t('A <strong>card description</strong> inside the Deck app has been changed');
|
return $this->l->t('A <strong>card description</strong> inside the Deck app has been changed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* @return string Lowercase a-z and underscore only identifier
|
* @return string Lowercase a-z and underscore only identifier
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getIdentifier() {
|
public function getIdentifier(): string {
|
||||||
return 'deck';
|
return 'deck';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* @return string A translated string
|
* @return string A translated string
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getName() {
|
public function getName(): string {
|
||||||
return $this->l10n->t('Deck');
|
return $this->l10n->t('Deck');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* priority values. It is required to return a value between 0 and 100.
|
* priority values. It is required to return a value between 0 and 100.
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getPriority() {
|
public function getPriority(): int {
|
||||||
return 90;
|
return 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* @return string Full URL to an icon, empty string when none is given
|
* @return string Full URL to an icon, empty string when none is given
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getIcon() {
|
public function getIcon(): string {
|
||||||
return $this->urlGenerator->imagePath('deck', 'deck-dark.svg');
|
return $this->urlGenerator->imagePath('deck', 'deck-dark.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* @return string[] An array of allowed apps from which activities should be displayed
|
* @return string[] An array of allowed apps from which activities should be displayed
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function filterTypes(array $types) {
|
public function filterTypes(array $types): array {
|
||||||
return array_merge($types, ['deck_comment']);
|
return array_merge($types, ['deck_comment']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class Filter implements \OCP\Activity\IFilter {
|
|||||||
* @return string[] An array of allowed apps from which activities should be displayed
|
* @return string[] An array of allowed apps from which activities should be displayed
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function allowedApps() {
|
public function allowedApps(): array {
|
||||||
return ['deck'];
|
return ['deck'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return string Lowercase a-z and underscore only identifier
|
* @return string Lowercase a-z and underscore only identifier
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getIdentifier() {
|
public function getIdentifier(): string {
|
||||||
return 'deck';
|
return 'deck';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return string A translated string
|
* @return string A translated string
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getName() {
|
public function getName(): string {
|
||||||
return $this->l->t('Changes in the <strong>Deck app</strong>');
|
return $this->l->t('Changes in the <strong>Deck app</strong>');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* priority values. It is required to return a value between 0 and 100.
|
* priority values. It is required to return a value between 0 and 100.
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getPriority() {
|
public function getPriority(): int {
|
||||||
return 90;
|
return 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return bool True when the option can be changed for the stream
|
* @return bool True when the option can be changed for the stream
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function canChangeStream() {
|
public function canChangeStream(): bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return bool True when the option can be changed for the stream
|
* @return bool True when the option can be changed for the stream
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function isDefaultEnabledStream() {
|
public function isDefaultEnabledStream(): bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return bool True when the option can be changed for the mail
|
* @return bool True when the option can be changed for the mail
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function canChangeMail() {
|
public function canChangeMail(): bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class Setting implements \OCP\Activity\ISetting {
|
|||||||
* @return bool True when the option can be changed for the stream
|
* @return bool True when the option can be changed for the stream
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function isDefaultEnabledMail() {
|
public function isDefaultEnabledMail(): bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class SettingComment extends Setting {
|
|||||||
* @return string Lowercase a-z and underscore only identifier
|
* @return string Lowercase a-z and underscore only identifier
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getIdentifier() {
|
public function getIdentifier(): string {
|
||||||
return 'deck_comment';
|
return 'deck_comment';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class SettingComment extends Setting {
|
|||||||
* @return string A translated string
|
* @return string A translated string
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function getName() {
|
public function getName(): string {
|
||||||
return $this->l->t('A <strong>comment</strong> was created on a card');
|
return $this->l->t('A <strong>comment</strong> was created on a card');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class SettingComment extends Setting {
|
|||||||
* @return bool True when the option can be changed for the stream
|
* @return bool True when the option can be changed for the stream
|
||||||
* @since 11.0.0
|
* @since 11.0.0
|
||||||
*/
|
*/
|
||||||
public function canChangeStream() {
|
public function canChangeStream(): bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,29 +25,35 @@ namespace OCA\Deck\AppInfo;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Exception;
|
||||||
use OC\EventDispatcher\SymfonyAdapter;
|
use OCA\Circles\Events\CircleDestroyedEvent;
|
||||||
use OCA\Deck\Activity\CommentEventHandler;
|
use OCA\Deck\Activity\CommentEventHandler;
|
||||||
use OCA\Deck\Capabilities;
|
use OCA\Deck\Capabilities;
|
||||||
use OCA\Deck\Collaboration\Resources\ResourceProvider;
|
use OCA\Deck\Collaboration\Resources\ResourceProvider;
|
||||||
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
|
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
|
||||||
use OCA\Deck\Dashboard\DeckWidget;
|
use OCA\Deck\Dashboard\DeckWidget;
|
||||||
use OCA\Deck\Db\Acl;
|
use OCA\Deck\Db\Acl;
|
||||||
use OCA\Deck\Db\AclMapper;
|
|
||||||
use OCA\Deck\Db\AssignmentMapper;
|
|
||||||
use OCA\Deck\Db\BoardMapper;
|
|
||||||
use OCA\Deck\Db\CardMapper;
|
use OCA\Deck\Db\CardMapper;
|
||||||
use OCA\Deck\Event\AclCreatedEvent;
|
use OCA\Deck\Event\AclCreatedEvent;
|
||||||
use OCA\Deck\Event\AclDeletedEvent;
|
use OCA\Deck\Event\AclDeletedEvent;
|
||||||
use OCA\Deck\Event\AclUpdatedEvent;
|
use OCA\Deck\Event\AclUpdatedEvent;
|
||||||
|
use OCA\Deck\Event\BoardUpdatedEvent;
|
||||||
use OCA\Deck\Event\CardCreatedEvent;
|
use OCA\Deck\Event\CardCreatedEvent;
|
||||||
use OCA\Deck\Event\CardDeletedEvent;
|
use OCA\Deck\Event\CardDeletedEvent;
|
||||||
use OCA\Deck\Event\CardUpdatedEvent;
|
use OCA\Deck\Event\CardUpdatedEvent;
|
||||||
|
use OCA\Deck\Event\SessionClosedEvent;
|
||||||
|
use OCA\Deck\Event\SessionCreatedEvent;
|
||||||
use OCA\Deck\Listeners\BeforeTemplateRenderedListener;
|
use OCA\Deck\Listeners\BeforeTemplateRenderedListener;
|
||||||
|
use OCA\Deck\Listeners\ParticipantCleanupListener;
|
||||||
use OCA\Deck\Listeners\FullTextSearchEventListener;
|
use OCA\Deck\Listeners\FullTextSearchEventListener;
|
||||||
|
use OCA\Deck\Listeners\ResourceAdditionalScriptsListener;
|
||||||
|
use OCA\Deck\Listeners\ResourceListener;
|
||||||
|
use OCA\Deck\Listeners\LiveUpdateListener;
|
||||||
use OCA\Deck\Middleware\DefaultBoardMiddleware;
|
use OCA\Deck\Middleware\DefaultBoardMiddleware;
|
||||||
use OCA\Deck\Middleware\ExceptionMiddleware;
|
use OCA\Deck\Middleware\ExceptionMiddleware;
|
||||||
use OCA\Deck\Notification\Notifier;
|
use OCA\Deck\Notification\Notifier;
|
||||||
|
use OCA\Deck\Reference\BoardReferenceProvider;
|
||||||
use OCA\Deck\Reference\CardReferenceProvider;
|
use OCA\Deck\Reference\CardReferenceProvider;
|
||||||
|
use OCA\Deck\Reference\CommentReferenceProvider;
|
||||||
use OCA\Deck\Search\CardCommentProvider;
|
use OCA\Deck\Search\CardCommentProvider;
|
||||||
use OCA\Deck\Search\DeckProvider;
|
use OCA\Deck\Search\DeckProvider;
|
||||||
use OCA\Deck\Service\PermissionService;
|
use OCA\Deck\Service\PermissionService;
|
||||||
@@ -60,18 +66,13 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
|||||||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
use OCP\Collaboration\Reference\RenderReferenceEvent;
|
use OCP\Collaboration\Reference\RenderReferenceEvent;
|
||||||
use OCP\Collaboration\Resources\IProviderManager;
|
use OCP\Collaboration\Resources\IProviderManager;
|
||||||
|
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
|
||||||
use OCP\Comments\CommentsEntityEvent;
|
use OCP\Comments\CommentsEntityEvent;
|
||||||
use OCP\Comments\ICommentsManager;
|
use OCP\Comments\ICommentsManager;
|
||||||
use OCP\EventDispatcher\Event;
|
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Group\Events\GroupDeletedEvent;
|
use OCP\Group\Events\GroupDeletedEvent;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IGroupManager;
|
|
||||||
use OCP\IRequest;
|
|
||||||
use OCP\Server;
|
|
||||||
use OCP\IUserManager;
|
|
||||||
use OCP\Notification\IManager as NotificationManager;
|
|
||||||
use OCP\Share\IManager;
|
use OCP\Share\IManager;
|
||||||
use OCP\User\Events\UserDeletedEvent;
|
use OCP\User\Events\UserDeletedEvent;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
@@ -90,15 +91,13 @@ class Application extends App implements IBootstrap {
|
|||||||
$container = $this->getContainer();
|
$container = $this->getContainer();
|
||||||
$eventDispatcher = $container->get(IEventDispatcher::class);
|
$eventDispatcher = $container->get(IEventDispatcher::class);
|
||||||
$eventDispatcher->addListener(RenderReferenceEvent::class, function () {
|
$eventDispatcher->addListener(RenderReferenceEvent::class, function () {
|
||||||
Util::addScript(self::APP_ID, self::APP_ID . '-card-reference');
|
Util::addScript(self::APP_ID, self::APP_ID . '-reference');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void {
|
public function boot(IBootContext $context): void {
|
||||||
$context->injectFn(Closure::fromCallable([$this, 'registerUserGroupHooks']));
|
|
||||||
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEntity']));
|
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEntity']));
|
||||||
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEventHandler']));
|
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEventHandler']));
|
||||||
$context->injectFn(Closure::fromCallable([$this, 'registerNotifications']));
|
|
||||||
$context->injectFn(Closure::fromCallable([$this, 'registerCollaborationResources']));
|
$context->injectFn(Closure::fromCallable([$this, 'registerCollaborationResources']));
|
||||||
|
|
||||||
$context->injectFn(function (IManager $shareManager) {
|
$context->injectFn(function (IManager $shareManager) {
|
||||||
@@ -132,7 +131,8 @@ class Application extends App implements IBootstrap {
|
|||||||
|
|
||||||
// reference widget
|
// reference widget
|
||||||
$context->registerReferenceProvider(CardReferenceProvider::class);
|
$context->registerReferenceProvider(CardReferenceProvider::class);
|
||||||
// $context->registerEventListener(RenderReferenceEvent::class, CardReferenceListener::class);
|
$context->registerReferenceProvider(BoardReferenceProvider::class);
|
||||||
|
$context->registerReferenceProvider(CommentReferenceProvider::class);
|
||||||
|
|
||||||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
||||||
|
|
||||||
@@ -143,57 +143,28 @@ class Application extends App implements IBootstrap {
|
|||||||
$context->registerEventListener(AclCreatedEvent::class, FullTextSearchEventListener::class);
|
$context->registerEventListener(AclCreatedEvent::class, FullTextSearchEventListener::class);
|
||||||
$context->registerEventListener(AclUpdatedEvent::class, FullTextSearchEventListener::class);
|
$context->registerEventListener(AclUpdatedEvent::class, FullTextSearchEventListener::class);
|
||||||
$context->registerEventListener(AclDeletedEvent::class, FullTextSearchEventListener::class);
|
$context->registerEventListener(AclDeletedEvent::class, FullTextSearchEventListener::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function registerNotifications(NotificationManager $notificationManager): void {
|
// Handling cache invalidation for collections
|
||||||
$notificationManager->registerNotifierService(Notifier::class);
|
$context->registerEventListener(AclCreatedEvent::class, ResourceListener::class);
|
||||||
}
|
$context->registerEventListener(AclDeletedEvent::class, ResourceListener::class);
|
||||||
|
|
||||||
private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void {
|
$context->registerEventListener(UserDeletedEvent::class, ParticipantCleanupListener::class);
|
||||||
$container = $this->getContainer();
|
$context->registerEventListener(GroupDeletedEvent::class, ParticipantCleanupListener::class);
|
||||||
/** @var IEventDispatcher $eventDispatcher */
|
$context->registerEventListener(CircleDestroyedEvent::class, ParticipantCleanupListener::class);
|
||||||
$eventDispatcher = $container->get(IEventDispatcher::class);
|
|
||||||
// Delete user/group acl entries when they get deleted
|
|
||||||
$eventDispatcher->addListener(UserDeletedEvent::class, static function (Event $event) use ($container): void {
|
|
||||||
if (!($event instanceof UserDeletedEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$user = $event->getUser();
|
|
||||||
// delete existing acl entries for deleted user
|
|
||||||
/** @var AclMapper $aclMapper */
|
|
||||||
$aclMapper = $container->get(AclMapper::class);
|
|
||||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID());
|
|
||||||
foreach ($acls as $acl) {
|
|
||||||
$aclMapper->delete($acl);
|
|
||||||
}
|
|
||||||
// delete existing user assignments
|
|
||||||
$assignmentMapper = $container->get(AssignmentMapper::class);
|
|
||||||
$assignments = $assignmentMapper->findByParticipant($user->getUID());
|
|
||||||
foreach ($assignments as $assignment) {
|
|
||||||
$assignmentMapper->delete($assignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var BoardMapper $boardMapper */
|
// Event listening for realtime updates via notify_push
|
||||||
$boardMapper = $container->get(BoardMapper::class);
|
$context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class);
|
||||||
$boards = $boardMapper->findAllByOwner($user->getUID());
|
$context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class);
|
||||||
foreach ($boards as $board) {
|
$context->registerEventListener(BoardUpdatedEvent::class, LiveUpdateListener::class);
|
||||||
$boardMapper->delete($board);
|
$context->registerEventListener(CardCreatedEvent::class, LiveUpdateListener::class);
|
||||||
}
|
$context->registerEventListener(CardUpdatedEvent::class, LiveUpdateListener::class);
|
||||||
});
|
$context->registerEventListener(CardDeletedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclCreatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclUpdatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclDeletedEvent::class, LiveUpdateListener::class);
|
||||||
|
|
||||||
$eventDispatcher->addListener(GroupDeletedEvent::class, static function (Event $event) use ($container): void {
|
$context->registerNotifierService(Notifier::class);
|
||||||
if (!($event instanceof GroupDeletedEvent)) {
|
$context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);
|
||||||
return;
|
|
||||||
}
|
|
||||||
$group = $event->getGroup();
|
|
||||||
/** @var AclMapper $aclMapper */
|
|
||||||
$aclMapper = $container->get(AclMapper::class);
|
|
||||||
$aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
|
||||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
|
||||||
foreach ($acls as $acl) {
|
|
||||||
$aclMapper->delete($acl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void {
|
public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void {
|
||||||
@@ -219,16 +190,8 @@ class Application extends App implements IBootstrap {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function registerCollaborationResources(IProviderManager $resourceManager, SymfonyAdapter $symfonyAdapter): void {
|
protected function registerCollaborationResources(IProviderManager $resourceManager): void {
|
||||||
$resourceManager->registerResourceProvider(ResourceProvider::class);
|
$resourceManager->registerResourceProvider(ResourceProvider::class);
|
||||||
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
|
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
|
||||||
|
|
||||||
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
|
|
||||||
if (strpos(Server::get(IRequest::class)->getPathInfo(), '/call/') === 0) {
|
|
||||||
// Talk integration has its own entrypoint which already includes collections handling
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Util::addScript('deck', 'deck-collections');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,14 @@ class BoardApiController extends ApiController {
|
|||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*
|
*
|
||||||
* Return all of the boards that the current user has access to.
|
* Return all of the boards that the current user has access to.
|
||||||
|
*
|
||||||
|
* @param bool $details
|
||||||
* @throws StatusException
|
* @throws StatusException
|
||||||
*/
|
*/
|
||||||
public function index($details = null) {
|
public function index(bool $details = false) {
|
||||||
$modified = $this->request->getHeader('If-Modified-Since');
|
$modified = $this->request->getHeader('If-Modified-Since');
|
||||||
if ($modified === null || $modified === '') {
|
if ($modified === null || $modified === '') {
|
||||||
$boards = $this->boardService->findAll(0, $details);
|
$boards = $this->boardService->findAll(0, $details === true);
|
||||||
} else {
|
} else {
|
||||||
$date = Util::parseHTTPDate($modified);
|
$date = Util::parseHTTPDate($modified);
|
||||||
if (!$date) {
|
if (!$date) {
|
||||||
|
|||||||
@@ -169,4 +169,15 @@ class BoardController extends ApiController {
|
|||||||
|
|
||||||
return new DataResponse([], HTTP::STATUS_UNAUTHORIZED);
|
return new DataResponse([], HTTP::STATUS_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @param $boardId
|
||||||
|
* @return Board
|
||||||
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
|
*/
|
||||||
|
public function export($boardId) {
|
||||||
|
return $this->boardService->export($boardId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use OCA\Deck\AppInfo\Application;
|
|||||||
use OCA\Deck\Service\ConfigService;
|
use OCA\Deck\Service\ConfigService;
|
||||||
use OCA\Deck\Service\PermissionService;
|
use OCA\Deck\Service\PermissionService;
|
||||||
use OCA\Files\Event\LoadSidebar;
|
use OCA\Files\Event\LoadSidebar;
|
||||||
|
use OCA\Text\Event\LoadEditor;
|
||||||
use OCA\Viewer\Event\LoadViewer;
|
use OCA\Viewer\Event\LoadViewer;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as CollaborationResourcesEvent;
|
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as CollaborationResourcesEvent;
|
||||||
@@ -90,6 +91,9 @@ class PageController extends Controller {
|
|||||||
|
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||||
$this->eventDispatcher->dispatchTyped(new CollaborationResourcesEvent());
|
$this->eventDispatcher->dispatchTyped(new CollaborationResourcesEvent());
|
||||||
|
if (class_exists(LoadEditor::class)) {
|
||||||
|
$this->eventDispatcher->dispatchTyped(new LoadEditor());
|
||||||
|
}
|
||||||
if (class_exists(LoadViewer::class)) {
|
if (class_exists(LoadViewer::class)) {
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||||
}
|
}
|
||||||
|
|||||||
91
lib/Controller/SessionController.php
Normal file
91
lib/Controller/SessionController.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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\SessionService;
|
||||||
|
use OCA\Deck\Service\PermissionService;
|
||||||
|
use OCA\Deck\Db\BoardMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\OCSController;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCA\Deck\Db\Acl;
|
||||||
|
|
||||||
|
class SessionController extends OCSController {
|
||||||
|
private SessionService $sessionService;
|
||||||
|
private PermissionService $permissionService;
|
||||||
|
private BoardMapper $boardMapper;
|
||||||
|
|
||||||
|
public function __construct($appName,
|
||||||
|
IRequest $request,
|
||||||
|
SessionService $sessionService,
|
||||||
|
PermissionService $permissionService,
|
||||||
|
BoardMapper $boardMapper
|
||||||
|
) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->sessionService = $sessionService;
|
||||||
|
$this->permissionService = $permissionService;
|
||||||
|
$this->boardMapper = $boardMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function create(int $boardId): DataResponse {
|
||||||
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
|
|
||||||
|
$session = $this->sessionService->initSession($boardId);
|
||||||
|
return new DataResponse([
|
||||||
|
'token' => $session->getToken(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notifies the server that the session is still active
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @param $boardId
|
||||||
|
*/
|
||||||
|
public function sync(int $boardId, string $token): DataResponse {
|
||||||
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
|
try {
|
||||||
|
$this->sessionService->syncSession($boardId, $token);
|
||||||
|
return new DataResponse([]);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return new DataResponse([], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete a session if existing
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @param $boardId
|
||||||
|
*/
|
||||||
|
public function close(int $boardId, string $token) {
|
||||||
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
|
$this->sessionService->closeSession($boardId, $token);
|
||||||
|
return new DataResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/Cron/SessionsCleanup.php
Normal file
56
lib/Cron/SessionsCleanup.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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\Cron;
|
||||||
|
|
||||||
|
use OCA\Deck\Service\SessionService;
|
||||||
|
use OCP\AppFramework\Utility\ITimeFactory;
|
||||||
|
use OCP\BackgroundJob\TimedJob;
|
||||||
|
use OCP\ILogger;
|
||||||
|
|
||||||
|
class SessionsCleanup extends TimedJob {
|
||||||
|
private $sessionService;
|
||||||
|
private $documentService;
|
||||||
|
private $logger;
|
||||||
|
private $imageService;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(ITimeFactory $time,
|
||||||
|
SessionService $sessionService,
|
||||||
|
ILogger $logger) {
|
||||||
|
parent::__construct($time);
|
||||||
|
$this->sessionService = $sessionService;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->setInterval(SessionService::SESSION_VALID_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function run($argument) {
|
||||||
|
$this->logger->debug('Run cleanup job for deck sessions');
|
||||||
|
|
||||||
|
$removedSessions = $this->sessionService->removeInactiveSessions();
|
||||||
|
$this->logger->debug('Removed ' . $removedSessions . ' inactive sessions');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ class DeckCalendarBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getBoards(): array {
|
public function getBoards(): array {
|
||||||
return $this->boardService->findAll(-1, null, false);
|
return $this->boardService->findAll(-1, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBoard(int $id): Board {
|
public function getBoard(int $id): Board {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
|
|||||||
$nowTimestamp = (new Datetime())->getTimestamp();
|
$nowTimestamp = (new Datetime())->getTimestamp();
|
||||||
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
|
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
|
||||||
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
|
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
|
||||||
if ($card['duedate']) {
|
if (isset($card['duedate'])) {
|
||||||
$ts = (new Datetime($card['duedate']))->getTimestamp();
|
$ts = (new Datetime($card['duedate']))->getTimestamp();
|
||||||
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
|
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
/** @template-extends DeckMapper<Acl> */
|
||||||
class AclMapper extends DeckMapper implements IPermissionMapper {
|
class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||||
public function __construct(IDBConnection $db) {
|
public function __construct(IDBConnection $db) {
|
||||||
parent::__construct($db, 'deck_board_acl', Acl::class);
|
parent::__construct($db, 'deck_board_acl', Acl::class);
|
||||||
@@ -51,6 +52,20 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findIn(array $boardIds, ?int $limit = null, ?int $offset = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
|
||||||
|
->from('deck_board_acl')
|
||||||
|
->where($qb->expr()->in('board_id', $qb->createParameter('boardIds')))
|
||||||
|
->setMaxResults($limit)
|
||||||
|
->setFirstResult($offset);
|
||||||
|
|
||||||
|
return iterator_to_array($this->chunkQuery($boardIds, function (array $ids) use ($qb) {
|
||||||
|
$qb->setParameter('boardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param numeric $userId
|
* @param numeric $userId
|
||||||
* @param numeric $id
|
* @param numeric $id
|
||||||
@@ -110,4 +125,12 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
|
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByType(int $type): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('deck_board_acl')
|
||||||
|
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,13 @@ namespace OCA\Deck\Db;
|
|||||||
use OCA\Deck\NotFoundException;
|
use OCA\Deck\NotFoundException;
|
||||||
use OCA\Deck\Service\CirclesService;
|
use OCA\Deck\Service\CirclesService;
|
||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\QBMapper;
|
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
/** @template-extends DeckMapper<Assignment> */
|
||||||
|
class AssignmentMapper extends DeckMapper implements IPermissionMapper {
|
||||||
|
|
||||||
/** @var CardMapper */
|
/** @var CardMapper */
|
||||||
private $cardMapper;
|
private $cardMapper;
|
||||||
@@ -59,7 +58,7 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from('deck_assigned_users')
|
->from('deck_assigned_users')
|
||||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, PDO::PARAM_INT)));
|
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||||
$users = $this->findEntities($qb);
|
$users = $this->findEntities($qb);
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$this->mapParticipant($user);
|
$this->mapParticipant($user);
|
||||||
@@ -67,12 +66,29 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return $users;
|
return $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findIn(array $cardIds): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('deck_assigned_users')
|
||||||
|
->where($qb->expr()->in('card_id', $qb->createParameter('cardIds')));
|
||||||
|
|
||||||
|
$users = iterator_to_array($this->chunkQuery($cardIds, function (array $ids) use ($qb) {
|
||||||
|
$qb->setParameter('cardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}));
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$this->mapParticipant($user);
|
||||||
|
}
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
public function findByParticipant(string $participant, $type = Assignment::TYPE_USER): array {
|
public function findByParticipant(string $participant, $type = Assignment::TYPE_USER): array {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from('deck_assigned_users')
|
->from('deck_assigned_users')
|
||||||
->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, PDO::PARAM_STR)))
|
->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
|
||||||
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, PDO::PARAM_INT)));
|
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
|
||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +147,8 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
|||||||
|
|
||||||
private function getOrigin(Assignment $assignment) {
|
private function getOrigin(Assignment $assignment) {
|
||||||
if ($assignment->getType() === Assignment::TYPE_USER) {
|
if ($assignment->getType() === Assignment::TYPE_USER) {
|
||||||
$origin = $this->userManager->get($assignment->getParticipant());
|
$origin = $this->userManager->userExists($assignment->getParticipant());
|
||||||
return $origin ? new User($origin) : null;
|
return $origin ? new User($assignment->getParticipant(), $this->userManager) : null;
|
||||||
}
|
}
|
||||||
if ($assignment->getType() === Assignment::TYPE_GROUP) {
|
if ($assignment->getType() === Assignment::TYPE_GROUP) {
|
||||||
$origin = $this->groupManager->get($assignment->getParticipant());
|
$origin = $this->groupManager->get($assignment->getParticipant());
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
|
|||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
/** @template-extends DeckMapper<Attachment> */
|
||||||
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||||
private $cardMapper;
|
private $cardMapper;
|
||||||
private $userManager;
|
private $userManager;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class Board extends RelationalEntity {
|
|||||||
protected $users = [];
|
protected $users = [];
|
||||||
protected $shared;
|
protected $shared;
|
||||||
protected $stacks = [];
|
protected $stacks = [];
|
||||||
|
protected $activeSessions = [];
|
||||||
protected $deletedAt = 0;
|
protected $deletedAt = 0;
|
||||||
protected $lastModified = 0;
|
protected $lastModified = 0;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class Board extends RelationalEntity {
|
|||||||
$this->addRelation('acl');
|
$this->addRelation('acl');
|
||||||
$this->addRelation('shared');
|
$this->addRelation('shared');
|
||||||
$this->addRelation('users');
|
$this->addRelation('users');
|
||||||
|
$this->addRelation('activeSessions');
|
||||||
$this->addRelation('permissions');
|
$this->addRelation('permissions');
|
||||||
$this->addRelation('stacks');
|
$this->addRelation('stacks');
|
||||||
$this->addRelation('settings');
|
$this->addRelation('settings');
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ use OCP\IUserManager;
|
|||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/** @template-extends QBMapper<Board> */
|
||||||
class BoardMapper extends QBMapper implements IPermissionMapper {
|
class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||||
private $labelMapper;
|
private $labelMapper;
|
||||||
private $aclMapper;
|
private $aclMapper;
|
||||||
@@ -89,9 +90,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$this->boardCache[$id] = $this->findEntity($qb);
|
$this->boardCache[$id] = $this->findEntity($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME is this necessary? it was NOT done with the old mapper
|
|
||||||
// $this->mapOwner($board);
|
|
||||||
|
|
||||||
// Add labels
|
// Add labels
|
||||||
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
||||||
$labels = $this->labelMapper->findAll($id);
|
$labels = $this->labelMapper->findAll($id);
|
||||||
@@ -158,7 +156,21 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
|
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
|
||||||
$groupBoards = $this->findAllByGroups($userId, $groups, 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);
|
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
||||||
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
$allBoards = array_values(array_unique(array_merge($userBoards, $groupBoards, $circleBoards)));
|
||||||
|
|
||||||
|
// Could be moved outside
|
||||||
|
$acls = $this->aclMapper->findIn(array_map(function ($board) {
|
||||||
|
return $board->getId();
|
||||||
|
}, $allBoards));
|
||||||
|
|
||||||
|
/* @var Board $entry */
|
||||||
|
foreach ($allBoards as $entry) {
|
||||||
|
$boardAcls = array_values(array_filter($acls, function ($acl) use ($entry) {
|
||||||
|
return $acl->getBoardId() === $entry->getId();
|
||||||
|
}));
|
||||||
|
$entry->setAcl($boardAcls);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($allBoards as $board) {
|
foreach ($allBoards as $board) {
|
||||||
$this->boardCache[$board->getId()] = $board;
|
$this->boardCache[$board->getId()] = $board;
|
||||||
}
|
}
|
||||||
@@ -258,11 +270,7 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$entry->setShared(1);
|
$entry->setShared(1);
|
||||||
}
|
}
|
||||||
$entries = array_merge($entries, $sharedEntries);
|
$entries = array_merge($entries, $sharedEntries);
|
||||||
/* @var Board $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
|
||||||
$entry->setAcl($acl);
|
|
||||||
}
|
|
||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,11 +343,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
$entry->setShared(2);
|
$entry->setShared(2);
|
||||||
}
|
}
|
||||||
/* @var Board $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
|
||||||
$entry->setAcl($acl);
|
|
||||||
}
|
|
||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,11 +399,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
$entry->setShared(2);
|
$entry->setShared(2);
|
||||||
}
|
}
|
||||||
/* @var Board $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
|
||||||
$entry->setAcl($acl);
|
|
||||||
}
|
|
||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,13 +452,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function mapAcl(Acl &$acl) {
|
public function mapAcl(Acl &$acl) {
|
||||||
$userManager = $this->userManager;
|
|
||||||
$groupManager = $this->groupManager;
|
$groupManager = $this->groupManager;
|
||||||
$acl->resolveRelation('participant', function ($participant) use (&$acl, &$userManager, &$groupManager) {
|
$acl->resolveRelation('participant', function ($participant) use (&$acl, &$userManager, &$groupManager) {
|
||||||
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
|
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
|
||||||
$user = $userManager->get($participant);
|
if ($this->userManager->userExists($acl->getParticipant())) {
|
||||||
if ($user !== null) {
|
return new User($acl->getParticipant(), $this->userManager);
|
||||||
return new User($user);
|
|
||||||
}
|
}
|
||||||
$this->logger->debug('User ' . $acl->getId() . ' not found when mapping acl ' . $acl->getParticipant());
|
$this->logger->debug('User ' . $acl->getId() . ' not found when mapping acl ' . $acl->getParticipant());
|
||||||
return null;
|
return null;
|
||||||
@@ -498,9 +494,8 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
public function mapOwner(Board &$board) {
|
public function mapOwner(Board &$board) {
|
||||||
$userManager = $this->userManager;
|
$userManager = $this->userManager;
|
||||||
$board->resolveRelation('owner', function ($owner) use (&$userManager) {
|
$board->resolveRelation('owner', function ($owner) use (&$userManager) {
|
||||||
$user = $userManager->get($owner);
|
if ($this->userManager->userExists($owner)) {
|
||||||
if ($user !== null) {
|
return new User($owner, $userManager);
|
||||||
return new User($user);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -158,6 +158,21 @@ class Card extends RelationalEntity {
|
|||||||
return $calendar;
|
return $calendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDaysUntilDue(): ?int {
|
||||||
|
if ($this->getDuedate() === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$today = new DateTime();
|
||||||
|
$today->setTime(0, 0);
|
||||||
|
|
||||||
|
$matchDate = DateTime::createFromInterface($this->getDuedate());
|
||||||
|
$matchDate->setTime(0, 0);
|
||||||
|
|
||||||
|
$diff = $today->diff($matchDate);
|
||||||
|
return (int) $diff->format('%R%a'); // Extract days count in interval
|
||||||
|
}
|
||||||
|
|
||||||
public function getCalendarPrefix(): string {
|
public function getCalendarPrefix(): string {
|
||||||
return 'card';
|
return 'card';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use OCP\IUser;
|
|||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Notification\IManager;
|
use OCP\Notification\IManager;
|
||||||
|
|
||||||
|
/** @template-extends QBMapper<Card> */
|
||||||
class CardMapper extends QBMapper implements IPermissionMapper {
|
class CardMapper extends QBMapper implements IPermissionMapper {
|
||||||
|
|
||||||
/** @var LabelMapper */
|
/** @var LabelMapper */
|
||||||
@@ -253,13 +254,13 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllWithDue($boardId) {
|
public function findAllWithDue(array $boardIds) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('c.*')
|
$qb->select('c.*')
|
||||||
->from('deck_cards', 'c')
|
->from('deck_cards', 'c')
|
||||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||||
->andWhere($qb->expr()->isNotNull('c.duedate'))
|
->andWhere($qb->expr()->isNotNull('c.duedate'))
|
||||||
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||||
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||||
@@ -269,14 +270,14 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findToMeOrNotAssignedCards($boardId, $username) {
|
public function findToMeOrNotAssignedCards(array $boardIds, string $username) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('c.*')
|
$qb->select('c.*')
|
||||||
->from('deck_cards', 'c')
|
->from('deck_cards', 'c')
|
||||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||||
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
|
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
|
||||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||||
->andWhere($qb->expr()->orX(
|
->andWhere($qb->expr()->orX(
|
||||||
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
|
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
|
||||||
$qb->expr()->isNull('u.participant'))
|
$qb->expr()->isNull('u.participant'))
|
||||||
@@ -606,9 +607,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
public function mapOwner(Card &$card) {
|
public function mapOwner(Card &$card) {
|
||||||
$userManager = $this->userManager;
|
$userManager = $this->userManager;
|
||||||
$card->resolveRelation('owner', function ($owner) use (&$userManager) {
|
$card->resolveRelation('owner', function ($owner) use (&$userManager) {
|
||||||
$user = $userManager->get($owner);
|
if ($userManager->userExists($owner)) {
|
||||||
if ($user !== null) {
|
return new User($owner, $this->userManager);
|
||||||
return new User($user);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,19 +23,20 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Db;
|
namespace OCA\Deck\Db;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\QBMapper;
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DeckMapper
|
* @template T of Entity
|
||||||
*
|
* @template-extends QBMapper<T>
|
||||||
* @package OCA\Deck\Db
|
|
||||||
*/
|
*/
|
||||||
class DeckMapper extends QBMapper {
|
abstract class DeckMapper extends QBMapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $id
|
* @param $id
|
||||||
* @return \OCP\AppFramework\Db\Entity if not found
|
* @return T
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
*/
|
*/
|
||||||
@@ -47,4 +48,21 @@ class DeckMapper extends QBMapper {
|
|||||||
|
|
||||||
return $this->findEntity($qb);
|
return $this->findEntity($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to split passed array into chunks of 1000 elements and
|
||||||
|
* call a given callback for fetching query results
|
||||||
|
*
|
||||||
|
* Can be useful to limit to 1000 results per query for oracle compatiblity
|
||||||
|
* but still iterate over all results
|
||||||
|
*/
|
||||||
|
public function chunkQuery(array $ids, callable $callback): Generator {
|
||||||
|
$limit = 1000;
|
||||||
|
while (!empty($ids)) {
|
||||||
|
$slice = array_splice($ids, 0, $limit);
|
||||||
|
foreach ($callback($slice) as $item) {
|
||||||
|
yield $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
/** @template-extends DeckMapper<Label> */
|
||||||
class LabelMapper extends DeckMapper implements IPermissionMapper {
|
class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||||
public function __construct(IDBConnection $db) {
|
public function __construct(IDBConnection $db) {
|
||||||
parent::__construct($db, 'deck_labels', Label::class);
|
parent::__construct($db, 'deck_labels', Label::class);
|
||||||
@@ -51,11 +52,6 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Entity $entity
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function delete(Entity $entity): Entity {
|
public function delete(Entity $entity): Entity {
|
||||||
// delete assigned labels
|
// delete assigned labels
|
||||||
$this->deleteLabelAssignments($entity->getId());
|
$this->deleteLabelAssignments($entity->getId());
|
||||||
@@ -83,6 +79,19 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findAssignedLabelsForCards($cardIds, $limit = null, $offset = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('l.*', 'card_id')
|
||||||
|
->from($this->getTableName(), 'l')
|
||||||
|
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
|
||||||
|
->where($qb->expr()->in('card_id', $qb->createNamedParameter($cardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||||
|
->orderBy('l.id')
|
||||||
|
->setMaxResults($limit)
|
||||||
|
->setFirstResult($offset);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param numeric $boardId
|
* @param numeric $boardId
|
||||||
* @param int|null $limit
|
* @param int|null $limit
|
||||||
@@ -105,23 +114,12 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Entity $entity
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function insert(Entity $entity): Entity {
|
public function insert(Entity $entity): Entity {
|
||||||
$entity->setLastModified(time());
|
$entity->setLastModified(time());
|
||||||
return parent::insert($entity);
|
return parent::insert($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function update(Entity $entity, bool $updateModified = true): Entity {
|
||||||
* @param Entity $entity
|
|
||||||
* @param bool $updateModified
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function update(Entity $entity, $updateModified = true): Entity {
|
|
||||||
if ($updateModified) {
|
if ($updateModified) {
|
||||||
$entity->setLastModified(time());
|
$entity->setLastModified(time());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
|||||||
* @param string $attribute the name of the attribute
|
* @param string $attribute the name of the attribute
|
||||||
* @since 7.0.0
|
* @since 7.0.0
|
||||||
*/
|
*/
|
||||||
protected function markFieldUpdated($attribute) {
|
protected function markFieldUpdated(string $attribute): void {
|
||||||
if (!in_array($attribute, $this->_relations, true)) {
|
if (!in_array($attribute, $this->_relations, true)) {
|
||||||
parent::markFieldUpdated($attribute);
|
parent::markFieldUpdated($attribute);
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __call($methodName, $args) {
|
public function __call(string $methodName, array $args) {
|
||||||
$attr = lcfirst(substr($methodName, 7));
|
$attr = lcfirst(substr($methodName, 7));
|
||||||
if (array_key_exists($attr, $this->_resolvedProperties) && strpos($methodName, 'resolve') === 0) {
|
if (array_key_exists($attr, $this->_resolvedProperties) && strpos($methodName, 'resolve') === 0) {
|
||||||
if ($this->_resolvedProperties[$attr] !== null) {
|
if ($this->_resolvedProperties[$attr] !== null) {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class RelationalObject implements JsonSerializable {
|
|||||||
* RelationalObject constructor.
|
* RelationalObject constructor.
|
||||||
*
|
*
|
||||||
* @param $primaryKey string
|
* @param $primaryKey string
|
||||||
* @param $object
|
* @param callable|mixed $object
|
||||||
*/
|
*/
|
||||||
public function __construct($primaryKey, $object) {
|
public function __construct($primaryKey, $object) {
|
||||||
$this->primaryKey = $primaryKey;
|
$this->primaryKey = $primaryKey;
|
||||||
@@ -47,16 +47,24 @@ class RelationalObject implements JsonSerializable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
if (is_callable($this->object)) {
|
||||||
|
$this->object = call_user_func($this->object, $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->object;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should be overwritten if object doesn't implement \JsonSerializable
|
* This method should be overwritten if object doesn't implement \JsonSerializable
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function getObjectSerialization() {
|
public function getObjectSerialization() {
|
||||||
if ($this->object instanceof JsonSerializable) {
|
if ($this->getObject() instanceof JsonSerializable) {
|
||||||
return $this->object->jsonSerialize();
|
return $this->getObject()->jsonSerialize();
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
|
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this->getObject()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
lib/Db/Session.php
Normal file
51
lib/Db/Session.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
class Session extends Entity implements \JsonSerializable {
|
||||||
|
public $id;
|
||||||
|
protected $userId;
|
||||||
|
protected $token;
|
||||||
|
protected $lastContact;
|
||||||
|
protected $boardId;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->addType('id', 'integer');
|
||||||
|
$this->addType('boardId', 'integer');
|
||||||
|
$this->addType('lastContact', 'integer');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'userId' => $this->userId,
|
||||||
|
'token' => $this->token,
|
||||||
|
'lastContact' => $this->lastContact,
|
||||||
|
'boardId' => $this->boardId,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
74
lib/Db/SessionMapper.php
Normal file
74
lib/Db/SessionMapper.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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\Db;
|
||||||
|
|
||||||
|
use OCA\Deck\Service\SessionService;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
/** @template-extends QBMapper<Session> */
|
||||||
|
class SessionMapper extends QBMapper {
|
||||||
|
public function __construct(IDBConnection $db) {
|
||||||
|
parent::__construct($db, 'deck_sessions', Session::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find(int $boardId, string $userId, string $token): Session {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$result = $qb->select('*')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
|
||||||
|
->andWhere($qb->expr()->gt('last_contact', $qb->createNamedParameter(time() - SessionService::SESSION_VALID_TIME)))
|
||||||
|
->executeQuery();
|
||||||
|
|
||||||
|
$data = $result->fetch();
|
||||||
|
$result->closeCursor();
|
||||||
|
if ($data === false) {
|
||||||
|
throw new DoesNotExistException('Session is invalid');
|
||||||
|
}
|
||||||
|
$session = Session::fromRow($data);
|
||||||
|
if ($session->getUserId() != $userId || $session->getBoardId() != $boardId) {
|
||||||
|
throw new DoesNotExistException('Session is invalid');
|
||||||
|
}
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAllActive($boardId) {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'board_id', 'last_contact', 'user_id', 'token')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId)))
|
||||||
|
->andWhere($qb->expr()->gt('last_contact', $qb->createNamedParameter(time() - SessionService::SESSION_VALID_TIME)))
|
||||||
|
->executeQuery();
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
public function deleteInactive(): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->lt('last_contact', $qb->createNamedParameter(time() - SessionService::SESSION_VALID_TIME)));
|
||||||
|
return $qb->executeStatement();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,15 +26,27 @@ namespace OCA\Deck\Db;
|
|||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
|
use OCP\Cache\CappedMemoryCache;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
use OCP\ICache;
|
||||||
|
use OCP\ICacheFactory;
|
||||||
|
|
||||||
|
/** @template-extends DeckMapper<Stack> */
|
||||||
class StackMapper extends DeckMapper implements IPermissionMapper {
|
class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||||
private $cardMapper;
|
private CappedMemoryCache $stackCache;
|
||||||
|
private CardMapper $cardMapper;
|
||||||
|
private ICache $cache;
|
||||||
|
|
||||||
public function __construct(IDBConnection $db, CardMapper $cardMapper) {
|
public function __construct(
|
||||||
|
IDBConnection $db,
|
||||||
|
CardMapper $cardMapper,
|
||||||
|
ICacheFactory $cacheFactory
|
||||||
|
) {
|
||||||
parent::__construct($db, 'deck_stacks', Stack::class);
|
parent::__construct($db, 'deck_stacks', Stack::class);
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
|
$this->stackCache = new CappedMemoryCache();
|
||||||
|
$this->cache = $cacheFactory->createDistributed('deck-stackMapper');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -46,12 +58,17 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
* @throws \OCP\DB\Exception
|
* @throws \OCP\DB\Exception
|
||||||
*/
|
*/
|
||||||
public function find($id): Stack {
|
public function find($id): Stack {
|
||||||
|
if (isset($this->stackCache[(string)$id])) {
|
||||||
|
return $this->stackCache[(string)$id];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from($this->getTableName())
|
->from($this->getTableName())
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
return $this->findEntity($qb);
|
$this->stackCache[(string)$id] = $this->findEntity($qb);
|
||||||
|
return $this->stackCache[(string)$id];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,14 +129,16 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function update(Entity $entity): Entity {
|
||||||
* @param Entity $entity
|
$result = parent::update($entity);
|
||||||
* @return Entity
|
$this->stackCache[(string)$entity->getId()] = $result;
|
||||||
* @throws \OCP\DB\Exception
|
return $result;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public function delete(Entity $entity): Entity {
|
public function delete(Entity $entity): Entity {
|
||||||
// delete cards on stack
|
// delete cards on stack
|
||||||
$this->cardMapper->deleteByStack($entity->getId());
|
$this->cardMapper->deleteByStack($entity->getId());
|
||||||
|
unset($this->stackCache[(string)$entity->getId()]);
|
||||||
return parent::delete($entity);
|
return parent::delete($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +165,19 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
* @throws \OCP\DB\Exception
|
* @throws \OCP\DB\Exception
|
||||||
*/
|
*/
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
|
$result = $this->cache->get('findBoardId:' . $id);
|
||||||
|
if ($result !== null) {
|
||||||
|
return $result !== false ? $result : null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$entity = $this->find($id);
|
$entity = $this->find($id);
|
||||||
return $entity->getBoardId();
|
$result = $entity->getBoardId();
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
|
$result = false;
|
||||||
} catch (MultipleObjectsReturnedException $e) {
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
}
|
}
|
||||||
return null;
|
$this->cache->set('findBoardId:' . $id, $result);
|
||||||
|
|
||||||
|
return $result !== false ? $result : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,27 +23,30 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Db;
|
namespace OCA\Deck\Db;
|
||||||
|
|
||||||
use OCP\IUser;
|
use OCP\IUserManager;
|
||||||
|
|
||||||
class User extends RelationalObject {
|
class User extends RelationalObject {
|
||||||
public function __construct(IUser $user) {
|
private IUserManager $userManager;
|
||||||
$primaryKey = $user->getUID();
|
public function __construct($uid, IUserManager $userManager) {
|
||||||
parent::__construct($primaryKey, $user);
|
$this->userManager = $userManager;
|
||||||
|
parent::__construct($uid, function ($object) {
|
||||||
|
return $this->userManager->get($object->getPrimaryKey());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getObjectSerialization() {
|
public function getObjectSerialization() {
|
||||||
return [
|
return [
|
||||||
'uid' => $this->object->getUID(),
|
'uid' => $this->getObject()->getUID(),
|
||||||
'displayname' => $this->object->getDisplayName(),
|
'displayname' => $this->getObject()->getDisplayName(),
|
||||||
'type' => 0
|
'type' => Acl::PERMISSION_TYPE_USER
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUID() {
|
public function getUID() {
|
||||||
return $this->object->getUID();
|
return $this->getPrimaryKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDisplayName() {
|
public function getDisplayName() {
|
||||||
return $this->object->getDisplayName();
|
return $this->userManager->getDisplayName($this->getPrimaryKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,8 @@ abstract class AAclEvent extends Event {
|
|||||||
public function getAcl(): Acl {
|
public function getAcl(): Acl {
|
||||||
return $this->acl;
|
return $this->acl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBoardId(): int {
|
||||||
|
return $this->acl->getBoardId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
lib/Event/BoardUpdatedEvent.php
Normal file
43
lib/Event/BoardUpdatedEvent.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2022 chandi Langecker <git@chandi.it>
|
||||||
|
*
|
||||||
|
* @author chandi Langecker <git@chandi.it>
|
||||||
|
*
|
||||||
|
* @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 OCP\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class BoardUpdatedEvent extends Event {
|
||||||
|
private $boardId;
|
||||||
|
|
||||||
|
public function __construct(int $boardId) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->boardId = $boardId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBoardId(): int {
|
||||||
|
return $this->boardId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,5 +26,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\Deck\Event;
|
namespace OCA\Deck\Event;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\Card;
|
||||||
|
|
||||||
class CardUpdatedEvent extends ACardEvent {
|
class CardUpdatedEvent extends ACardEvent {
|
||||||
|
private $cardBefore;
|
||||||
|
|
||||||
|
public function __construct(Card $card, Card $before = null) {
|
||||||
|
parent::__construct($card);
|
||||||
|
$this->cardBefore = $before;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCardBefore() {
|
||||||
|
return $this->cardBefore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
lib/Event/SessionClosedEvent.php
Normal file
48
lib/Event/SessionClosedEvent.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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 OCP\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class SessionClosedEvent extends Event {
|
||||||
|
private $boardId;
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(int $boardId, string $userId) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->boardId = $boardId;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBoardId(): int {
|
||||||
|
return $this->boardId;
|
||||||
|
}
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
lib/Event/SessionCreatedEvent.php
Normal file
49
lib/Event/SessionCreatedEvent.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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 OCP\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class SessionCreatedEvent extends Event {
|
||||||
|
private $boardId;
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(int $boardId, string $userId) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->boardId = $boardId;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBoardId(): int {
|
||||||
|
return $this->boardId;
|
||||||
|
}
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ use OCP\EventDispatcher\IEventListener;
|
|||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|BeforeTemplateRenderedEvent> */
|
||||||
class BeforeTemplateRenderedListener implements IEventListener {
|
class BeforeTemplateRenderedListener implements IEventListener {
|
||||||
private $request;
|
private $request;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ use OCP\FullTextSearch\Model\IIndex;
|
|||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|ACardEvent|AAclEvent> */
|
||||||
class FullTextSearchEventListener implements IEventListener {
|
class FullTextSearchEventListener implements IEventListener {
|
||||||
|
|
||||||
/** @var string|null */
|
/** @var string|null */
|
||||||
|
|||||||
115
lib/Listeners/LiveUpdateListener.php
Normal file
115
lib/Listeners/LiveUpdateListener.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace OCA\Deck\Listeners;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\StackMapper;
|
||||||
|
use OCA\Deck\NotifyPushEvents;
|
||||||
|
use OCA\Deck\Event\AAclEvent;
|
||||||
|
use OCA\Deck\Event\ACardEvent;
|
||||||
|
use OCA\Deck\Event\BoardUpdatedEvent;
|
||||||
|
use OCA\Deck\Event\CardUpdatedEvent;
|
||||||
|
use OCA\Deck\Event\SessionClosedEvent;
|
||||||
|
use OCA\Deck\Event\SessionCreatedEvent;
|
||||||
|
use OCA\Deck\Service\SessionService;
|
||||||
|
use OCA\NotifyPush\Queue\IQueue;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|SessionCreatedEvent|SessionClosedEvent|AAclEvent|ACardEvent|CardUpdatedEvent|BoardUpdatedEvent> */
|
||||||
|
class LiveUpdateListener implements IEventListener {
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
private SessionService $sessionService;
|
||||||
|
private IRequest $request;
|
||||||
|
private StackMapper $stackMapper;
|
||||||
|
private $queue;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ContainerInterface $container,
|
||||||
|
IRequest $request,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
SessionService $sessionService,
|
||||||
|
StackMapper $stackMapper
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$this->queue = $container->get(IQueue::class);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// most likely notify_push is not installed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->sessionService = $sessionService;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->stackMapper = $stackMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
if (!$this->queue) {
|
||||||
|
// notify_push is not active
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// the web frontend is adding the Session-ID as a header
|
||||||
|
// TODO: verify the token! this currently allows to spoof a token from someone
|
||||||
|
// else, preventing this person from getting updates
|
||||||
|
$causingSessionToken = $this->request->getHeader('x-nc-deck-session');
|
||||||
|
if (
|
||||||
|
$event instanceof SessionCreatedEvent ||
|
||||||
|
$event instanceof SessionClosedEvent ||
|
||||||
|
$event instanceof BoardUpdatedEvent ||
|
||||||
|
$event instanceof AAclEvent
|
||||||
|
) {
|
||||||
|
$this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [
|
||||||
|
'id' => $event->getBoardId()
|
||||||
|
], $causingSessionToken);
|
||||||
|
} elseif ($event instanceof ACardEvent) {
|
||||||
|
$boardId = $this->stackMapper->findBoardId($event->getCard()->getStackId());
|
||||||
|
$this->sessionService->notifyAllSessions($this->queue, $boardId, NotifyPushEvents::DeckCardUpdate, [
|
||||||
|
'boardId' => $boardId,
|
||||||
|
'cardId' => $event->getCard()->getId()
|
||||||
|
], $causingSessionToken);
|
||||||
|
|
||||||
|
// if card got moved to a diferent board, we should notify
|
||||||
|
// also sessions active on the previous board
|
||||||
|
if ($event instanceof CardUpdatedEvent && $event->getCardBefore()) {
|
||||||
|
$previousBoardId = $this->stackMapper->findBoardId($event->getCardBefore()->getStackId());
|
||||||
|
if ($boardId !== $previousBoardId) {
|
||||||
|
$this->sessionService->notifyAllSessions($this->queue, $previousBoardId, NotifyPushEvents::DeckCardUpdate, [
|
||||||
|
'boardId' => $boardId,
|
||||||
|
'cardId' => $event->getCard()->getId()
|
||||||
|
], $causingSessionToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error('Error when handling live update event', ['exception' => $e]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
lib/Listeners/ParticipantCleanupListener.php
Normal file
58
lib/Listeners/ParticipantCleanupListener.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Deck\Listeners;
|
||||||
|
|
||||||
|
use OCA\Circles\Events\CircleDestroyedEvent;
|
||||||
|
use OCA\Deck\Db\Acl;
|
||||||
|
use OCA\Deck\Db\AclMapper;
|
||||||
|
use OCA\Deck\Db\AssignmentMapper;
|
||||||
|
use OCA\Deck\Db\BoardMapper;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
use OCP\Group\Events\GroupDeletedEvent;
|
||||||
|
use OCP\User\Events\UserDeletedEvent;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|UserDeletedEvent|GroupDeletedEvent|CircleDestroyedEvent> */
|
||||||
|
class ParticipantCleanupListener implements IEventListener {
|
||||||
|
private AclMapper $aclMapper;
|
||||||
|
private AssignmentMapper $assignmentMapper;
|
||||||
|
private BoardMapper $boardMapper;
|
||||||
|
|
||||||
|
public function __construct(AclMapper $aclMapper, AssignmentMapper $assignmentMapper, BoardMapper $boardMapper) {
|
||||||
|
$this->aclMapper = $aclMapper;
|
||||||
|
$this->assignmentMapper = $assignmentMapper;
|
||||||
|
$this->boardMapper = $boardMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
if ($event instanceof UserDeletedEvent) {
|
||||||
|
$boards = $this->boardMapper->findAllByOwner($event->getUser()->getUID());
|
||||||
|
foreach ($boards as $board) {
|
||||||
|
$this->boardMapper->delete($board);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_USER, $event->getUser()->getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event instanceof GroupDeletedEvent) {
|
||||||
|
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_GROUP, $event->getGroup()->getGID());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event instanceof CircleDestroyedEvent) {
|
||||||
|
$circleId = $event->getCircle()->getSingleId();
|
||||||
|
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_CIRCLE, $circleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupByParticipant(int $type, string $participant): void {
|
||||||
|
$acls = $this->aclMapper->findByParticipant($type, $participant);
|
||||||
|
foreach ($acls as $acl) {
|
||||||
|
$this->aclMapper->delete($acl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignments = $this->assignmentMapper->findByParticipant($participant, $type);
|
||||||
|
foreach ($assignments as $assignment) {
|
||||||
|
$this->assignmentMapper->delete($assignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
lib/Listeners/ResourceAdditionalScriptsListener.php
Normal file
31
lib/Listeners/ResourceAdditionalScriptsListener.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Deck\Listeners;
|
||||||
|
|
||||||
|
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|LoadAdditionalScriptsEvent> */
|
||||||
|
class ResourceAdditionalScriptsListener implements IEventListener {
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
if (!$event instanceof LoadAdditionalScriptsEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($this->request->getPathInfo(), '/call/') === 0) {
|
||||||
|
// Talk integration has its own entrypoint which already includes collections handling
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Util::addScript('deck', 'deck-collections');
|
||||||
|
}
|
||||||
|
}
|
||||||
43
lib/Listeners/ResourceListener.php
Normal file
43
lib/Listeners/ResourceListener.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Deck\Listeners;
|
||||||
|
|
||||||
|
use OCA\Deck\Collaboration\Resources\ResourceProvider;
|
||||||
|
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
|
||||||
|
use OCA\Deck\Event\AclCreatedEvent;
|
||||||
|
use OCA\Deck\Event\AclDeletedEvent;
|
||||||
|
use OCP\Collaboration\Resources\IManager;
|
||||||
|
use OCP\Collaboration\Resources\ResourceException;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
|
||||||
|
/** @template-implements IEventListener<Event|AclDeletedEvent|AclCreatedEvent> */
|
||||||
|
class ResourceListener implements IEventListener {
|
||||||
|
|
||||||
|
/** @var IManager */
|
||||||
|
private $resourceManager;
|
||||||
|
/** @var ResourceProviderCard */
|
||||||
|
private $resourceProviderCard;
|
||||||
|
|
||||||
|
public function __construct(IManager $resourceManager, ResourceProviderCard $resourceProviderCard) {
|
||||||
|
$this->resourceManager = $resourceManager;
|
||||||
|
$this->resourceProviderCard = $resourceProviderCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
if (!$event instanceof AclDeletedEvent && !$event instanceof AclCreatedEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$boardId = $event->getAcl()->getBoardId();
|
||||||
|
|
||||||
|
$this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, $boardId, null);
|
||||||
|
$this->resourceManager->invalidateAccessCacheForResource($resource);
|
||||||
|
} catch (ResourceException $e) {
|
||||||
|
// If there is no resource we don't need to invalidate anything, but this should not happen anyways
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ use OCP\AppFramework\OCS\OCSException;
|
|||||||
use OCP\AppFramework\OCSController;
|
use OCP\AppFramework\OCSController;
|
||||||
use OCP\ILogger;
|
use OCP\ILogger;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\Util;
|
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
|
|
||||||
class ExceptionMiddleware extends Middleware {
|
class ExceptionMiddleware extends Middleware {
|
||||||
@@ -87,7 +86,7 @@ class ExceptionMiddleware extends Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($exception instanceof StatusException) {
|
if ($exception instanceof StatusException) {
|
||||||
if ($this->config->getSystemValue('loglevel', Util::WARN) === Util::DEBUG) {
|
if ($this->config->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
|
||||||
$this->logger->logException($exception);
|
$this->logger->logException($exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
lib/Migration/DeletedCircleCleanup.php
Normal file
36
lib/Migration/DeletedCircleCleanup.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Deck\Migration;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\Acl;
|
||||||
|
use OCA\Deck\Db\AclMapper;
|
||||||
|
use OCA\Deck\Service\CirclesService;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\IRepairStep;
|
||||||
|
|
||||||
|
class DeletedCircleCleanup implements IRepairStep {
|
||||||
|
private AclMapper $aclMapper;
|
||||||
|
private CirclesService $circleService;
|
||||||
|
|
||||||
|
public function __construct(AclMapper $aclMapper, CirclesService $circlesService) {
|
||||||
|
$this->aclMapper = $aclMapper;
|
||||||
|
$this->circleService = $circlesService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return 'Cleanup Deck ACL entries for circles which have been already deleted';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(IOutput $output) {
|
||||||
|
if (!$this->circleService->isCirclesEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->aclMapper->findByType(Acl::PERMISSION_TYPE_CIRCLE) as $acl) {
|
||||||
|
if ($this->circleService->getCircle($acl->getParticipant()) === null) {
|
||||||
|
$this->aclMapper->delete($acl);
|
||||||
|
$output->info('Removed circle with id ' . $acl->getParticipant());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
lib/Migration/Version10900Date202206151724222.php
Normal file
67
lib/Migration/Version10900Date202206151724222.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\DB\Types;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
class Version10900Date202206151724222 extends SimpleMigrationStep {
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if (!$schema->hasTable('deck_sessions')) {
|
||||||
|
$table = $schema->createTable('deck_sessions');
|
||||||
|
$table->addColumn('id', Types::INTEGER, [
|
||||||
|
'autoincrement' => true,
|
||||||
|
'notnull' => true,
|
||||||
|
'unsigned' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('user_id', Types::STRING, [
|
||||||
|
'notnull' => false,
|
||||||
|
'length' => 64,
|
||||||
|
]);
|
||||||
|
$table->addColumn('board_id', Types::INTEGER, [
|
||||||
|
'notnull' => false,
|
||||||
|
]);
|
||||||
|
$table->addColumn('token', Types::STRING, [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 64,
|
||||||
|
]);
|
||||||
|
$table->addColumn('last_contact', Types::INTEGER, [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 20,
|
||||||
|
'unsigned' => true,
|
||||||
|
]);
|
||||||
|
$table->setPrimaryKey(['id']);
|
||||||
|
$table->addIndex(['board_id'], 'deck_session_board_id_idx');
|
||||||
|
$table->addIndex(['token'], 'deck_session_token_idx');
|
||||||
|
$table->addIndex(['last_contact'], 'deck_session_last_contact_idx');
|
||||||
|
}
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,10 @@ class BoardSummary extends Board {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getter(string $name): mixed {
|
||||||
|
return $this->board->getter($name);
|
||||||
|
}
|
||||||
|
|
||||||
public function __call($name, $arguments) {
|
public function __call($name, $arguments) {
|
||||||
return $this->board->__call($name, $arguments);
|
return $this->board->__call($name, $arguments);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
*/
|
*/
|
||||||
namespace OCA\Deck\Model;
|
namespace OCA\Deck\Model;
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use OCA\Deck\Db\Board;
|
use OCA\Deck\Db\Board;
|
||||||
use OCA\Deck\Db\Card;
|
use OCA\Deck\Db\Card;
|
||||||
|
|
||||||
@@ -41,6 +40,14 @@ class CardDetails extends Card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(array $extras = []): array {
|
public function jsonSerialize(array $extras = []): array {
|
||||||
|
$array = parent::jsonSerialize();
|
||||||
|
$array['overdue'] = $this->getDueStatus();
|
||||||
|
|
||||||
|
unset($array['notified']);
|
||||||
|
unset($array['descriptionPrev']);
|
||||||
|
unset($array['relatedStack']);
|
||||||
|
unset($array['relatedBoard']);
|
||||||
|
|
||||||
$array = $this->card->jsonSerialize();
|
$array = $this->card->jsonSerialize();
|
||||||
unset($array['notified'], $array['descriptionPrev'], $array['relatedStack'], $array['relatedBoard']);
|
unset($array['notified'], $array['descriptionPrev'], $array['relatedStack'], $array['relatedBoard']);
|
||||||
|
|
||||||
@@ -51,30 +58,18 @@ class CardDetails extends Card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getDueStatus(): int {
|
private function getDueStatus(): int {
|
||||||
$today = new DateTime();
|
$diffDays = $this->getDaysUntilDue();
|
||||||
$today->setTime(0, 0);
|
if ($diffDays === null || $diffDays > 1) {
|
||||||
|
return static::DUEDATE_FUTURE;
|
||||||
$match_date = $this->card->getDuedate();
|
|
||||||
if (!$match_date) {
|
|
||||||
return Card::DUEDATE_FUTURE;
|
|
||||||
}
|
}
|
||||||
$match_date->setTime(0, 0);
|
|
||||||
|
|
||||||
$diff = $today->diff($match_date);
|
|
||||||
$diffDays = (int) $diff->format('%R%a'); // Extract days count in interval
|
|
||||||
|
|
||||||
|
|
||||||
if ($diffDays === 1) {
|
if ($diffDays === 1) {
|
||||||
return Card::DUEDATE_NEXT;
|
return static::DUEDATE_NEXT;
|
||||||
}
|
}
|
||||||
if ($diffDays === 0) {
|
if ($diffDays === 0) {
|
||||||
return Card::DUEDATE_NOW;
|
return static::DUEDATE_NOW;
|
||||||
}
|
|
||||||
if ($diffDays < 0) {
|
|
||||||
return Card::DUEDATE_OVERDUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card::DUEDATE_FUTURE;
|
return static::DUEDATE_OVERDUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function appendBoardDetails(&$array): void {
|
private function appendBoardDetails(&$array): void {
|
||||||
@@ -86,7 +81,11 @@ class CardDetails extends Card {
|
|||||||
$array['board'] = (new BoardSummary($this->board))->jsonSerialize();
|
$array['board'] = (new BoardSummary($this->board))->jsonSerialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __call($name, $arguments) {
|
protected function getter(string $name): mixed {
|
||||||
return $this->card->__call($name, $arguments);
|
return $this->card->getter($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call(string $methodName, array $args) {
|
||||||
|
return $this->card->__call($methodName, $args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
lib/NotifyPushEvents.php
Normal file
30
lib/NotifyPushEvents.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
class NotifyPushEvents {
|
||||||
|
public const DeckBoardUpdate = 'deck_board_update';
|
||||||
|
public const DeckCardUpdate = 'deck_card_update';
|
||||||
|
}
|
||||||
134
lib/Reference/BoardReferenceProvider.php
Normal file
134
lib/Reference/BoardReferenceProvider.php
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Deck\Reference;
|
||||||
|
|
||||||
|
use OCA\Deck\AppInfo\Application;
|
||||||
|
use OCA\Deck\NoPermissionException;
|
||||||
|
use OCA\Deck\Service\BoardService;
|
||||||
|
use OCP\Collaboration\Reference\IReference;
|
||||||
|
use OCP\Collaboration\Reference\IReferenceProvider;
|
||||||
|
use OCP\Collaboration\Reference\Reference;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
|
class BoardReferenceProvider implements IReferenceProvider {
|
||||||
|
private IURLGenerator $urlGenerator;
|
||||||
|
private BoardService $boardService;
|
||||||
|
private ?string $userId;
|
||||||
|
private IL10N $l10n;
|
||||||
|
|
||||||
|
public function __construct(BoardService $boardService,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
IL10N $l10n,
|
||||||
|
?string $userId) {
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->boardService = $boardService;
|
||||||
|
$this->userId = $userId;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function matchReference(string $referenceText): bool {
|
||||||
|
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
||||||
|
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
||||||
|
|
||||||
|
// link example: https://nextcloud.local/index.php/apps/deck/#/board/2
|
||||||
|
$noIndexMatch = preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/[0-9]+$/', $referenceText) === 1;
|
||||||
|
$indexMatch = preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/[0-9]+$/', $referenceText) === 1;
|
||||||
|
|
||||||
|
return $noIndexMatch || $indexMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function resolveReference(string $referenceText): ?IReference {
|
||||||
|
if ($this->matchReference($referenceText)) {
|
||||||
|
$boardId = $this->getBoardId($referenceText);
|
||||||
|
if ($boardId !== null) {
|
||||||
|
try {
|
||||||
|
$board = $this->boardService->find($boardId)->jsonSerialize();
|
||||||
|
} catch (NoPermissionException $e) {
|
||||||
|
// Skip throwing if user has no permissions
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$board = $this->sanitizeSerializedBoard($board);
|
||||||
|
/** @var IReference $reference */
|
||||||
|
$reference = new Reference($referenceText);
|
||||||
|
$reference->setTitle($this->l10n->t('Deck board') . ': ' . $board['title']);
|
||||||
|
$ownerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
|
||||||
|
$reference->setDescription($this->l10n->t('Owned by %1$s', [$ownerDisplayName]));
|
||||||
|
$imageUrl = $this->urlGenerator->getAbsoluteURL(
|
||||||
|
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
||||||
|
);
|
||||||
|
$reference->setImageUrl($imageUrl);
|
||||||
|
$reference->setRichObject(Application::APP_ID . '-board', [
|
||||||
|
'id' => $boardId,
|
||||||
|
'board' => $board,
|
||||||
|
]);
|
||||||
|
return $reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeSerializedBoard(array $board): array {
|
||||||
|
unset($board['labels']);
|
||||||
|
$board['owner'] = $board['owner']->jsonSerialize();
|
||||||
|
unset($board['acl']);
|
||||||
|
unset($board['users']);
|
||||||
|
|
||||||
|
return $board;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBoardId(string $url): ?int {
|
||||||
|
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
||||||
|
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
||||||
|
|
||||||
|
preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/([0-9]+)$/', $url, $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/([0-9]+)$/', $url, $matches);
|
||||||
|
}
|
||||||
|
if ($matches && count($matches) > 1) {
|
||||||
|
return (int) $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCachePrefix(string $referenceId): string {
|
||||||
|
$boardId = $this->getBoardId($referenceId);
|
||||||
|
if ($boardId !== null) {
|
||||||
|
return (string) $boardId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $referenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheKey(string $referenceId): ?string {
|
||||||
|
return $this->userId ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,31 +27,77 @@ use OCA\Deck\Db\Assignment;
|
|||||||
use OCA\Deck\Db\Attachment;
|
use OCA\Deck\Db\Attachment;
|
||||||
use OCA\Deck\Db\Label;
|
use OCA\Deck\Db\Label;
|
||||||
use OCA\Deck\Model\CardDetails;
|
use OCA\Deck\Model\CardDetails;
|
||||||
|
use OCA\Deck\NoPermissionException;
|
||||||
use OCA\Deck\Service\BoardService;
|
use OCA\Deck\Service\BoardService;
|
||||||
use OCA\Deck\Service\CardService;
|
use OCA\Deck\Service\CardService;
|
||||||
use OCA\Deck\Service\StackService;
|
use OCA\Deck\Service\StackService;
|
||||||
|
use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
|
||||||
use OCP\Collaboration\Reference\IReference;
|
use OCP\Collaboration\Reference\IReference;
|
||||||
use OCP\Collaboration\Reference\IReferenceProvider;
|
use OCP\Collaboration\Reference\ISearchableReferenceProvider;
|
||||||
use OCP\Collaboration\Reference\Reference;
|
use OCP\Collaboration\Reference\Reference;
|
||||||
|
use OCP\IL10N;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
class CardReferenceProvider implements IReferenceProvider {
|
class CardReferenceProvider extends ADiscoverableReferenceProvider implements ISearchableReferenceProvider {
|
||||||
private CardService $cardService;
|
private CardService $cardService;
|
||||||
private IURLGenerator $urlGenerator;
|
private IURLGenerator $urlGenerator;
|
||||||
private BoardService $boardService;
|
private BoardService $boardService;
|
||||||
private StackService $stackService;
|
private StackService $stackService;
|
||||||
private ?string $userId;
|
private ?string $userId;
|
||||||
|
private IL10N $l10n;
|
||||||
|
|
||||||
public function __construct(CardService $cardService,
|
public function __construct(CardService $cardService,
|
||||||
BoardService $boardService,
|
BoardService $boardService,
|
||||||
StackService $stackService,
|
StackService $stackService,
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
|
IL10N $l10n,
|
||||||
?string $userId) {
|
?string $userId) {
|
||||||
$this->cardService = $cardService;
|
$this->cardService = $cardService;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->boardService = $boardService;
|
$this->boardService = $boardService;
|
||||||
$this->stackService = $stackService;
|
$this->stackService = $stackService;
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getId(): string {
|
||||||
|
return Application::APP_ID . '-ref-cards';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getTitle(): string {
|
||||||
|
return $this->l10n->t('Deck boards, cards and comments');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getOrder(): int {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getIconUrl(): string {
|
||||||
|
return $this->urlGenerator->getAbsoluteURL(
|
||||||
|
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getSupportedSearchProviderIds(): array {
|
||||||
|
return [
|
||||||
|
'search-deck-card-board',
|
||||||
|
'search-deck-comment',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,14 +122,20 @@ class CardReferenceProvider implements IReferenceProvider {
|
|||||||
$ids = $this->getBoardCardId($referenceText);
|
$ids = $this->getBoardCardId($referenceText);
|
||||||
if ($ids !== null) {
|
if ($ids !== null) {
|
||||||
[$boardId, $cardId] = $ids;
|
[$boardId, $cardId] = $ids;
|
||||||
|
try {
|
||||||
$card = $this->cardService->find((int) $cardId)->jsonSerialize();
|
$card = $this->cardService->find((int) $cardId)->jsonSerialize();
|
||||||
$board = $this->boardService->find((int) $boardId)->jsonSerialize();
|
$board = $this->boardService->find((int) $boardId)->jsonSerialize();
|
||||||
$stack = $this->stackService->find((int) $card['stackId'])->jsonSerialize();
|
$stack = $this->stackService->find((int) $card['stackId'])->jsonSerialize();
|
||||||
|
} catch (NoPermissionException $e) {
|
||||||
|
// Skip throwing if user has no permissions
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$card = $this->sanitizeSerializedCard($card);
|
$card = $this->sanitizeSerializedCard($card);
|
||||||
$board = $this->sanitizeSerializedBoard($board);
|
$board = $this->sanitizeSerializedBoard($board);
|
||||||
$stack = $this->sanitizeSerializedStack($stack);
|
$stack = $this->sanitizeSerializedStack($stack);
|
||||||
|
/** @var IReference $reference */
|
||||||
$reference = new Reference($referenceText);
|
$reference = new Reference($referenceText);
|
||||||
$reference->setRichObject(Application::APP_ID . '-card', [
|
$reference->setRichObject(Application::APP_ID . '-card', [
|
||||||
'id' => $boardId . '/' . $cardId,
|
'id' => $boardId . '/' . $cardId,
|
||||||
|
|||||||
197
lib/Reference/CommentReferenceProvider.php
Normal file
197
lib/Reference/CommentReferenceProvider.php
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Deck\Reference;
|
||||||
|
|
||||||
|
use OCA\Deck\AppInfo\Application;
|
||||||
|
use OCA\Deck\Db\Assignment;
|
||||||
|
use OCA\Deck\Db\Attachment;
|
||||||
|
use OCA\Deck\Db\Label;
|
||||||
|
use OCA\Deck\Model\CardDetails;
|
||||||
|
use OCA\Deck\NoPermissionException;
|
||||||
|
use OCA\Deck\Service\BoardService;
|
||||||
|
use OCA\Deck\Service\CardService;
|
||||||
|
use OCA\Deck\Service\CommentService;
|
||||||
|
use OCA\Deck\Service\StackService;
|
||||||
|
use OCP\Collaboration\Reference\IReference;
|
||||||
|
use OCP\Collaboration\Reference\IReferenceProvider;
|
||||||
|
use OCP\Collaboration\Reference\Reference;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
|
class CommentReferenceProvider implements IReferenceProvider {
|
||||||
|
private CardService $cardService;
|
||||||
|
private IURLGenerator $urlGenerator;
|
||||||
|
private BoardService $boardService;
|
||||||
|
private StackService $stackService;
|
||||||
|
private ?string $userId;
|
||||||
|
private IL10N $l10n;
|
||||||
|
private CommentService $commentService;
|
||||||
|
|
||||||
|
public function __construct(CardService $cardService,
|
||||||
|
BoardService $boardService,
|
||||||
|
StackService $stackService,
|
||||||
|
CommentService $commentService,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
IL10N $l10n,
|
||||||
|
?string $userId) {
|
||||||
|
$this->cardService = $cardService;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->boardService = $boardService;
|
||||||
|
$this->stackService = $stackService;
|
||||||
|
$this->userId = $userId;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->commentService = $commentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function matchReference(string $referenceText): bool {
|
||||||
|
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
||||||
|
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
||||||
|
|
||||||
|
// link example: https://nextcloud.local/index.php/apps/deck/#/board/2/card/11/comments/501
|
||||||
|
$noIndexMatch = preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+\/comments\/\d+$/', $referenceText) === 1;
|
||||||
|
$indexMatch = preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+\/comments\/\d+$/', $referenceText) === 1;
|
||||||
|
|
||||||
|
return $noIndexMatch || $indexMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function resolveReference(string $referenceText): ?IReference {
|
||||||
|
if ($this->matchReference($referenceText)) {
|
||||||
|
$ids = $this->getIds($referenceText);
|
||||||
|
if ($ids !== null) {
|
||||||
|
[$boardId, $cardId, $commentId] = $ids;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$card = $this->cardService->find($cardId)->jsonSerialize();
|
||||||
|
$board = $this->boardService->find($boardId)->jsonSerialize();
|
||||||
|
$stack = $this->stackService->find((int) $card['stackId'])->jsonSerialize();
|
||||||
|
} catch (NoPermissionException $e) {
|
||||||
|
// Skip throwing if user has no permissions
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$card = $this->sanitizeSerializedCard($card);
|
||||||
|
$board = $this->sanitizeSerializedBoard($board);
|
||||||
|
$stack = $this->sanitizeSerializedStack($stack);
|
||||||
|
|
||||||
|
$comment = $this->commentService->getFormatted($cardId, $commentId);
|
||||||
|
|
||||||
|
/** @var IReference $reference */
|
||||||
|
$reference = new Reference($referenceText);
|
||||||
|
$reference->setTitle($comment['message']);
|
||||||
|
$boardOwnerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
|
||||||
|
$reference->setDescription(
|
||||||
|
$this->l10n->t('From %1$s, in %2$s/%3$s, owned by %4$s', [
|
||||||
|
$comment['actorDisplayName'],
|
||||||
|
$board['title'],
|
||||||
|
$stack['title'],
|
||||||
|
$boardOwnerDisplayName
|
||||||
|
])
|
||||||
|
);
|
||||||
|
$imageUrl = $this->urlGenerator->getAbsoluteURL(
|
||||||
|
$this->urlGenerator->imagePath('core', 'actions/comment.svg')
|
||||||
|
);
|
||||||
|
$reference->setImageUrl($imageUrl);
|
||||||
|
$reference->setRichObject(Application::APP_ID . '-comment', [
|
||||||
|
'id' => $ids,
|
||||||
|
'board' => $board,
|
||||||
|
'card' => $card,
|
||||||
|
'stack' => $stack,
|
||||||
|
'comment' => $comment,
|
||||||
|
]);
|
||||||
|
return $reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeSerializedStack(array $stack): array {
|
||||||
|
$stack['cards'] = array_map(function (CardDetails $cardDetails) {
|
||||||
|
$result = $cardDetails->jsonSerialize();
|
||||||
|
unset($result['assignedUsers']);
|
||||||
|
return $result;
|
||||||
|
}, $stack['cards']);
|
||||||
|
|
||||||
|
return $stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeSerializedBoard(array $board): array {
|
||||||
|
unset($board['labels']);
|
||||||
|
$board['owner'] = $board['owner']->jsonSerialize();
|
||||||
|
unset($board['acl']);
|
||||||
|
unset($board['users']);
|
||||||
|
|
||||||
|
return $board;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeSerializedCard(array $card): array {
|
||||||
|
$card['labels'] = array_map(function (Label $label) {
|
||||||
|
return $label->jsonSerialize();
|
||||||
|
}, $card['labels']);
|
||||||
|
$card['assignedUsers'] = array_map(function (Assignment $assignment) {
|
||||||
|
$result = $assignment->jsonSerialize();
|
||||||
|
$result['participant'] = $result['participant']->jsonSerialize();
|
||||||
|
return $result;
|
||||||
|
}, $card['assignedUsers']);
|
||||||
|
$card['owner'] = $card['owner']->jsonSerialize();
|
||||||
|
unset($card['relatedStack']);
|
||||||
|
unset($card['relatedBoard']);
|
||||||
|
$card['attachments'] = array_map(function (Attachment $attachment) {
|
||||||
|
return $attachment->jsonSerialize();
|
||||||
|
}, $card['attachments']);
|
||||||
|
|
||||||
|
return $card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getIds(string $url): ?array {
|
||||||
|
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
||||||
|
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
||||||
|
|
||||||
|
preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)\/comments\/(\d+)$/', $url, $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)\/comments\/(\d+)$/', $url, $matches);
|
||||||
|
}
|
||||||
|
if ($matches && count($matches) > 3) {
|
||||||
|
return [
|
||||||
|
(int) $matches[1],
|
||||||
|
(int) $matches[2],
|
||||||
|
(int) $matches[3],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCachePrefix(string $referenceId): string {
|
||||||
|
return $referenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheKey(string $referenceId): ?string {
|
||||||
|
return $this->userId ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user