Compare commits
1065 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c5aa15c6a | |||
| 14b6fba1f7 | |||
| 1e77bf8d78 | |||
| 6790c08a3a | |||
| a0da3ea2e0 | |||
| db1a703f23 | |||
| 2602181851 | |||
| 6b04f7b80f | |||
| 2fe89eaed7 | |||
| f6914b5037 | |||
| 08368a0125 | |||
| 864135c02c | |||
| 8a315566bd | |||
| 9987f4f110 | |||
| 20f79caf71 | |||
| 5356f900fb | |||
| bd9576d7c7 | |||
| e2d490abf0 | |||
| 1c935bc8da | |||
| dadabb9a2e | |||
| db71d044ae | |||
| e0adb51a38 | |||
| af3ac9c9ac | |||
| 9ad2b2fce4 | |||
| 758f343513 | |||
| daebc05171 | |||
| ac9c0ea10d | |||
| 9e8e1fde93 | |||
| ca969ded18 | |||
| b3a4a161fe | |||
| 63869de5d3 | |||
| fd254fb3a7 | |||
| 09de3caf9e | |||
| 6a5aaa8f5e | |||
| b62989e6dc | |||
| 2676453f1f | |||
| f5bd253436 | |||
| 13a7f9835a | |||
| 79505af588 | |||
| f1d7f21022 | |||
| 8e351e5650 | |||
| f049b87dbb | |||
| 2a701212af | |||
| 7fcd007912 | |||
| a395e4e69e | |||
| 12b7c0bfcd | |||
| 6b4f1a6966 | |||
| e8c2589612 | |||
| 4fa3ab042e | |||
| 694f3e0cea | |||
| 4cc06f1333 | |||
| b7f2f9307b | |||
| 9a65d20282 | |||
| 4e334dd42c | |||
| c6c726c240 | |||
| be94d0a8f0 | |||
| 87814a3670 | |||
| e9d0dab646 | |||
| 6e0b0c50a8 | |||
| ef2dd3e41a | |||
| 10b7463468 | |||
| 0bd91e2b16 | |||
| 46b9c8ebb5 | |||
| 646e79dd6c | |||
| d0a9a5dd47 | |||
| b417f92e8e | |||
| c42a88e410 | |||
| 15788b1605 | |||
| 9eea27efcd | |||
| 10081802f6 | |||
| c7a9b7de36 | |||
| 474fae352d | |||
| 355efa0a7b | |||
| a048c9357a | |||
| d6c23cb304 | |||
| 33d472c72a | |||
| a31af5ccbd | |||
| 71090ff59d | |||
| a0d965242a | |||
| db32aeb99d | |||
| 76766f7df3 | |||
| 8d19ed5b46 | |||
| a55456d5e6 | |||
| 3053ab2bb3 | |||
| e9fec7b448 | |||
| e729f7bf6a | |||
| 262c8aa73d | |||
| 8aaa3dd2c2 | |||
| 8e067112e2 | |||
| 4b9d1ca97b | |||
| f36ba8bb01 | |||
| 6cdc63ff69 | |||
| 6e892c0db6 | |||
| b48daab731 | |||
| 07e7f79a8e | |||
| fd57cbca55 | |||
| e7a1ef793a | |||
| f615102792 | |||
| f11cb2e22f | |||
| 830a8b25c1 | |||
| 5d03800141 | |||
| 618d3bf607 | |||
| 973f99977d | |||
| 9ec55abffc | |||
| 7df5e37750 | |||
| a19f3536bc | |||
| 613b3907da | |||
| 506ca2b8be | |||
| a8b028eb1a | |||
| 8768cede11 | |||
| 35b3986ace | |||
| 1438a33f2c | |||
| 0f3626032b | |||
| 7057c8e7e4 | |||
| 80bac36d56 | |||
| dc2f716dc2 | |||
| a6d184bda4 | |||
| ffec001997 | |||
| 0676ea8012 | |||
| efa0558263 | |||
| 45d9d9e003 | |||
| 41337a70ce | |||
| d72927da67 | |||
| e801494d44 | |||
| 422ba52ecb | |||
| 33f360b58c | |||
| 3846660202 | |||
| 026334e182 | |||
| 720f57e524 | |||
| 7cd92034d4 | |||
| 25b78ac569 | |||
| a4a1214231 | |||
| 35a373f425 | |||
| 660f547689 | |||
| 386b827e9b | |||
| 49eaef63c2 | |||
| 05fc83f18e | |||
| 60b63968b5 | |||
| 220bf00bf6 | |||
| 32231e9d8c | |||
| 4730181015 | |||
| 0731981bbf | |||
| 51af31dd6b | |||
| e641d6f1bd | |||
| e990a43709 | |||
| 2bfaa94804 | |||
| 8264ee569a | |||
| c9b168f505 | |||
| 4d43c27e67 | |||
| 1f4b441305 | |||
| 54f34a4a7f | |||
| 9275149e60 | |||
| 5b51413a7f | |||
| d8a903b1e9 | |||
| 6df574e8fb | |||
| a02107dcb6 | |||
| cccbbf7c19 | |||
| fe29c3b304 | |||
| 27618de3da | |||
| 3338b7a665 | |||
| 8df76b56fc | |||
| f8569fc038 | |||
| 1484fd4581 | |||
| 85dfb36cb1 | |||
| 1b2f1506cb | |||
| 985d2c9447 | |||
| 56422f10ea | |||
| 2e9f442499 | |||
| 068b6b8a0b | |||
| bceb827958 | |||
| 34e74f0ac4 | |||
| 3c0cde49c2 | |||
| c941baf6b3 | |||
| ad2de8e9a0 | |||
| e76116b03b | |||
| def368a74c | |||
| 7e10851980 | |||
| 042e1492d3 | |||
| ccfd570d33 | |||
| 4ef42c2037 | |||
| d8dee2b90b | |||
| 4ae44a4ed3 | |||
| cfc8a3fd84 | |||
| 9b430e1312 | |||
| 9553a532bb | |||
| ad317fe375 | |||
| 7ca6a377bc | |||
| 5c7948f74a | |||
| 26e8f27cde | |||
| 8acf32f7af | |||
| 8a0800fa6e | |||
| 22a4e780bc | |||
| 656f4fd839 | |||
| b485099115 | |||
| 3200eedc4c | |||
| 27a4bb4bf6 | |||
| c0d4330360 | |||
| 10bd6542e9 | |||
| 2b0ad3a9cf | |||
| a9e5528125 | |||
| 8e19aaa4d1 | |||
| 11a5e996ba | |||
| b0f3866b70 | |||
| c1894ff57a | |||
| bf9169dbcd | |||
| ece24cd58b | |||
| 9010fbf700 | |||
| 30d1e5d694 | |||
| a76d24e297 | |||
| 0f333de6fb | |||
| 4b217a7562 | |||
| 0b856f9e1a | |||
| f4abee5209 | |||
| fe5bc6810c | |||
| 16025e3af0 | |||
| a9e44bb57a | |||
| cc436de229 | |||
| 747ca0b29c | |||
| 489c376953 | |||
| c9bb2e940d | |||
| 88a0dd6302 | |||
| 5d9b6f6a8e | |||
| bd1f2cc4f2 | |||
| a7a539794d | |||
| 126faf483d | |||
| a0d1917f86 | |||
| 95fa13d851 | |||
| 28c01a0a4a | |||
| 2212b2a534 | |||
| f60fcbdb7c | |||
| 3889b1d44a | |||
| 6bab6aa3e0 | |||
| fdfb80c03a | |||
| ef28c8881f | |||
| 4cc3ca8508 | |||
| bcd4e21177 | |||
| 3ca2b6dec0 | |||
| f608e8c2e0 | |||
| 27440ee339 | |||
| 5f0115e2cf | |||
| 2d6a6d1436 | |||
| d08dbda106 | |||
| 08dd86e4f1 | |||
| 6576c782a4 | |||
| 34fbd0d6c3 | |||
| 8fb398bfb0 | |||
| eb58a0201f | |||
| ce8febce2d | |||
| 0948d1b7a2 | |||
| 5504153e54 | |||
| 5dcb67b11a | |||
| 9831c5eee6 | |||
| a643e74bda | |||
| b3d8e239d1 | |||
| 3c414b3534 | |||
| 10ccd41ef6 | |||
| d6d35013bb | |||
| f40a2bfdf4 | |||
| d6b214e410 | |||
| 34d1600736 | |||
| 719f828de9 | |||
| 27ffd89076 | |||
| 54d9df2681 | |||
| 0172fb7aad | |||
| 86df25ebd3 | |||
| 216c6f658d | |||
| 75222d5a77 | |||
| 560f44dafb | |||
| 97e6cb2716 | |||
| 3940335da7 | |||
| a0f4a82e51 | |||
| e2a54118df | |||
| 8682e1aef8 | |||
| 4267c1fe07 | |||
| 4f46269201 | |||
| 1671bd4ce0 | |||
| dfcb53c13d | |||
| 8b393e150e | |||
| e2b9470862 | |||
| b523cedd76 | |||
| 177f0220ae | |||
| 0855e100b7 | |||
| b78a1df1a7 | |||
| 0d5b7d4043 | |||
| 8c37607010 | |||
| abca34522d | |||
| 64fd1b9fde | |||
| 1fa8fc71c5 | |||
| 62b4de04b1 | |||
| 676b162e37 | |||
| d8c6d9824f | |||
| 9c580e75c1 | |||
| 35d62199e2 | |||
| 102152a3b3 | |||
| 28be5544ee | |||
| c6171c6148 | |||
| 62b6b6e0ad | |||
| 193a1664fd | |||
| 5244df8c6d | |||
| dc3ed03510 | |||
| 463088fbc8 | |||
| 916e3185d8 | |||
| b2a0f2649f | |||
| 3d7b5a1a01 | |||
| dd97971609 | |||
| 4261a91f85 | |||
| a0ceaff687 | |||
| ab1528c2b0 | |||
| 9d26cb25cf | |||
| baf956b121 | |||
| 1619338549 | |||
| b2fba45572 | |||
| b67980b52d | |||
| 2989732597 | |||
| 38184ca9fd | |||
| d85782706d | |||
| afa23942a5 | |||
| 021cfbf104 | |||
| efcbe865a9 | |||
| e98e0306a5 | |||
| 76b54fa2e7 | |||
| 68fa396eed | |||
| 1b26d3e542 | |||
| 8c43734cca | |||
| 2f8cfbb76d | |||
| c4c8b7677d | |||
| 0c0600af09 | |||
| 0fad25b78c | |||
| 20f6ab1040 | |||
| 42a51146d1 | |||
| c743af30d6 | |||
| 877e1bb55b | |||
| b7b7380d6b | |||
| 25d7d6965d | |||
| 1fe16ca6f5 | |||
| 0f3fe7be2c | |||
| d32fd77d22 | |||
| 0cb869321d | |||
| d41f36dad5 | |||
| 67559c34e8 | |||
| 2addd34277 | |||
| e7611e308a | |||
| b73faad2a1 | |||
| cdd0e4d3ad | |||
| f3d9c1f005 | |||
| 174bc54911 | |||
| 2a9c872d7a | |||
| 8af38e7dcf | |||
| 5c58a21ef7 | |||
| f632092648 | |||
| 4d26a8f6dc | |||
| 139e45f360 | |||
| 2ab1c4ded4 | |||
| 623776f5e5 | |||
| b25f545c99 | |||
| 42bb1391c5 | |||
| 8e74b232ed | |||
| 436cb021f7 | |||
| d37e870515 | |||
| 9f332e53ab | |||
| 726a5101ac | |||
| 7c98898b38 | |||
| caccc2caf1 | |||
| 0a34c55937 | |||
| 8a815060a0 | |||
| bbf417ec85 | |||
| 15d6dd30b6 | |||
| 806a067bfb | |||
| b307d32ed1 | |||
| b7c8bce168 | |||
| d07742b80d | |||
| 00e9f5f024 | |||
| 05f40475cc | |||
| 900cd128fb | |||
| 2dd531fc5f | |||
| 56528c6ab1 | |||
| 9f1bba8597 | |||
| 0485022d8b | |||
| a8031dd301 | |||
| c3823a2892 | |||
| 426f0a714e | |||
| 79e335dd55 | |||
| dc2bec7c98 | |||
| 36b0bee728 | |||
| f6f1cd5c60 | |||
| 4c8ef504f8 | |||
| 6a04dbda58 | |||
| da23aa3f0f | |||
| e7f3790ee6 | |||
| 269a6be41e | |||
| 2a501eb3ff | |||
| ed8740205b | |||
| a88f6309c6 | |||
| 356e75eeed | |||
| 77bae5e60a | |||
| 084e62864b | |||
| 1c6b658d28 | |||
| ecc06866dd | |||
| 4cac0e893c | |||
| 3259cb64a9 | |||
| 7d7b7fcb58 | |||
| 4cd1d56888 | |||
| f8a4966544 | |||
| 529623e749 | |||
| a61d88f473 | |||
| 0660f1fcc7 | |||
| 15e097ae6c | |||
| 6cbc8a08d5 | |||
| 54cdbea127 | |||
| 3273c51485 | |||
| f943a656bd | |||
| d814c457c1 | |||
| fbce2b7156 | |||
| 61382f525b | |||
| 411534bcaf | |||
| 711ce573c1 | |||
| e400b70e0b | |||
| bfda90d9d4 | |||
| 6c3faa96d2 | |||
| 5c94ae2c25 | |||
| 4ef8ff5660 | |||
| 21968d0595 | |||
| 733f10cc16 | |||
| 8f3062577c | |||
| 54aef17e73 | |||
| a2c2389dd4 | |||
| fe78fe9ba8 | |||
| 1a4505f64b | |||
| 20cf58396f | |||
| e19a32ccfc | |||
| 3099781f3c | |||
| 26aa01e161 | |||
| c3dc9b7e84 | |||
| 04e6470b2f | |||
| 555900a0d2 | |||
| 0e57eabfa0 | |||
| d171d1aff8 | |||
| d0db369464 | |||
| 00b838ca6c | |||
| 47e8313218 | |||
| bf3de19f62 | |||
| a575ef9145 | |||
| 153a764e3c | |||
| 803b54186b | |||
| 96d4233545 | |||
| c34abfa16b | |||
| 18d484dd30 | |||
| 61681c9688 | |||
| a5995d2f74 | |||
| 1a607a5153 | |||
| 73411eae9f | |||
| 522d050731 | |||
| 7c207d002a | |||
| 3ce9f0013a | |||
| a7de52e9d9 | |||
| fc707b6f5b | |||
| c5273404ee | |||
| 166bbadef1 | |||
| bb42b520ef | |||
| 4dc91420cf | |||
| 70fea997af | |||
| 57f901e972 | |||
| e298edb243 | |||
| 27cadc5dcd | |||
| f24bb46b06 | |||
| c6d664a817 | |||
| cbc82a9449 | |||
| b8be6ed15e | |||
| 3a995a1656 | |||
| 7b4d5aa8a9 | |||
| 34fa5dae37 | |||
| d5aaf66479 | |||
| 59d0b4402e | |||
| 6a9acba384 | |||
| 377a955f86 | |||
| 6b18986a87 | |||
| a9f1ab83e6 | |||
| 2647e61321 | |||
| 4294347e6a | |||
| 7a31a482f4 | |||
| 8ce5346491 | |||
| 63479ba72e | |||
| 54166dfbcd | |||
| 140e21b6e8 | |||
| 75c5f0662f | |||
| 9c0b20ca29 | |||
| 7711585ab7 | |||
| f4853072ef | |||
| 07a4b2577a | |||
| 8d68e9e323 | |||
| 4011d97a2b | |||
| bd8dc10221 | |||
| 0708ffcf77 | |||
| 29a1ac7799 | |||
| 86cf56df14 | |||
| 16690a2e04 | |||
| cd975f8036 | |||
| 8a58c4cef1 | |||
| 323fd3c538 | |||
| 91a94469c1 | |||
| 1ab9ef8224 | |||
| 8183f23f34 | |||
| 0f5cc5249c | |||
| 3fc86e639e | |||
| bb34e90866 | |||
| 41d2aaa32c | |||
| b8cc88ce86 | |||
| dc4c74a716 | |||
| 701343c1c8 | |||
| 6021df4afc | |||
| 54411b724e | |||
| 45108fcb40 | |||
| 58bf308c40 | |||
| f09d1d6dbf | |||
| d248f94835 | |||
| 3cb1d4f579 | |||
| 9c3bfc7166 | |||
| 0c5978447f | |||
| ba25fe84e0 | |||
| c934786dc4 | |||
| 6f02458356 | |||
| f1cee77582 | |||
| 50f2900b4b | |||
| 82f72f9282 | |||
| 3a8d3cdf90 | |||
| bda30e590a | |||
| fa59e681dc | |||
| c5b9fcaf90 | |||
| f6f82d3fa8 | |||
| ce2906782c | |||
| 387eeeba7f | |||
| d932653a29 | |||
| 0b7ad2c22b | |||
| 8918b0174f | |||
| c7157ef586 | |||
| d513d56729 | |||
| 4cd718efae | |||
| d4f3bfb4cc | |||
| 1918cda75f | |||
| f65e06fd6c | |||
| 63a142c47d | |||
| 2e99756cb3 | |||
| bac125e201 | |||
| 5504bb4430 | |||
| 167af81d26 | |||
| 0ecc8ed904 | |||
| ec1ff53a64 | |||
| fcd637b938 | |||
| cbc4134f33 | |||
| 0263bc2e35 | |||
| 42b905c070 | |||
| 1a4b07cae4 | |||
| b07f656432 | |||
| 2b090ffa6f | |||
| dd7548ac69 | |||
| c1018bbbe4 | |||
| 9a39b6a622 | |||
| c2e3fce9c6 | |||
| 9a7ac74eee | |||
| 39d041df55 | |||
| 5ca3a19f9f | |||
| 32bf44195b | |||
| 31e399cccd | |||
| 7ee1f827bf | |||
| 279b9ad577 | |||
| 29c8844306 | |||
| 36183cb2f1 | |||
| 5fcff2f7c5 | |||
| 5931eaadec | |||
| 91247f7d30 | |||
| 4e8aae50a8 | |||
| 1b47b99456 | |||
| d2042c5af4 | |||
| 0729097912 | |||
| 512240e41f | |||
| 7b755993a3 | |||
| bd0b4c52ec | |||
| 8ba431b9a6 | |||
| e05093d42c | |||
| ad01cbfb78 | |||
| 48b746443d | |||
| e0737b7816 | |||
| 2f671bbea7 | |||
| ecd05d7cad | |||
| 1db1d2ce9d | |||
| f4ef37285b | |||
| 1b8ae61b7c | |||
| 372307a762 | |||
| 1d0971b72e | |||
| 34daee58e7 | |||
| 4878a1e40d | |||
| f48f8f94c7 | |||
| f42a6e0506 | |||
| f29395e89d | |||
| a7c0bb030c | |||
| 55b416c448 | |||
| 957edb8dde | |||
| 56c7864fc7 | |||
| 860e35501e | |||
| 999cc69568 | |||
| 7d6cb4eb6b | |||
| bb70d1096a | |||
| d810883587 | |||
| 654c0b2b1b | |||
| cf2af05def | |||
| c01f99904d | |||
| 3f03cc7733 | |||
| 59994bc7df | |||
| 6d856ceaf9 | |||
| 0b753f052a | |||
| b5f407e02d | |||
| 1025cee109 | |||
| 6d926c8b28 | |||
| a0b0b1904e | |||
| c2dbe61088 | |||
| 5abc146b37 | |||
| 3244d910ee | |||
| 8f0a36cddf | |||
| 71e46474df | |||
| 85756b9348 | |||
| 50d948daf4 | |||
| fc9f12d72d | |||
| 11bb942483 | |||
| c4bac0131e | |||
| 4d5e74007a | |||
| e7039cc2f5 | |||
| 760da64b17 | |||
| e1f1a13263 | |||
| 300631a4e9 | |||
| a48a298a30 | |||
| 35a7d5707c | |||
| 81a6adab41 | |||
| 905c7786fb | |||
| 930dea7129 | |||
| 7355173156 | |||
| 48e93a1307 | |||
| 0c66dcfa61 | |||
| 6ed2c130fb | |||
| 0d106a4b02 | |||
| 8d572cc80d | |||
| a33f2248e5 | |||
| 4cbeb680b0 | |||
| 62a637fd42 | |||
| c8d2c5ed33 | |||
| 18b4c1e8ca | |||
| 313e98437d | |||
| f9fdc641f3 | |||
| 97f3fbc505 | |||
| ae06f2312c | |||
| c37b4e3f75 | |||
| e66f58135d | |||
| a584e2e899 | |||
| 2bca7f372c | |||
| 94a74b8eba | |||
| 639ff3dbda | |||
| fbeae1cc7b | |||
| 558ec35c89 | |||
| 7c63605886 | |||
| 7fefb77f5c | |||
| ed0f6033af | |||
| a1a245e882 | |||
| 85a070b242 | |||
| a4a3047894 | |||
| 4dea089ed3 | |||
| aebcbc4b04 | |||
| fb3ac4dcaf | |||
| c577bd1bdb | |||
| d7590ab90d | |||
| 57e30495cd | |||
| 3df123dc45 | |||
| d88324e5e8 | |||
| 6bfcf97c7f | |||
| 18aa047dd4 | |||
| fff47cdecb | |||
| 9ec4527cb7 | |||
| 55ad6c1fa2 | |||
| 89ba8c6c6d | |||
| 1def358729 | |||
| 8280bb5bf1 | |||
| c809dc1827 | |||
| d5053d00c7 | |||
| b8068fc11d | |||
| fb65d1e0ae | |||
| 2754e49925 | |||
| e031965348 | |||
| 25235a770f | |||
| 7a1169910a | |||
| b0c50ef6e9 | |||
| 271f86cff3 | |||
| 29b04742db | |||
| 9e5994a210 | |||
| bb9f14115c | |||
| f852c82bf9 | |||
| 40cf574df9 | |||
| ad3efd6efb | |||
| bf10181e49 | |||
| 291f85f0b8 | |||
| 53bb2862f0 | |||
| c730e2269c | |||
| 11109c6dfa | |||
| 51fd053d6c | |||
| ee6f4febbd | |||
| 07d32779db | |||
| 516588ff91 | |||
| 951147c6e9 | |||
| af87bd2f60 | |||
| a557d34481 | |||
| 10ce9c6878 | |||
| 48b271a216 | |||
| e7920203ba | |||
| f1f755740d | |||
| 5ac52d2e09 | |||
| 6b814e5002 | |||
| ff3e510b76 | |||
| 45815e5c09 | |||
| 5959245417 | |||
| 64fb4c7736 | |||
| d092802a2d | |||
| 5786c6c058 | |||
| 05788a814e | |||
| d638060520 | |||
| 7c225bd706 | |||
| 94db7053d2 | |||
| 167a8d72cf | |||
| a9ccac2f20 | |||
| 84a849ec8a | |||
| 159ff6dd97 | |||
| 00379ebffa | |||
| 540168fc43 | |||
| 6ee98f3567 | |||
| 861daee4d8 | |||
| 3f5ce65879 | |||
| c08d2cbda7 | |||
| 19e5cb37a6 | |||
| eeefef4843 | |||
| 67c8dc001d | |||
| c9bf543cd4 | |||
| f1031e1e24 | |||
| 62826ae018 | |||
| 9072d82cd6 | |||
| c7322c7efd | |||
| 27a6f8020b | |||
| 9e62b2f25a | |||
| 227701b6ee | |||
| 93c5176f1e | |||
| abd714d685 | |||
| 55e70858b1 | |||
| 70d3fd8a76 | |||
| 69e5591229 | |||
| 291103a495 | |||
| 158ca6f03c | |||
| a6bf6a61e8 | |||
| 21822512dc | |||
| 7e954ebca0 | |||
| ee2c5d1e16 | |||
| eaf4147f71 | |||
| 8e6ac9d678 | |||
| 26971af51d | |||
| c440b64d26 | |||
| 69ed33b4d6 | |||
| ef25d464a4 | |||
| f7a6f4527e | |||
| c208ea49d4 | |||
| 988df2d2e9 | |||
| e945e3bf6f | |||
| 89f3df067f | |||
| ae67ece59c | |||
| 659d684a7e | |||
| 45e0398379 | |||
| 96875d4a20 | |||
| c3f8077831 | |||
| e603c734eb | |||
| 1b20a7daf3 | |||
| 19cf6277ad | |||
| 006e2b4dda | |||
| ef903785ac | |||
| 640898928e | |||
| 9a54a0cb02 | |||
| 73e1fef0a3 | |||
| b62b835f6c | |||
| c885ef3991 | |||
| 9c2074fb53 | |||
| ecf60b59ef | |||
| 185087e9bc | |||
| bc29891423 | |||
| 7224caaf93 | |||
| 533fc0f454 | |||
| 6e319dd4db | |||
| ac3cc7829f | |||
| 3ffd5b0a14 | |||
| 99b5b337eb | |||
| 57688ce673 | |||
| 7524f18305 | |||
| 4cebe07bb7 | |||
| 7028cfffe8 | |||
| 662fd359f5 | |||
| aaea7c9146 | |||
| 1a16ede0b2 | |||
| cabccd160b | |||
| 047fda6a81 | |||
| 33f44c9024 | |||
| 80cdd4cbea | |||
| a77a9b3c83 | |||
| edb6df2199 | |||
| 135cfd2c55 | |||
| 51f1291382 | |||
| 79d86aadc0 | |||
| b4a38cc242 | |||
| 03005dea43 | |||
| 16d1354239 | |||
| 88114a4d3f | |||
| 21e2c2d79c | |||
| 946424f64f | |||
| af901a8509 | |||
| 181172656a | |||
| 98bfe79e5f | |||
| cad16d7863 | |||
| a17f4b0d7c | |||
| 3402e620b4 | |||
| e19240b60b | |||
| 8828c873bc | |||
| e36ad8177c | |||
| 276f784c20 | |||
| e84c958574 | |||
| 36ea3677d2 | |||
| 05579fa5b1 | |||
| 7121a1e060 | |||
| 2fc91516ba | |||
| 67d645ae7f | |||
| b0de86db6e | |||
| b451a633af | |||
| 771d2ba952 | |||
| 02cc18b759 | |||
| 34e28920c5 | |||
| 0f8a3e8fc2 | |||
| 555b62a619 | |||
| 808280b1a6 | |||
| 824c57d508 | |||
| 9bb690d299 | |||
| 8f972785f7 | |||
| 88139907dd | |||
| 66dc4c9c72 | |||
| 57bf23cdd7 | |||
| 41e94f2f1f | |||
| 9f48090545 | |||
| 193e8a750e | |||
| 449f23e8d5 | |||
| b5035dc27b | |||
| 33663ff04e | |||
| c09c530529 | |||
| 49f740b46a | |||
| 6600f5c6d3 | |||
| 1066e4a952 | |||
| 4ea822c71e | |||
| 9ef93976e8 | |||
| 870a330abb | |||
| 8c1ce34a03 | |||
| 27a9b7f3fa | |||
| b22fca475a | |||
| 42930f6fab | |||
| e69e2c5473 | |||
| d73edee893 | |||
| 948770205a | |||
| ab13fe80e4 | |||
| cd69e9052f | |||
| 5fed2ddb9e | |||
| a4e03408ad | |||
| 8f5dc4e5ce | |||
| 7085ec3551 | |||
| 624f8f0e3f | |||
| 5f45a2ecb2 | |||
| 92f11a646b | |||
| 5055d63f37 | |||
| f7b7f07e08 | |||
| d44f7df6f6 | |||
| d3199cfc7f | |||
| 265a7ad882 | |||
| 15c77deeef | |||
| bca48e088b | |||
| 0c79118e9f | |||
| b18befd339 | |||
| dce4a245b5 | |||
| c08f3836b0 | |||
| c95da45e48 | |||
| 26ab38aea0 | |||
| 067627b678 | |||
| 3c61ba7db4 | |||
| 17bc99743b | |||
| d325d23593 | |||
| 5369a132b6 | |||
| 7a70c37549 | |||
| 36d48fa2bd | |||
| 29f157ec86 | |||
| 1d4e394cf5 | |||
| 73a186066b | |||
| b41a7cacff | |||
| 84f45fe73f | |||
| 9dfe777fc8 | |||
| 827b941c49 | |||
| 9346718cc2 | |||
| f21b9c0df7 | |||
| c5881c4af3 | |||
| 50829f3a2f | |||
| 376e570b15 | |||
| 45505407de | |||
| f8d32096b2 | |||
| 24c8fc22df | |||
| c6007f3a82 | |||
| 62e2ce05de | |||
| 4488293d62 | |||
| 538a710d1c | |||
| baf376b6fd | |||
| 74cb5d592d | |||
| d467e9129b | |||
| 4a71a02215 | |||
| 7764979281 | |||
| b9d441f530 | |||
| 153d9ba9d6 | |||
| a7ec1cbc99 | |||
| 8b775e6895 | |||
| 73e4594e08 | |||
| 90ca099341 | |||
| 1583b2af91 | |||
| b32587697e | |||
| 38bbfe7e22 | |||
| 055f1c2216 | |||
| 932efb0d6f | |||
| 0e7c66ff6d | |||
| ac6e818f9d | |||
| 2e567918c1 | |||
| d0b45498cd | |||
| 7eb28b0cc0 | |||
| 7ad5f7b60b | |||
| 36db83a7ff | |||
| e643a3e8fb | |||
| 5d182394bc | |||
| 2629a0b0b5 | |||
| ce3da21b65 | |||
| 8cd7218e38 | |||
| 629cebf2a3 | |||
| 67bca39de0 | |||
| 9ed21f521b | |||
| 5b602723b1 | |||
| 8b058b03a0 | |||
| 969e753c19 | |||
| 7c481fdf11 | |||
| 2ed17ff985 | |||
| 4b8cce0b5f | |||
| ae5d37cd0f | |||
| 0f00e99799 | |||
| 00595a35c3 | |||
| 67ede4825d | |||
| 0e8829b8df | |||
| eb264e92ad | |||
| 747721bb78 | |||
| 223d32ba7d | |||
| 092cf812c9 | |||
| 27d0d1ad0a | |||
| 6e332d95fc | |||
| 42f915e15c | |||
| 658fd84035 | |||
| c2729178ca | |||
| 23bb5f573a | |||
| c7d3556b72 | |||
| 8319fd635d | |||
| fcd5fc667e | |||
| 70610a2fbe | |||
| 13f98ad272 | |||
| 1a5285066c | |||
| 0751c1f5f8 | |||
| eb40636aaf | |||
| b48ad14589 | |||
| 0d6a5ce302 | |||
| 7f5a4ebd7f | |||
| d4bc8d390f | |||
| a1236d0125 | |||
| 8f56f6969f | |||
| 14537a07af | |||
| 02d82df7d7 | |||
| 794fd89a5e | |||
| 6ef1b90e42 | |||
| 3ab884a479 | |||
| 26f5fffe72 | |||
| 2c43e9cf81 | |||
| f8dc04464b | |||
| 8be9c9b0d9 | |||
| b70053be25 | |||
| 7886642979 | |||
| 3102633260 | |||
| da3c3a04dc | |||
| 064fa5bdb5 | |||
| 2bb636d536 | |||
| b2e8b1b1c9 | |||
| 364457c2a4 | |||
| d65b9bf217 | |||
| 79afc82b8c | |||
| 2ec80217df | |||
| 5f93c754b4 | |||
| aff90527db | |||
| 0444289908 | |||
| 03988b114c | |||
| fbc9249afb | |||
| e7a9bf4ac4 | |||
| f161f77bf6 | |||
| 40df9c648a | |||
| ecd29001d7 | |||
| 64d3a51080 | |||
| 3a9738e433 | |||
| 1a58d8c86f | |||
| d23e2cb9ab | |||
| f2e5ef1cbf | |||
| d84f62f3cb | |||
| e719bd595f | |||
| 50827582b6 | |||
| fc6f8077bd | |||
| fcca3778ad | |||
| 473d9a9a08 | |||
| 53fab04fbd | |||
| e63d456588 | |||
| 95461ad82d | |||
| 755543879b | |||
| be5cc3a973 | |||
| 65ca68134a | |||
| 2f0055455d | |||
| aa91b0ab3c | |||
| e4d05cb669 | |||
| 790a755232 | |||
| 6781f9fc76 | |||
| 7b6073c424 | |||
| 9dfea7ed32 | |||
| 35ba5ae2a0 | |||
| 847dfb0eb8 | |||
| fd42be531c | |||
| 946444a485 | |||
| 06a05700aa | |||
| d3e6feb7de | |||
| 621a872306 | |||
| 907894212b | |||
| 10aeef9786 | |||
| b6fe0f1f9a | |||
| 7d5ab15ac0 | |||
| c4da304170 | |||
| 311c4147bf | |||
| fe0a8b4df3 | |||
| 2536b1b983 | |||
| a2339f71b8 | |||
| dfb2577171 | |||
| 9a34ccb1e1 | |||
| 4ad74937dc | |||
| 5b89ebc3bb | |||
| 6286923022 | |||
| e0a825658d | |||
| 5805a9d505 | |||
| 4e5327f77a | |||
| 090c520d49 | |||
| d2e5368375 | |||
| 9b6d4c4a4f | |||
| 9791e918c0 | |||
| b3c390ea1e | |||
| 8b2a3994a6 | |||
| 39d0acc5f9 | |||
| f42483b80d | |||
| 736ed2f4e7 | |||
| 35efd6d0ff | |||
| 0060e5f28e |
@@ -3,7 +3,13 @@
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": [
|
||||
"last 2 versions",
|
||||
"ie >= 11"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
+74
-253
@@ -27,14 +27,15 @@ kind: pipeline
|
||||
name: webpack-build
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: webpack-build
|
||||
image: nextcloudci/node:node-4
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run build
|
||||
- name: changes
|
||||
image: nextcloudci/node:node-4
|
||||
commands:
|
||||
- git status
|
||||
- bash -c "[[ ! \"`git status --porcelain `\" ]] || ( echo 'Uncommited changes in webpack build' && exit 1 )"
|
||||
|
||||
@@ -79,7 +80,7 @@ steps:
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: handlebars
|
||||
image: node
|
||||
image: node:10
|
||||
commands:
|
||||
- npm i
|
||||
- ./build/compile-handlebars-templates.sh
|
||||
@@ -396,215 +397,7 @@ trigger:
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mariadb10.0-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mariadb10.0-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mariadb
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mariadb
|
||||
image: mariadb:10.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mariadb10.1-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mariadb10.1-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mariadb
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mariadb
|
||||
image: mariadb:10.1
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mariadb10.2-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mariadb10.2-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mariadb
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mariadb
|
||||
image: mariadb:10.2
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mariadb10.3-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mariadb10.3-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mariadb
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mariadb
|
||||
image: mariadb:10.3
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mariadb10.4-php7.3
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mariadb10.4-php7.3
|
||||
image: nextcloudci/php7.3:php7.3-1
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mariadb
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mariadb
|
||||
image: mariadb:10.4
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mysql8.0-php7.2
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: mysql-php7.2
|
||||
image: nextcloudci/php7.2:php7.2-12
|
||||
commands:
|
||||
- NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mysql
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: mysql
|
||||
image: mysql:8.0
|
||||
command: [ "--default-authentication-plugin=mysql_native_password" ]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: owncloud
|
||||
MYSQL_USER: oc_autotest
|
||||
MYSQL_PASSWORD: owncloud
|
||||
MYSQL_DATABASE: oc_autotest
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mysql5.7-php7.1
|
||||
name: mysql-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
@@ -634,11 +427,12 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mysql5.7-php7.2
|
||||
name: mysql-php7.2
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
@@ -666,11 +460,12 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: mysql5.7-php7.3
|
||||
name: mysql-php7.3
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
@@ -698,6 +493,7 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
@@ -766,19 +562,20 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: postgres9-php7.3
|
||||
name: postgres9-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: postgres-php7.3
|
||||
image: nextcloudci/php7.3:php7.3-1
|
||||
- name: postgres-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- sleep 10 # gives the database enough time to initialize
|
||||
- POSTGRES=9 NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh pgsql
|
||||
@@ -830,40 +627,6 @@ services:
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: postgres11-php7.1
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: postgres-php7.1
|
||||
image: nextcloudci/php7.1:php7.1-16
|
||||
commands:
|
||||
- sleep 10 # gives the database enough time to initialize
|
||||
- POSTGRES=11 NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh pgsql
|
||||
|
||||
services:
|
||||
- name: cache
|
||||
image: redis
|
||||
- name: postgres-11
|
||||
image: postgres:11
|
||||
environment:
|
||||
POSTGRES_USER: oc_autotest
|
||||
POSTGRES_DB: oc_autotest_dummy
|
||||
POSTGRES_PASSWORD: owncloud
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
@@ -905,6 +668,7 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
@@ -940,6 +704,7 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
@@ -975,6 +740,7 @@ trigger:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
@@ -2441,3 +2207,58 @@ trigger:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: ui-regression
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: ui-regression
|
||||
image: nextcloudci/ui-regression:ui-regression-1
|
||||
commands:
|
||||
- cd tests/ui-regression
|
||||
- npm install
|
||||
- chown -R pptruser out node_modules
|
||||
- bash -c "until curl -s http://ui-regression-php-master > /dev/null && curl -s http://ui-regression-php > /dev/null; do sleep 2; done"
|
||||
- sudo -u pptruser node runTests.js || true
|
||||
- echo "The result can be found at https://s3.bitgrid.net/nextcloud-ui-regression/nextcloud/server/${DRONE_PULL_REQUEST}/index.html"
|
||||
shm_size: '1gb'
|
||||
|
||||
services:
|
||||
- name: ui-regression-php
|
||||
image: nextcloudci/server:server-1
|
||||
commands:
|
||||
- . /etc/apache2/envvars
|
||||
- rm -fr /var/www/html
|
||||
- mkdir /var/www/html && cp -rT . /var/www/html && chown -R www-data:www-data /var/www/html
|
||||
- rm -fr /var/www/html/config/config.php /var/www/html/data/*
|
||||
- apache2 -DFOREGROUND
|
||||
- name: ui-regression-php-master
|
||||
image: nextcloudci/server:server-1
|
||||
commands:
|
||||
- . /etc/apache2/envvars
|
||||
- rm -fr /var/www/html/config/config.php /var/www/html/data/*
|
||||
- su www-data -c "cd /var/www/html/ && git checkout $DRONE_REPO_BRANCH && git pull && git submodule update"
|
||||
- apache2 -DFOREGROUND
|
||||
- name: publish-s3
|
||||
image: plugins/s3
|
||||
settings:
|
||||
endpoint: https://ci-assets.nextcloud.com
|
||||
bucket: nextcloud-ui-regression
|
||||
path_style: true
|
||||
source: tests/ui-regression/out/**/*
|
||||
strip_prefix: tests/ui-regression/out/
|
||||
acl: public-read
|
||||
target: ${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}/${DRONE_PULL_REQUEST}
|
||||
secrets: [ aws_access_key_id, aws_secret_access_key ]
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
@@ -17,7 +17,5 @@
|
||||
/apps/twofactor_backupcodes/js/*.js.map binary
|
||||
/apps/updatenotification/js/updatenotification.js binary
|
||||
/apps/updatenotification/js/updatenotification.js.map binary
|
||||
/apps/workflowengine/js/*.js binary
|
||||
/apps/workflowengine/js/*.js.map binary
|
||||
|
||||
/settings/js/vue* binary
|
||||
|
||||
@@ -58,7 +58,7 @@ like `git config --global alias.ci 'commit -s'`. Now you can commit with
|
||||
|
||||
In case you are not sure how to add or update the license header correctly please have a look at [contribute/HowToApplyALicense.md][applyalicense]
|
||||
|
||||
[devmanual]: https://docs.nextcloud.com/server/latest/developer_manual/
|
||||
[devmanual]: https://docs.nextcloud.org/server/13/developer_manual/
|
||||
[dcofile]: https://github.com/nextcloud/server/blob/master/contribute/developer-certificate-of-origin
|
||||
[applyalicense]: https://github.com/nextcloud/server/blob/master/contribute/HowToApplyALicense.md
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# You can add one username per supported platform and one custom link
|
||||
custom: https://www.bountysource.com/teams/nextcloud
|
||||
@@ -1,33 +0,0 @@
|
||||
# We wait 1 months to check
|
||||
daysUntilStale: 30
|
||||
|
||||
# If no answer after two weeks, we close
|
||||
daysUntilClose: 14
|
||||
|
||||
# Only issues that requires info from the op
|
||||
onlyLabels:
|
||||
- "needs info"
|
||||
|
||||
# Only if issues that are not triaged
|
||||
exemptLabels:
|
||||
- "1. to develop"
|
||||
- "2. developing"
|
||||
- "3. to review"
|
||||
- "4. to release"
|
||||
- security
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity and seems to be missing some essential information.
|
||||
It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
||||
@@ -0,0 +1,30 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Pull request checks
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
block-merges-eol:
|
||||
name: Block merges for EOL branches
|
||||
|
||||
# Only run on stableXX branches
|
||||
if: startsWith( github.base_ref, 'stable')
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download updater config
|
||||
run: curl https://raw.githubusercontent.com/nextcloud/updater_server/production/config/config.php --output config.php
|
||||
|
||||
- name: Set server major version environment
|
||||
run: |
|
||||
# retrieve version number from branch reference
|
||||
server_major=$(echo "${{ github.base_ref }}" | sed -En 's/stable//p')
|
||||
echo "server_major=$server_major" >> $GITHUB_ENV
|
||||
|
||||
- name: Checking if ${{ env.server_major }} is EOL
|
||||
run: |
|
||||
php -r 'echo json_encode(require_once "config.php");' | jq --arg version "${{ env.server_major }}" '.stable[$version]["100"].eol' | grep --silent -i 'false'
|
||||
@@ -11,13 +11,12 @@
|
||||
|
||||
<IfModule mod_env.c>
|
||||
# Add security and privacy related headers
|
||||
Header always set Referrer-Policy "no-referrer"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-Download-Options "noopen"
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Permitted-Cross-Domain-Policies "none"
|
||||
Header always set X-Robots-Tag "none"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
Header set X-Robots-Tag "none"
|
||||
Header set X-Download-Options "noopen"
|
||||
Header set X-Permitted-Cross-Domain-Policies "none"
|
||||
Header set Referrer-Policy "no-referrer"
|
||||
SetEnv modHeadersAvailable true
|
||||
</IfModule>
|
||||
|
||||
@@ -41,13 +40,12 @@
|
||||
</IfModule>
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP_USER_AGENT} DavClnt
|
||||
RewriteRule ^$ /remote.php/webdav/ [L,R=302]
|
||||
RewriteCond %{HTTP_USER_AGENT} DavClnt
|
||||
RewriteRule ^$ /remote.php/webdav/ [L,R=302]
|
||||
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
|
||||
RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
|
||||
RewriteRule ^\.well-known/webfinger /public.php?service=webfinger [QSA,L]
|
||||
RewriteRule ^\.well-known/nodeinfo /public.php?service=nodeinfo [QSA,L]
|
||||
RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
|
||||
RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
|
||||
RewriteRule ^remote/(.*) remote.php [QSA,L]
|
||||
|
||||
+1
-1
Submodule 3rdparty updated: 3f236a8cb5...97e41cf109
@@ -1,4 +1,4 @@
|
||||
all: clean dev-setup build-js-production
|
||||
all: dev-setup build-js-production
|
||||
|
||||
dev-setup: clean-dev npm-init
|
||||
|
||||
@@ -20,30 +20,3 @@ watch-js:
|
||||
clean-dev:
|
||||
rm -rf node_modules
|
||||
|
||||
clean:
|
||||
rm -rf apps/accessibility/js/
|
||||
rm -rf apps/comments/js/
|
||||
rm -rf apps/files_sharing/js/dist/
|
||||
rm -rf apps/files_trashbin/js/
|
||||
rm -rf apps/files_versions/js/
|
||||
rm -rf apps/oauth2/js/
|
||||
rm -rf apps/systemtags/js/systemtags.*
|
||||
rm -rf apps/twofactor_backupcodes/js
|
||||
rm -rf apps/updatenotification/js/updatenotification.*
|
||||
rm -rf apps/workflowengine/js/
|
||||
rm -rf core/js/dist
|
||||
rm -rf settings/js/vue-*
|
||||
|
||||
clean-git: clean
|
||||
git checkout -- apps/accessibility/js/
|
||||
git checkout -- apps/comments/js/
|
||||
git checkout -- apps/files_sharing/js/dist/
|
||||
git checkout -- apps/files_trashbin/js/
|
||||
git checkout -- apps/files_versions/js/
|
||||
git checkout -- apps/oauth2/js/
|
||||
git checkout -- apps/systemtags/js/systemtags.*
|
||||
git checkout -- apps/twofactor_backupcodes/js
|
||||
git checkout -- apps/updatenotification/js/updatenotification.*
|
||||
git checkout -- apps/workflowengine/js/
|
||||
git checkout -- core/js/dist
|
||||
git checkout -- settings/js/vue-*
|
||||
|
||||
@@ -26,7 +26,7 @@ You want to learn more about how you can use Nextcloud to access, share and prot
|
||||
- 📦 Buy one of the [awesome **devices** coming with a preinstalled Nextcloud](https://nextcloud.com/devices/)
|
||||
- 🏢 Find a [service **provider**](https://nextcloud.com/providers/) who hosts Nextcloud for you or your company
|
||||
|
||||
Enterprise? Public Sector or Education user? You may want to have a look into the [**Enterprise Subscription**](https://nextcloud.com/enterprise/) provided by Nextcloud GmbH.
|
||||
Enterprise? Public Sector or Education user? You may want to have a look into the [**Enterprise Support Subscription**](https://nextcloud.com/enterprise/) provided by the Nextcloud GmbH.
|
||||
|
||||
## Get in touch 💬
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<name>Accessibility</name>
|
||||
<summary>Accessibility options for nextcloud</summary>
|
||||
<description><![CDATA[Provides multiple accessibilities options to ease your use of Nextcloud]]></description>
|
||||
<version>1.3.0</version>
|
||||
<version>1.2.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>John Molakvoæ</author>
|
||||
<namespace>Accessibility</namespace>
|
||||
@@ -16,7 +16,7 @@
|
||||
<bugs>https://github.com/nextcloud/server/issues</bugs>
|
||||
|
||||
<dependencies>
|
||||
<nextcloud min-version="17" max-version="17"/>
|
||||
<nextcloud min-version="16" max-version="16"/>
|
||||
</dependencies>
|
||||
|
||||
<settings>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitAccessibility::getLoader();
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"config" : {
|
||||
"vendor-dir": ".",
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true,
|
||||
"autoloader-suffix": "Accessibility"
|
||||
},
|
||||
"autoload" : {
|
||||
"psr-4": {
|
||||
"OCA\\Accessibility\\": "../lib/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'OCA\\Accessibility\\AccessibilityProvider' => $baseDir . '/../lib/AccessibilityProvider.php',
|
||||
'OCA\\Accessibility\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Accessibility\\Controller\\AccessibilityController' => $baseDir . '/../lib/Controller/AccessibilityController.php',
|
||||
'OCA\\Accessibility\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php',
|
||||
'OCA\\Accessibility\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
|
||||
'OCA\\Accessibility\\Settings\\PersonalSection' => $baseDir . '/../lib/Settings/PersonalSection.php',
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'OCA\\Accessibility\\' => array($baseDir . '/../lib'),
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitAccessibility
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitAccessibility', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitAccessibility', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitAccessibility::getInitializer($loader));
|
||||
} else {
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitAccessibility
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\Accessibility\\' => 18,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\Accessibility\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'OCA\\Accessibility\\AccessibilityProvider' => __DIR__ . '/..' . '/../lib/AccessibilityProvider.php',
|
||||
'OCA\\Accessibility\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Accessibility\\Controller\\AccessibilityController' => __DIR__ . '/..' . '/../lib/Controller/AccessibilityController.php',
|
||||
'OCA\\Accessibility\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php',
|
||||
'OCA\\Accessibility\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
|
||||
'OCA\\Accessibility\\Settings\\PersonalSection' => __DIR__ . '/..' . '/../lib/Settings/PersonalSection.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitAccessibility::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitAccessibility::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitAccessibility::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
@font-face {
|
||||
font-family: 'OpenDyslexic';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/OpenDyslexic-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'OpenDyslexic';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-weight: normal;
|
||||
src: url('../fonts/OpenDyslexic-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'OpenDyslexic';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/OpenDyslexic-Bold.woff') format('woff');
|
||||
}
|
||||
|
||||
$font-face: OpenDyslexic, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
$font-face: OpenDyslexic, 'Nunito', 'Open Sans', Frutiger, Calibri, 'Myriad Pro', Myriad, sans-serif;
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -63,7 +63,7 @@ class AccessibilityProvider {
|
||||
], [
|
||||
'id' => 'themedark',
|
||||
'img' => $this->urlGenerator->imagePath($this->appName, 'theme-dark.jpg'),
|
||||
'title' => $this->l->t('Dark theme'),
|
||||
'title' => $this->l->t('Dark theme (beta)'),
|
||||
'text' => $this->l->t('A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find.')
|
||||
]
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<script>
|
||||
import preview from './components/itemPreview';
|
||||
import axios from '@nextcloud/axios';
|
||||
import axios from 'nextcloud-axios';
|
||||
|
||||
export default {
|
||||
name: 'Accessibility',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const path = require('path')
|
||||
const path = require('path');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'src', 'main.js'),
|
||||
@@ -6,5 +7,22 @@ module.exports = {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
publicPath: '/js/',
|
||||
filename: 'accessibility.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [new VueLoaderPlugin()],
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<name>Auditing / Logging</name>
|
||||
<summary>Provides logging abilities for Nextcloud such as logging file accesses or otherwise sensitive actions.</summary>
|
||||
<description>Provides logging abilities for Nextcloud such as logging file accesses or otherwise sensitive actions.</description>
|
||||
<version>1.7.0</version>
|
||||
<version>1.6.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Nextcloud</author>
|
||||
<namespace>AdminAudit</namespace>
|
||||
@@ -15,7 +15,7 @@
|
||||
<category>monitoring</category>
|
||||
<bugs>https://github.com/nextcloud/server/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="17" max-version="17"/>
|
||||
<nextcloud min-version="16" max-version="16"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\AdminAudit\BackgroundJobs\Rotate</job>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<name>Cloud Federation API</name>
|
||||
<summary>Enable clouds to communicate with each other and exchange data</summary>
|
||||
<description>The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data.</description>
|
||||
<version>1.0.0</version>
|
||||
<version>0.2.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Bjoern Schiessle</author>
|
||||
<namespace>CloudFederationAPI</namespace>
|
||||
@@ -15,6 +15,6 @@
|
||||
<category>files</category>
|
||||
<bugs>https://github.com/nextcloud/cloud_federation/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="17" max-version="17"/>
|
||||
<nextcloud min-version="16" max-version="16"/>
|
||||
</dependencies>
|
||||
</info>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitCloudFederationAPI::getLoader();
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"config" : {
|
||||
"vendor-dir": ".",
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true,
|
||||
"autoloader-suffix": "CloudFederationAPI"
|
||||
},
|
||||
"autoload" : {
|
||||
"psr-4": {
|
||||
"OCA\\CloudFederationAPI\\": "../lib/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'OCA\\CloudFederationAPI\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\CloudFederationAPI\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
|
||||
'OCA\\CloudFederationAPI\\Config' => $baseDir . '/../lib/Config.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => $baseDir . '/../lib/Controller/RequestHandlerController.php',
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'OCA\\CloudFederationAPI\\' => array($baseDir . '/../lib'),
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitCloudFederationAPI
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitCloudFederationAPI', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitCloudFederationAPI', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitCloudFederationAPI::getInitializer($loader));
|
||||
} else {
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitCloudFederationAPI
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\CloudFederationAPI\\' => 23,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\CloudFederationAPI\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'OCA\\CloudFederationAPI\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\CloudFederationAPI\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
|
||||
'OCA\\CloudFederationAPI\\Config' => __DIR__ . '/..' . '/../lib/Config.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => __DIR__ . '/..' . '/../lib/Controller/RequestHandlerController.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitCloudFederationAPI::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitCloudFederationAPI::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitCloudFederationAPI::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<name>Comments</name>
|
||||
<summary>Files app plugin to add comments to files</summary>
|
||||
<description>Files app plugin to add comments to files</description>
|
||||
<version>1.7.0</version>
|
||||
<version>1.6.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Arthur Schiwon</author>
|
||||
<author>Vincent Petry</author>
|
||||
@@ -17,7 +17,7 @@
|
||||
<category>social</category>
|
||||
<bugs>https://github.com/nextcloud/server/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="17" max-version="17"/>
|
||||
<nextcloud min-version="16" max-version="16"/>
|
||||
</dependencies>
|
||||
|
||||
<activity>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="m6.667 4c-2.578 0-4.667 2.105-4.667 4.7v11.282c0 2.597 2.09 4.701 4.667 4.701 1.716 0.01 12.083 3e-3 17.057 0 1.115 0.842 1.807 1.748 3.057 3.206a0.93 0.93 0 0 0 0.561 0.103 0.969 0.969 0 0 0 0.445-0.187c0.302-0.223 0.466-0.603 0.427-0.988l-0.314-2.912a4.699 4.699 0 0 0 2.1-3.923v-11.281c0-2.596-2.09-4.701-4.667-4.701zm3.733 8.461c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21zm5.6 0c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21zm5.6 0c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21z" stroke-width=".5"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32"><path fill="#000" d="M16 3C7.163 3 0 7.925 0 14s7.163 11 16 11c.5 0 .98-.032 1.47-.063L26 32v-9.406c3.658-2.017 6-5.12 6-8.595 0-6.076-7.164-11-16-11z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 760 B After Width: | Height: | Size: 243 B |
@@ -1 +1 @@
|
||||
<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="m6.667 4c-2.578 0-4.667 2.105-4.667 4.7v11.282c0 2.597 2.09 4.701 4.667 4.701 1.716 0.01 12.083 3e-3 17.057 0 1.115 0.842 1.807 1.748 3.057 3.206a0.93 0.93 0 0 0 0.561 0.103 0.969 0.969 0 0 0 0.445-0.187c0.302-0.223 0.466-0.603 0.427-0.988l-0.314-2.912a4.699 4.699 0 0 0 2.1-3.923v-11.281c0-2.596-2.09-4.701-4.667-4.701zm3.733 8.461c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21zm5.6 0c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21zm5.6 0c1.03 0 1.867 0.842 1.867 1.88 0 1.676-2.01 2.514-3.187 1.33-1.176-1.184-0.343-3.21 1.32-3.21z" stroke-width=".5" fill="#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32"><path fill="#fff" d="M16 3C7.163 3 0 7.925 0 14s7.163 11 16 11c.5 0 .98-.032 1.47-.063L26 32v-9.406c3.658-2.017 6-5.12 6-8.595 0-6.076-7.164-11-16-11z"/></svg>
|
||||
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 242 B |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -76,7 +76,15 @@ class Application extends App {
|
||||
}
|
||||
|
||||
protected function registerNotifier() {
|
||||
$this->getContainer()->getServer()->getNotificationManager()->registerNotifierService(Notifier::class);
|
||||
$this->getContainer()->getServer()->getNotificationManager()->registerNotifier(
|
||||
function() {
|
||||
return $this->getContainer()->query(Notifier::class);
|
||||
},
|
||||
function () {
|
||||
$l = $this->getContainer()->getServer()->getL10NFactory()->get('comments');
|
||||
return ['id' => 'comments', 'name' => $l->t('Comments')];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function registerCommentsEventHandler() {
|
||||
|
||||
@@ -32,7 +32,6 @@ use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Notification\AlreadyProcessedException;
|
||||
use OCP\Notification\INotification;
|
||||
use OCP\Notification\INotifier;
|
||||
|
||||
@@ -67,35 +66,13 @@ class Notifier implements INotifier {
|
||||
$this->userManager = $userManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier of the notifier, only use [a-z0-9_]
|
||||
*
|
||||
* @return string
|
||||
* @since 17.0.0
|
||||
*/
|
||||
public function getID(): string {
|
||||
return 'comments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Human readable name describing the notifier
|
||||
*
|
||||
* @return string
|
||||
* @since 17.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l10nFactory->get('comments')->t('Comments');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param INotification $notification
|
||||
* @param string $languageCode The code of the language that should be used to prepare the notification
|
||||
* @return INotification
|
||||
* @throws \InvalidArgumentException When the notification was not prepared by a notifier
|
||||
* @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function prepare(INotification $notification, string $languageCode): INotification {
|
||||
public function prepare(INotification $notification, $languageCode) {
|
||||
if($notification->getApp() !== 'comments') {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
@@ -124,7 +101,7 @@ class Notifier implements INotifier {
|
||||
$userFolder = $this->rootFolder->getUserFolder($notification->getUser());
|
||||
$nodes = $userFolder->getById((int)$parameters[1]);
|
||||
if(empty($nodes)) {
|
||||
throw new AlreadyProcessedException();
|
||||
throw new \InvalidArgumentException('Cannot resolve file ID to node instance');
|
||||
}
|
||||
$node = $nodes[0];
|
||||
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
});
|
||||
|
||||
var username = $el.find('.avatar').data('username');
|
||||
if (username !== OC.getCurrentUser().uid) {
|
||||
if (username !== oc_current_user) {
|
||||
$el.find('.authorRow .avatar, .authorRow .author').contactsMenu(
|
||||
username, 0, $el.find('.authorRow'));
|
||||
}
|
||||
|
||||
@@ -117,9 +117,6 @@ class NotificationsTest extends TestCase {
|
||||
$comment->expects($this->any())
|
||||
->method('getObjectType')
|
||||
->willReturn('files');
|
||||
$comment->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
$this->commentsManager->expects($this->any())
|
||||
->method('get')
|
||||
@@ -128,7 +125,6 @@ class NotificationsTest extends TestCase {
|
||||
|
||||
$file = $this->createMock(Node::class);
|
||||
$folder = $this->createMock(Folder::class);
|
||||
$user = $this->createMock(IUser::class);
|
||||
|
||||
$this->rootFolder->expects($this->once())
|
||||
->method('getUserFolder')
|
||||
@@ -140,11 +136,7 @@ class NotificationsTest extends TestCase {
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->willReturn('user');
|
||||
->willReturn($this->createMock(IUser::class));
|
||||
|
||||
$notification = $this->createMock(INotification::class);
|
||||
$notification->expects($this->any())
|
||||
@@ -171,15 +163,9 @@ class NotificationsTest extends TestCase {
|
||||
$this->rootFolder->expects($this->never())
|
||||
->method('getUserFolder');
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->willReturn('user');
|
||||
->willReturn($this->createMock(IUser::class));
|
||||
|
||||
$this->notificationManager->expects($this->never())
|
||||
->method('createNotification');
|
||||
@@ -195,9 +181,6 @@ class NotificationsTest extends TestCase {
|
||||
$comment->expects($this->any())
|
||||
->method('getObjectType')
|
||||
->willReturn('files');
|
||||
$comment->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
$this->commentsManager->expects($this->any())
|
||||
->method('get')
|
||||
@@ -214,15 +197,9 @@ class NotificationsTest extends TestCase {
|
||||
->method('getById')
|
||||
->willReturn([]);
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->willReturn('user');
|
||||
->willReturn($this->createMock(IUser::class));
|
||||
|
||||
$notification = $this->createMock(INotification::class);
|
||||
$notification->expects($this->any())
|
||||
|
||||
@@ -91,9 +91,6 @@ class ListenerTest extends TestCase {
|
||||
[ 'type' => 'user', 'id' => '23452-4333-54353-2342'],
|
||||
[ 'type' => 'user', 'id' => 'yolo'],
|
||||
]);
|
||||
$comment->expects($this->atLeastOnce())
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
|
||||
$event = $this->getMockBuilder(CommentsEvent::class)
|
||||
@@ -189,9 +186,6 @@ class ListenerTest extends TestCase {
|
||||
$comment->expects($this->once())
|
||||
->method('getMentions')
|
||||
->willReturn([[ 'type' => 'user', 'id' => 'foobar']]);
|
||||
$comment->expects($this->atLeastOnce())
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
/** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */
|
||||
$event = $this->getMockBuilder(CommentsEvent::class)
|
||||
|
||||
@@ -195,9 +195,6 @@ class NotifierTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getMentions')
|
||||
->willReturn([['type' => 'user', 'id' => 'you']]);
|
||||
$this->comment->expects($this->atLeastOnce())
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
$this->commentsManager
|
||||
->expects($this->once())
|
||||
@@ -542,7 +539,7 @@ class NotifierTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Notification\AlreadyProcessedException
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testPrepareUnresolvableFileID() {
|
||||
$displayName = 'Huraga';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const path = require('path')
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'src', 'comments.js'),
|
||||
@@ -7,7 +7,34 @@ module.exports = {
|
||||
publicPath: '/js/',
|
||||
filename: 'comments.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader']
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
externals: {
|
||||
jquery: 'jQuery'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['*', '.js']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
*/
|
||||
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService;
|
||||
use OCA\DAV\CardDAV\CardDavBackend;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
@@ -56,13 +55,6 @@ $eventDispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createSubscription
|
||||
$jobList = $app->getContainer()->getServer()->getJobList();
|
||||
$subscriptionData = $event->getArgument('subscriptionData');
|
||||
|
||||
/**
|
||||
* Initial subscription refetch
|
||||
* @var RefreshWebcalService $refreshWebcalService
|
||||
*/
|
||||
$refreshWebcalService = $app->getContainer()->query(RefreshWebcalService::class);
|
||||
$refreshWebcalService->refreshSubscription($subscriptionData['principaluri'], $subscriptionData['uri']);
|
||||
|
||||
$jobList->add(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
|
||||
'principaluri' => $subscriptionData['principaluri'],
|
||||
'uri' => $subscriptionData['uri']
|
||||
@@ -116,6 +108,3 @@ $calendarManager->register(function() use ($calendarManager, $app) {
|
||||
$app->setupCalendarProvider($calendarManager, $user->getUID());
|
||||
}
|
||||
});
|
||||
|
||||
$app->registerNotifier();
|
||||
$app->registerCalendarReminders();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<name>WebDAV</name>
|
||||
<summary>WebDAV endpoint</summary>
|
||||
<description>WebDAV endpoint</description>
|
||||
<version>1.13.0</version>
|
||||
<version>1.9.2</version>
|
||||
<licence>agpl</licence>
|
||||
<author>owncloud.org</author>
|
||||
<namespace>DAV</namespace>
|
||||
@@ -16,14 +16,13 @@
|
||||
<category>integration</category>
|
||||
<bugs>https://github.com/nextcloud/server/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="17" max-version="17"/>
|
||||
<nextcloud min-version="16" max-version="16"/>
|
||||
</dependencies>
|
||||
|
||||
<background-jobs>
|
||||
<job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\EventReminderJob</job>
|
||||
</background-jobs>
|
||||
|
||||
<repair-steps>
|
||||
@@ -33,7 +32,6 @@
|
||||
<step>OCA\DAV\Migration\CalDAVRemoveEmptyValue</step>
|
||||
<step>OCA\DAV\Migration\BuildCalendarSearchIndex</step>
|
||||
<step>OCA\DAV\Migration\RefreshWebcalJobRegistrar</step>
|
||||
<step>OCA\DAV\Migration\RegisterBuildReminderIndexBackgroundJob</step>
|
||||
<step>OCA\DAV\Migration\RemoveOrphanEventsAndContacts</step>
|
||||
<step>OCA\DAV\Migration\RemoveClassifiedEventActivity</step>
|
||||
</post-migration>
|
||||
@@ -47,7 +45,6 @@
|
||||
<command>OCA\DAV\Command\CreateCalendar</command>
|
||||
<command>OCA\DAV\Command\MoveCalendar</command>
|
||||
<command>OCA\DAV\Command\ListCalendars</command>
|
||||
<command>OCA\DAV\Command\SendEventReminders</command>
|
||||
<command>OCA\DAV\Command\SyncBirthdayCalendar</command>
|
||||
<command>OCA\DAV\Command\SyncSystemAddressBook</command>
|
||||
<command>OCA\DAV\Command\RemoveInvalidShares</command>
|
||||
|
||||
@@ -46,9 +46,8 @@ $principalBackend = new Principal(
|
||||
\OC::$server->getGroupManager(),
|
||||
\OC::$server->getShareManager(),
|
||||
\OC::$server->getUserSession(),
|
||||
\OC::$server->getAppManager(),
|
||||
\OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class),
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getAppManager(),
|
||||
'principals/'
|
||||
);
|
||||
$db = \OC::$server->getDatabaseConnection();
|
||||
|
||||
@@ -47,9 +47,8 @@ $principalBackend = new Principal(
|
||||
\OC::$server->getGroupManager(),
|
||||
\OC::$server->getShareManager(),
|
||||
\OC::$server->getUserSession(),
|
||||
\OC::$server->getAppManager(),
|
||||
\OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class),
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getAppManager(),
|
||||
'principals/'
|
||||
);
|
||||
$db = \OC::$server->getDatabaseConnection();
|
||||
|
||||
@@ -79,9 +79,7 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, funct
|
||||
\OC\Files\Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
|
||||
return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => $share->getPermissions() | \OCP\Constants::PERMISSION_SHARE));
|
||||
});
|
||||
\OC\Files\Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
|
||||
return new \OCA\DAV\Storage\PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
|
||||
});
|
||||
|
||||
\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
|
||||
|
||||
OC_Util::tearDownFS();
|
||||
|
||||
@@ -11,10 +11,8 @@ return array(
|
||||
'OCA\\DAV\\Avatars\\AvatarHome' => $baseDir . '/../lib/Avatars/AvatarHome.php',
|
||||
'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php',
|
||||
'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php',
|
||||
'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => $baseDir . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
|
||||
@@ -46,24 +44,11 @@ return array(
|
||||
'OCA\\DAV\\CalDAV\\Plugin' => $baseDir . '/../lib/CalDAV/Plugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Principal\\Collection' => $baseDir . '/../lib/CalDAV/Principal/Collection.php',
|
||||
'OCA\\DAV\\CalDAV\\Principal\\User' => $baseDir . '/../lib/CalDAV/Principal/User.php',
|
||||
'OCA\\DAV\\CalDAV\\Proxy\\Proxy' => $baseDir . '/../lib/CalDAV/Proxy/Proxy.php',
|
||||
'OCA\\DAV\\CalDAV\\Proxy\\ProxyMapper' => $baseDir . '/../lib/CalDAV/Proxy/ProxyMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendar' => $baseDir . '/../lib/CalDAV/PublicCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendarObject' => $baseDir . '/../lib/CalDAV/PublicCalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => $baseDir . '/../lib/CalDAV/PublicCalendarRoot.php',
|
||||
'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => $baseDir . '/../lib/CalDAV/Publishing/PublishPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => $baseDir . '/../lib/CalDAV/Publishing/Xml/Publisher.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\Backend' => $baseDir . '/../lib/CalDAV/Reminder/Backend.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\INotificationProvider' => $baseDir . '/../lib/CalDAV/Reminder/INotificationProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProviderManager.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AbstractProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AudioProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\EmailProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\ProviderNotAvailableException' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\PushProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/PushProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationTypeDoesNotExistException' => $baseDir . '/../lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\Notifier' => $baseDir . '/../lib/CalDAV/Reminder/Notifier.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\ReminderService' => $baseDir . '/../lib/CalDAV/Reminder/ReminderService.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php',
|
||||
@@ -78,7 +63,6 @@ return array(
|
||||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
|
||||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => $baseDir . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
|
||||
'OCA\\DAV\\CalDAV\\WebcalCaching\\Plugin' => $baseDir . '/../lib/CalDAV/WebcalCaching/Plugin.php',
|
||||
'OCA\\DAV\\CalDAV\\WebcalCaching\\RefreshWebcalService' => $baseDir . '/../lib/CalDAV/WebcalCaching/RefreshWebcalService.php',
|
||||
'OCA\\DAV\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
|
||||
'OCA\\DAV\\CardDAV\\AddressBook' => $baseDir . '/../lib/CardDAV/AddressBook.php',
|
||||
'OCA\\DAV\\CardDAV\\AddressBookImpl' => $baseDir . '/../lib/CardDAV/AddressBookImpl.php',
|
||||
@@ -100,7 +84,6 @@ return array(
|
||||
'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php',
|
||||
'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php',
|
||||
'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php',
|
||||
'OCA\\DAV\\Command\\SendEventReminders' => $baseDir . '/../lib/Command/SendEventReminders.php',
|
||||
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => $baseDir . '/../lib/Command/SyncBirthdayCalendar.php',
|
||||
'OCA\\DAV\\Command\\SyncSystemAddressBook' => $baseDir . '/../lib/Command/SyncSystemAddressBook.php',
|
||||
'OCA\\DAV\\Comments\\CommentNode' => $baseDir . '/../lib/Comments/CommentNode.php',
|
||||
@@ -119,6 +102,7 @@ return array(
|
||||
'OCA\\DAV\\Connector\\Sabre\\ChecksumList' => $baseDir . '/../lib/Connector/Sabre/ChecksumList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CommentPropertiesPlugin' => $baseDir . '/../lib/Connector/Sabre/CommentPropertiesPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CopyEtagHeaderPlugin' => $baseDir . '/../lib/Connector/Sabre/CopyEtagHeaderPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CustomPropertiesBackend' => $baseDir . '/../lib/Connector/Sabre/CustomPropertiesBackend.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\DavAclPlugin' => $baseDir . '/../lib/Connector/Sabre/DavAclPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\Directory' => $baseDir . '/../lib/Connector/Sabre/Directory.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\DummyGetResponsePlugin' => $baseDir . '/../lib/Connector/Sabre/DummyGetResponsePlugin.php',
|
||||
@@ -142,7 +126,6 @@ return array(
|
||||
'OCA\\DAV\\Connector\\Sabre\\Server' => $baseDir . '/../lib/Connector/Sabre/Server.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ServerFactory' => $baseDir . '/../lib/Connector/Sabre/ServerFactory.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ShareTypeList' => $baseDir . '/../lib/Connector/Sabre/ShareTypeList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ShareeList' => $baseDir . '/../lib/Connector/Sabre/ShareeList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => $baseDir . '/../lib/Connector/Sabre/SharesPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php',
|
||||
@@ -164,7 +147,6 @@ return array(
|
||||
'OCA\\DAV\\Direct\\DirectHome' => $baseDir . '/../lib/Direct/DirectHome.php',
|
||||
'OCA\\DAV\\Direct\\Server' => $baseDir . '/../lib/Direct/Server.php',
|
||||
'OCA\\DAV\\Direct\\ServerFactory' => $baseDir . '/../lib/Direct/ServerFactory.php',
|
||||
'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => $baseDir . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php',
|
||||
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.php',
|
||||
'OCA\\DAV\\Files\\FileSearchBackend' => $baseDir . '/../lib/Files/FileSearchBackend.php',
|
||||
'OCA\\DAV\\Files\\FilesHome' => $baseDir . '/../lib/Files/FilesHome.php',
|
||||
@@ -180,7 +162,6 @@ return array(
|
||||
'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => $baseDir . '/../lib/Migration/FixBirthdayCalendarComponent.php',
|
||||
'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => $baseDir . '/../lib/Migration/RefreshWebcalJobRegistrar.php',
|
||||
'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => $baseDir . '/../lib/Migration/RegenerateBirthdayCalendars.php',
|
||||
'OCA\\DAV\\Migration\\RegisterBuildReminderIndexBackgroundJob' => $baseDir . '/../lib/Migration/RegisterBuildReminderIndexBackgroundJob.php',
|
||||
'OCA\\DAV\\Migration\\RemoveClassifiedEventActivity' => $baseDir . '/../lib/Migration/RemoveClassifiedEventActivity.php',
|
||||
'OCA\\DAV\\Migration\\RemoveOrphanEventsAndContacts' => $baseDir . '/../lib/Migration/RemoveOrphanEventsAndContacts.php',
|
||||
'OCA\\DAV\\Migration\\Version1004Date20170825134824' => $baseDir . '/../lib/Migration/Version1004Date20170825134824.php',
|
||||
@@ -197,15 +178,11 @@ return array(
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => $baseDir . '/../lib/Migration/Version1008Date20181105110300.php',
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => $baseDir . '/../lib/Migration/Version1008Date20181105112049.php',
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => $baseDir . '/../lib/Migration/Version1008Date20181114084440.php',
|
||||
'OCA\\DAV\\Migration\\Version1011Date20190725113607' => $baseDir . '/../lib/Migration/Version1011Date20190725113607.php',
|
||||
'OCA\\DAV\\Migration\\Version1011Date20190806104428' => $baseDir . '/../lib/Migration/Version1011Date20190806104428.php',
|
||||
'OCA\\DAV\\Migration\\Version1012Date20190808122342' => $baseDir . '/../lib/Migration/Version1012Date20190808122342.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
|
||||
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
|
||||
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
|
||||
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => $baseDir . '/../lib/SystemTag/SystemTagMappingNode.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php',
|
||||
@@ -213,7 +190,6 @@ return array(
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
|
||||
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => $baseDir . '/../lib/Traits/PrincipalProxyTrait.php',
|
||||
'OCA\\DAV\\Upload\\AssemblyStream' => $baseDir . '/../lib/Upload/AssemblyStream.php',
|
||||
'OCA\\DAV\\Upload\\ChunkingPlugin' => $baseDir . '/../lib/Upload/ChunkingPlugin.php',
|
||||
'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php',
|
||||
|
||||
@@ -26,10 +26,8 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Avatars\\AvatarHome' => __DIR__ . '/..' . '/../lib/Avatars/AvatarHome.php',
|
||||
'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php',
|
||||
'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php',
|
||||
'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
|
||||
@@ -61,24 +59,11 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\CalDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Plugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Principal\\Collection' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/Collection.php',
|
||||
'OCA\\DAV\\CalDAV\\Principal\\User' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/User.php',
|
||||
'OCA\\DAV\\CalDAV\\Proxy\\Proxy' => __DIR__ . '/..' . '/../lib/CalDAV/Proxy/Proxy.php',
|
||||
'OCA\\DAV\\CalDAV\\Proxy\\ProxyMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Proxy/ProxyMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarRoot.php',
|
||||
'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/PublishPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/Xml/Publisher.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/Backend.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\INotificationProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/INotificationProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProviderManager.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AbstractProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AudioProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\EmailProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\ProviderNotAvailableException' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\PushProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/PushProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\NotificationTypeDoesNotExistException' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\Notifier' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/Notifier.php',
|
||||
'OCA\\DAV\\CalDAV\\Reminder\\ReminderService' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/ReminderService.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php',
|
||||
'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php',
|
||||
@@ -93,7 +78,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
|
||||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
|
||||
'OCA\\DAV\\CalDAV\\WebcalCaching\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/WebcalCaching/Plugin.php',
|
||||
'OCA\\DAV\\CalDAV\\WebcalCaching\\RefreshWebcalService' => __DIR__ . '/..' . '/../lib/CalDAV/WebcalCaching/RefreshWebcalService.php',
|
||||
'OCA\\DAV\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
|
||||
'OCA\\DAV\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBook.php',
|
||||
'OCA\\DAV\\CardDAV\\AddressBookImpl' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookImpl.php',
|
||||
@@ -115,7 +99,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php',
|
||||
'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php',
|
||||
'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php',
|
||||
'OCA\\DAV\\Command\\SendEventReminders' => __DIR__ . '/..' . '/../lib/Command/SendEventReminders.php',
|
||||
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => __DIR__ . '/..' . '/../lib/Command/SyncBirthdayCalendar.php',
|
||||
'OCA\\DAV\\Command\\SyncSystemAddressBook' => __DIR__ . '/..' . '/../lib/Command/SyncSystemAddressBook.php',
|
||||
'OCA\\DAV\\Comments\\CommentNode' => __DIR__ . '/..' . '/../lib/Comments/CommentNode.php',
|
||||
@@ -134,6 +117,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Connector\\Sabre\\ChecksumList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ChecksumList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CommentPropertiesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/CommentPropertiesPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CopyEtagHeaderPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/CopyEtagHeaderPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/Connector/Sabre/CustomPropertiesBackend.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\DavAclPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/DavAclPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\Directory' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Directory.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\DummyGetResponsePlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/DummyGetResponsePlugin.php',
|
||||
@@ -157,7 +141,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Connector\\Sabre\\Server' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Server.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ServerFactory' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ServerFactory.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ShareTypeList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ShareTypeList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ShareeList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ShareeList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/SharesPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php',
|
||||
@@ -179,7 +162,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Direct\\DirectHome' => __DIR__ . '/..' . '/../lib/Direct/DirectHome.php',
|
||||
'OCA\\DAV\\Direct\\Server' => __DIR__ . '/..' . '/../lib/Direct/Server.php',
|
||||
'OCA\\DAV\\Direct\\ServerFactory' => __DIR__ . '/..' . '/../lib/Direct/ServerFactory.php',
|
||||
'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => __DIR__ . '/..' . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php',
|
||||
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.php',
|
||||
'OCA\\DAV\\Files\\FileSearchBackend' => __DIR__ . '/..' . '/../lib/Files/FileSearchBackend.php',
|
||||
'OCA\\DAV\\Files\\FilesHome' => __DIR__ . '/..' . '/../lib/Files/FilesHome.php',
|
||||
@@ -195,7 +177,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => __DIR__ . '/..' . '/../lib/Migration/FixBirthdayCalendarComponent.php',
|
||||
'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => __DIR__ . '/..' . '/../lib/Migration/RefreshWebcalJobRegistrar.php',
|
||||
'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/Migration/RegenerateBirthdayCalendars.php',
|
||||
'OCA\\DAV\\Migration\\RegisterBuildReminderIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/Migration/RegisterBuildReminderIndexBackgroundJob.php',
|
||||
'OCA\\DAV\\Migration\\RemoveClassifiedEventActivity' => __DIR__ . '/..' . '/../lib/Migration/RemoveClassifiedEventActivity.php',
|
||||
'OCA\\DAV\\Migration\\RemoveOrphanEventsAndContacts' => __DIR__ . '/..' . '/../lib/Migration/RemoveOrphanEventsAndContacts.php',
|
||||
'OCA\\DAV\\Migration\\Version1004Date20170825134824' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170825134824.php',
|
||||
@@ -212,15 +193,11 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105110300.php',
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105112049.php',
|
||||
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181114084440.php',
|
||||
'OCA\\DAV\\Migration\\Version1011Date20190725113607' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190725113607.php',
|
||||
'OCA\\DAV\\Migration\\Version1011Date20190806104428' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190806104428.php',
|
||||
'OCA\\DAV\\Migration\\Version1012Date20190808122342' => __DIR__ . '/..' . '/../lib/Migration/Version1012Date20190808122342.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
|
||||
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
|
||||
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
|
||||
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagMappingNode.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',
|
||||
@@ -228,7 +205,6 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
|
||||
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
|
||||
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => __DIR__ . '/..' . '/../lib/Traits/PrincipalProxyTrait.php',
|
||||
'OCA\\DAV\\Upload\\AssemblyStream' => __DIR__ . '/..' . '/../lib/Upload/AssemblyStream.php',
|
||||
'OCA\\DAV\\Upload\\ChunkingPlugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingPlugin.php',
|
||||
'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php',
|
||||
|
||||
@@ -36,18 +36,3 @@ $('#caldavGenerateBirthdayCalendar').change(function() {
|
||||
$.post(OC.generateUrl('/apps/dav/disableBirthdayCalendar'));
|
||||
}
|
||||
});
|
||||
|
||||
$('#caldavSendRemindersNotifications').change(function() {
|
||||
var val = $(this)[0].checked;
|
||||
|
||||
OCP.AppConfig.setValue('dav', 'sendEventReminders', val ? 'yes' : 'no');
|
||||
|
||||
$('#caldavSendRemindersNotificationsPush').prop('disabled', !val)
|
||||
});
|
||||
|
||||
$('#caldavSendRemindersNotificationsPush').change(function() {
|
||||
var val = $(this)[0].checked;
|
||||
|
||||
OCP.AppConfig.setValue('dav', 'sendEventRemindersPush', val ? 'yes' : 'no');
|
||||
});
|
||||
|
||||
|
||||
@@ -30,13 +30,6 @@ use OCA\DAV\CalDAV\Activity\Backend;
|
||||
use OCA\DAV\CalDAV\Activity\Provider\Event;
|
||||
use OCA\DAV\CalDAV\BirthdayService;
|
||||
use OCA\DAV\CalDAV\CalendarManager;
|
||||
use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
|
||||
use OCA\DAV\CalDAV\Reminder\Notifier;
|
||||
use OCA\DAV\CalDAV\Reminder\ReminderService;
|
||||
use OCA\DAV\Capabilities;
|
||||
use OCA\DAV\CardDAV\ContactsManager;
|
||||
use OCA\DAV\CardDAV\PhotoCache;
|
||||
@@ -50,8 +43,6 @@ use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class Application extends App {
|
||||
|
||||
const APP_ID = 'dav';
|
||||
|
||||
/**
|
||||
* Application constructor.
|
||||
*/
|
||||
@@ -118,7 +109,8 @@ class Application extends App {
|
||||
}
|
||||
});
|
||||
|
||||
$birthdayListener = function ($event) {
|
||||
// carddav/caldav sync event setup
|
||||
$listener = function($event) {
|
||||
if ($event instanceof GenericEvent) {
|
||||
/** @var BirthdayService $b */
|
||||
$b = $this->getContainer()->query(BirthdayService::class);
|
||||
@@ -130,9 +122,9 @@ class Application extends App {
|
||||
}
|
||||
};
|
||||
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener);
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener);
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) {
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $listener);
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $listener);
|
||||
$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function($event) {
|
||||
if ($event instanceof GenericEvent) {
|
||||
/** @var BirthdayService $b */
|
||||
$b = $this->getContainer()->query(BirthdayService::class);
|
||||
@@ -185,11 +177,6 @@ class Application extends App {
|
||||
$event->getArgument('calendarData'),
|
||||
$event->getArgument('shares')
|
||||
);
|
||||
|
||||
$reminderBackend = $this->getContainer()->query(ReminderBackend::class);
|
||||
$reminderBackend->cleanRemindersForCalendar(
|
||||
$event->getArgument('calendarId')
|
||||
);
|
||||
});
|
||||
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function(GenericEvent $event) {
|
||||
/** @var Backend $backend */
|
||||
@@ -200,8 +187,6 @@ class Application extends App {
|
||||
$event->getArgument('add'),
|
||||
$event->getArgument('remove')
|
||||
);
|
||||
|
||||
// Here we should recalculate if reminders should be sent to new or old sharees
|
||||
});
|
||||
|
||||
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function(GenericEvent $event) {
|
||||
@@ -229,14 +214,6 @@ class Application extends App {
|
||||
$event->getArgument('shares'),
|
||||
$event->getArgument('objectData')
|
||||
);
|
||||
|
||||
/** @var ReminderService $reminderBackend */
|
||||
$reminderService = $this->getContainer()->query(ReminderService::class);
|
||||
|
||||
$reminderService->onTouchCalendarObject(
|
||||
$eventName,
|
||||
$event->getArgument('objectData')
|
||||
);
|
||||
};
|
||||
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $listener);
|
||||
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', $listener);
|
||||
@@ -247,23 +224,4 @@ class Application extends App {
|
||||
return $this->getContainer()->query(SyncService::class);
|
||||
}
|
||||
|
||||
public function registerNotifier():void {
|
||||
$this->getContainer()
|
||||
->getServer()
|
||||
->getNotificationManager()
|
||||
->registerNotifierService(Notifier::class);
|
||||
}
|
||||
|
||||
public function registerCalendarReminders():void {
|
||||
try {
|
||||
/** @var NotificationProviderManager $notificationProviderManager */
|
||||
$notificationProviderManager = $this->getContainer()->query(NotificationProviderManager::class);
|
||||
$notificationProviderManager->registerProvider(AudioProvider::class);
|
||||
$notificationProviderManager->registerProvider(EmailProvider::class);
|
||||
$notificationProviderManager->registerProvider(PushProvider::class);
|
||||
} catch(\Exception $ex) {
|
||||
$this->getContainer()->getServer()->getLogger()->logException($ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright 2019 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\BackgroundJob;
|
||||
|
||||
use OCP\BackgroundJob\QueuedJob;
|
||||
use OCA\DAV\CalDAV\Reminder\ReminderService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\ILogger;
|
||||
|
||||
/**
|
||||
* Class BuildReminderIndexBackgroundJob
|
||||
*
|
||||
* @package OCA\DAV\BackgroundJob
|
||||
*/
|
||||
class BuildReminderIndexBackgroundJob extends QueuedJob {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $db;
|
||||
|
||||
/** @var ReminderService */
|
||||
private $reminderService;
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
/** @var IJobList */
|
||||
private $jobList;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/**
|
||||
* BuildReminderIndexBackgroundJob constructor.
|
||||
*
|
||||
* @param IDBConnection $db
|
||||
* @param ReminderService $reminderService
|
||||
* @param ILogger $logger
|
||||
* @param IJobList $jobList
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(IDBConnection $db,
|
||||
ReminderService $reminderService,
|
||||
ILogger $logger,
|
||||
IJobList $jobList,
|
||||
ITimeFactory $timeFactory) {
|
||||
parent::__construct($timeFactory);
|
||||
$this->db = $db;
|
||||
$this->reminderService = $reminderService;
|
||||
$this->logger = $logger;
|
||||
$this->jobList = $jobList;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $arguments
|
||||
*/
|
||||
public function run($arguments) {
|
||||
$offset = (int) $arguments['offset'];
|
||||
$stopAt = (int) $arguments['stopAt'];
|
||||
|
||||
$this->logger->info('Building calendar reminder index (' . $offset .'/' . $stopAt . ')');
|
||||
|
||||
$offset = $this->buildIndex($offset, $stopAt);
|
||||
|
||||
if ($offset >= $stopAt) {
|
||||
$this->logger->info('Building calendar reminder index done');
|
||||
} else {
|
||||
$this->jobList->add(self::class, [
|
||||
'offset' => $offset,
|
||||
'stopAt' => $stopAt
|
||||
]);
|
||||
$this->logger->info('Scheduled a new BuildReminderIndexBackgroundJob with offset ' . $offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $stopAt
|
||||
* @return int
|
||||
*/
|
||||
private function buildIndex(int $offset, int $stopAt):int {
|
||||
$startTime = $this->timeFactory->getTime();
|
||||
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from('calendarobjects')
|
||||
->where($query->expr()->lte('id', $query->createNamedParameter($stopAt)))
|
||||
->andWhere($query->expr()->gt('id', $query->createNamedParameter($offset)))
|
||||
->orderBy('id', 'ASC');
|
||||
|
||||
$stmt = $query->execute();
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$offset = $row['id'];
|
||||
if (is_resource($row['calendardata'])) {
|
||||
$row['calendardata'] = stream_get_contents($row['calendardata']);
|
||||
}
|
||||
$row['component'] = $row['componenttype'];
|
||||
|
||||
try {
|
||||
$this->reminderService->onTouchCalendarObject('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $row);
|
||||
} catch(\Exception $ex) {
|
||||
$this->logger->logException($ex);
|
||||
}
|
||||
|
||||
if (($this->timeFactory->getTime() - $startTime) > 15) {
|
||||
return $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return $stopAt;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\BackgroundJob;
|
||||
|
||||
use OC\BackgroundJob\TimedJob;
|
||||
use OCA\DAV\CalDAV\Reminder\ReminderService;
|
||||
use OCP\IConfig;
|
||||
|
||||
class EventReminderJob extends TimedJob {
|
||||
|
||||
/** @var ReminderService */
|
||||
private $reminderService;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* EventReminderJob constructor.
|
||||
*
|
||||
* @param ReminderService $reminderService
|
||||
* @param IConfig $config
|
||||
*/
|
||||
public function __construct(ReminderService $reminderService, IConfig $config) {
|
||||
$this->reminderService = $reminderService;
|
||||
$this->config = $config;
|
||||
/** Run every 5 minutes */
|
||||
$this->setInterval(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $arg
|
||||
* @throws \OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException
|
||||
* @throws \OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException
|
||||
* @throws \OC\User\NoUserException
|
||||
*/
|
||||
public function run($arg):void {
|
||||
if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') !== 'yes') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->config->getAppValue('dav', 'sendEventRemindersMode', 'backgroundjob') !== 'backgroundjob') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reminderService->processReminders();
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Citharel <nextcloud@tcit.fr>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
@@ -25,25 +23,35 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\DAV\BackgroundJob;
|
||||
|
||||
use DateInterval;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\DAV\Xml\Property\Href;
|
||||
use Sabre\VObject\Component;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\VObject\Splitter\ICalendar;
|
||||
|
||||
class RefreshWebcalJob extends Job {
|
||||
|
||||
/**
|
||||
* @var RefreshWebcalService
|
||||
*/
|
||||
private $refreshWebcalService;
|
||||
/** @var CalDavBackend */
|
||||
private $calDavBackend;
|
||||
|
||||
/**
|
||||
* @var IConfig
|
||||
*/
|
||||
/** @var IClientService */
|
||||
private $clientService;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var ILogger */
|
||||
@@ -52,16 +60,21 @@ class RefreshWebcalJob extends Job {
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/** @var array */
|
||||
private $subscription;
|
||||
|
||||
/**
|
||||
* RefreshWebcalJob constructor.
|
||||
*
|
||||
* @param RefreshWebcalService $refreshWebcalService
|
||||
* @param CalDavBackend $calDavBackend
|
||||
* @param IClientService $clientService
|
||||
* @param IConfig $config
|
||||
* @param ILogger $logger
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(RefreshWebcalService $refreshWebcalService, IConfig $config, ILogger $logger, ITimeFactory $timeFactory) {
|
||||
$this->refreshWebcalService = $refreshWebcalService;
|
||||
public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, ILogger $logger, ITimeFactory $timeFactory) {
|
||||
$this->calDavBackend = $calDavBackend;
|
||||
$this->clientService = $clientService;
|
||||
$this->config = $config;
|
||||
$this->logger = $logger;
|
||||
$this->timeFactory = $timeFactory;
|
||||
@@ -73,22 +86,18 @@ class RefreshWebcalJob extends Job {
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function execute($jobList, ILogger $logger = null) {
|
||||
$subscription = $this->refreshWebcalService->getSubscription($this->argument['principaluri'], $this->argument['uri']);
|
||||
$subscription = $this->getSubscription($this->argument['principaluri'], $this->argument['uri']);
|
||||
if (!$subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fixSubscriptionRowTyping($subscription);
|
||||
|
||||
// if no refresh rate was configured, just refresh once a week
|
||||
$defaultRefreshRate = $this->config->getAppValue('dav', 'calendarSubscriptionRefreshRate', 'P1W');
|
||||
$refreshRate = $subscription[RefreshWebcalService::REFRESH_RATE] ?? $defaultRefreshRate;
|
||||
|
||||
$subscriptionId = $subscription['id'];
|
||||
$refreshrate = $subscription['refreshrate'] ?? 'P1W';
|
||||
|
||||
try {
|
||||
/** @var DateInterval $dateInterval */
|
||||
$dateInterval = DateTimeParser::parseDuration($refreshRate);
|
||||
/** @var \DateInterval $dateInterval */
|
||||
$dateInterval = DateTimeParser::parseDuration($refreshrate);
|
||||
} catch(InvalidDataException $ex) {
|
||||
$this->logger->logException($ex);
|
||||
$this->logger->warning("Subscription $subscriptionId could not be refreshed, refreshrate in database is invalid");
|
||||
@@ -107,16 +116,243 @@ class RefreshWebcalJob extends Job {
|
||||
* @param array $argument
|
||||
*/
|
||||
protected function run($argument) {
|
||||
$this->refreshWebcalService->refreshSubscription($argument['principaluri'], $argument['uri']);
|
||||
$subscription = $this->getSubscription($argument['principaluri'], $argument['uri']);
|
||||
$mutations = [];
|
||||
if (!$subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webcalData = $this->queryWebcalFeed($subscription, $mutations);
|
||||
if (!$webcalData) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stripTodos = $subscription['striptodos'] ?? 1;
|
||||
$stripAlarms = $subscription['stripalarms'] ?? 1;
|
||||
$stripAttachments = $subscription['stripattachments'] ?? 1;
|
||||
|
||||
try {
|
||||
$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
|
||||
|
||||
// we wait with deleting all outdated events till we parsed the new ones
|
||||
// in case the new calendar is broken and `new ICalendar` throws a ParseException
|
||||
// the user will still see the old data
|
||||
$this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
|
||||
|
||||
while ($vObject = $splitter->getNext()) {
|
||||
/** @var Component $vObject */
|
||||
$uid = null;
|
||||
$compName = null;
|
||||
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
if ($component->name === 'VTIMEZONE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uid = $component->{'UID'}->getValue();
|
||||
$compName = $component->name;
|
||||
|
||||
if ($stripAlarms) {
|
||||
unset($component->{'VALARM'});
|
||||
}
|
||||
if ($stripAttachments) {
|
||||
unset($component->{'ATTACH'});
|
||||
}
|
||||
}
|
||||
|
||||
if ($stripTodos && $compName === 'VTODO') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uri = $uid . '.ics';
|
||||
$calendarData = $vObject->serialize();
|
||||
try {
|
||||
$this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
} catch(BadRequest $ex) {
|
||||
$this->logger->logException($ex);
|
||||
}
|
||||
}
|
||||
|
||||
$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
|
||||
if ($newRefreshRate) {
|
||||
$mutations['{http://apple.com/ns/ical/}refreshrate'] = $newRefreshRate;
|
||||
}
|
||||
|
||||
$this->updateSubscription($subscription, $mutations);
|
||||
} catch(ParseException $ex) {
|
||||
$subscriptionId = $subscription['id'];
|
||||
|
||||
$this->logger->logException($ex);
|
||||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a parsing error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gets webcal feed from remote server
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param array &$mutations
|
||||
* @return null|string
|
||||
*/
|
||||
private function queryWebcalFeed(array $subscription, array &$mutations) {
|
||||
$client = $this->clientService->newClient();
|
||||
|
||||
$didBreak301Chain = false;
|
||||
$latestLocation = null;
|
||||
|
||||
$handlerStack = HandlerStack::create();
|
||||
$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
|
||||
return $request
|
||||
->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
|
||||
->withHeader('User-Agent', 'Nextcloud Webcal Crawler');
|
||||
}));
|
||||
$handlerStack->push(Middleware::mapResponse(function(ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
|
||||
if (!$didBreak301Chain) {
|
||||
if ($response->getStatusCode() !== 301) {
|
||||
$didBreak301Chain = true;
|
||||
} else {
|
||||
$latestLocation = $response->getHeader('Location');
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}));
|
||||
|
||||
$allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
|
||||
$subscriptionId = $subscription['id'];
|
||||
$url = $this->cleanURL($subscription['source']);
|
||||
if ($url === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($allowLocalAccess !== 'yes') {
|
||||
$host = strtolower(parse_url($url, PHP_URL_HOST));
|
||||
// remove brackets from IPv6 addresses
|
||||
if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
|
||||
$host = substr($host, 1, -1);
|
||||
}
|
||||
|
||||
// Disallow localhost and local network
|
||||
if ($host === 'localhost' || substr($host, -6) === '.local' || substr($host, -10) === '.localhost') {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disallow hostname only
|
||||
if (substr_count($host, '.') === 0) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((bool)filter_var($host, FILTER_VALIDATE_IP) && !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Also check for IPv6 IPv4 nesting, because that's not covered by filter_var
|
||||
if ((bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && substr_count($host, '.') > 0) {
|
||||
$delimiter = strrpos($host, ':'); // Get last colon
|
||||
$ipv4Address = substr($host, $delimiter + 1);
|
||||
|
||||
if (!filter_var($ipv4Address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$params = [
|
||||
'allow_redirects' => [
|
||||
'redirects' => 10
|
||||
],
|
||||
'handler' => $handlerStack,
|
||||
];
|
||||
|
||||
$user = parse_url($subscription['source'], PHP_URL_USER);
|
||||
$pass = parse_url($subscription['source'], PHP_URL_PASS);
|
||||
if ($user !== null && $pass !== null) {
|
||||
$params['auth'] = [$user, $pass];
|
||||
}
|
||||
|
||||
$response = $client->get($url, $params);
|
||||
$body = $response->getBody();
|
||||
|
||||
if ($latestLocation) {
|
||||
$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
|
||||
}
|
||||
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
$contentType = explode(';', $contentType, 2)[0];
|
||||
switch($contentType) {
|
||||
case 'application/calendar+json':
|
||||
try {
|
||||
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
|
||||
} catch(\Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $jCalendar->serialize();
|
||||
|
||||
case 'application/calendar+xml':
|
||||
try {
|
||||
$xCalendar = Reader::readXML($body);
|
||||
} catch(\Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $xCalendar->serialize();
|
||||
|
||||
case 'text/calendar':
|
||||
default:
|
||||
try {
|
||||
$vCalendar = Reader::read($body);
|
||||
} catch(\Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $vCalendar->serialize();
|
||||
}
|
||||
} catch(\Exception $ex) {
|
||||
$this->logger->logException($ex);
|
||||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads subscription from backend
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $uri
|
||||
* @return array|null
|
||||
*/
|
||||
private function getSubscription(string $principalUri, string $uri) {
|
||||
$subscriptions = array_values(array_filter(
|
||||
$this->calDavBackend->getSubscriptionsForUser($principalUri),
|
||||
function($sub) use ($uri) {
|
||||
return $sub['uri'] === $uri;
|
||||
}
|
||||
));
|
||||
|
||||
if (\count($subscriptions) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->subscription = $subscriptions[0];
|
||||
return $this->subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* get total number of seconds from DateInterval object
|
||||
*
|
||||
* @param DateInterval $interval
|
||||
* @param \DateInterval $interval
|
||||
* @return int
|
||||
*/
|
||||
private function getIntervalFromDateInterval(DateInterval $interval):int {
|
||||
private function getIntervalFromDateInterval(\DateInterval $interval):int {
|
||||
return $interval->s
|
||||
+ ($interval->i * 60)
|
||||
+ ($interval->h * 60 * 60)
|
||||
@@ -126,23 +362,99 @@ class RefreshWebcalJob extends Job {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes types of rows
|
||||
* check if:
|
||||
* - current subscription stores a refreshrate
|
||||
* - the webcal feed suggests a refreshrate
|
||||
* - return suggested refreshrate if user didn't set a custom one
|
||||
*
|
||||
* @param array $row
|
||||
* @param array $subscription
|
||||
* @param string $webcalData
|
||||
* @return string|null
|
||||
*/
|
||||
private function fixSubscriptionRowTyping(array &$row):void {
|
||||
$forceInt = [
|
||||
'id',
|
||||
'lastmodified',
|
||||
RefreshWebcalService::STRIP_ALARMS,
|
||||
RefreshWebcalService::STRIP_ATTACHMENTS,
|
||||
RefreshWebcalService::STRIP_TODOS,
|
||||
];
|
||||
|
||||
foreach($forceInt as $column) {
|
||||
if (isset($row[$column])) {
|
||||
$row[$column] = (int) $row[$column];
|
||||
}
|
||||
private function checkWebcalDataForRefreshRate($subscription, $webcalData) {
|
||||
// if there is no refreshrate stored in the database, check the webcal feed
|
||||
// whether it suggests any refresh rate and store that in the database
|
||||
if (isset($subscription['refreshrate']) && $subscription['refreshrate'] !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Component\VCalendar $vCalendar */
|
||||
$vCalendar = Reader::read($webcalData);
|
||||
|
||||
$newRefreshrate = null;
|
||||
if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
|
||||
$newRefreshrate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
|
||||
}
|
||||
if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
|
||||
$newRefreshrate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
|
||||
}
|
||||
|
||||
if (!$newRefreshrate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if new refresh rate is even valid
|
||||
try {
|
||||
DateTimeParser::parseDuration($newRefreshrate);
|
||||
} catch(InvalidDataException $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $newRefreshrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* update subscription stored in database
|
||||
* used to set:
|
||||
* - refreshrate
|
||||
* - source
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param array $mutations
|
||||
*/
|
||||
private function updateSubscription(array $subscription, array $mutations) {
|
||||
if (empty($mutations)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propPatch = new PropPatch($mutations);
|
||||
$this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
|
||||
$propPatch->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will strip authentication information and replace the
|
||||
* 'webcal' or 'webcals' protocol scheme
|
||||
*
|
||||
* @param string $url
|
||||
* @return string|null
|
||||
*/
|
||||
private function cleanURL(string $url) {
|
||||
$parsed = parse_url($url);
|
||||
if ($parsed === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
|
||||
$scheme = 'http';
|
||||
} else {
|
||||
$scheme = 'https';
|
||||
}
|
||||
|
||||
$host = $parsed['host'] ?? '';
|
||||
$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
|
||||
$path = $parsed['path'] ?? '';
|
||||
$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
|
||||
$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
|
||||
|
||||
$cleanURL = "$scheme://$host$port$path$query$fragment";
|
||||
// parse_url is giving some weird results if no url and no :// is given,
|
||||
// so let's test the url again
|
||||
$parsedClean = parse_url($cleanURL);
|
||||
if ($parsedClean === false || !isset($parsedClean['host'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $cleanURL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright 2019, Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2018, Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
@@ -26,8 +26,6 @@ namespace OCA\DAV\BackgroundJob;
|
||||
use OC\BackgroundJob\TimedJob;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\Calendar\BackendTemporarilyUnavailableException;
|
||||
use OCP\Calendar\IMetadataProvider;
|
||||
use OCP\Calendar\Resource\IBackend as IResourceBackend;
|
||||
use OCP\Calendar\Resource\IManager as IResourceManager;
|
||||
use OCP\Calendar\Resource\IResource;
|
||||
use OCP\Calendar\Room\IManager as IRoomManager;
|
||||
@@ -43,11 +41,23 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
private $roomManager;
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $dbConnection;
|
||||
private $db;
|
||||
|
||||
/** @var CalDavBackend */
|
||||
private $calDavBackend;
|
||||
|
||||
/** @var string */
|
||||
private $resourceDbTable;
|
||||
|
||||
/** @var string */
|
||||
private $resourcePrincipalUri;
|
||||
|
||||
/** @var string */
|
||||
private $roomDbTable;
|
||||
|
||||
/** @var string */
|
||||
private $roomPrincipalUri;
|
||||
|
||||
/**
|
||||
* UpdateCalendarResourcesRoomsBackgroundJob constructor.
|
||||
*
|
||||
@@ -56,14 +66,16 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
* @param IDBConnection $dbConnection
|
||||
* @param CalDavBackend $calDavBackend
|
||||
*/
|
||||
public function __construct(IResourceManager $resourceManager,
|
||||
IRoomManager $roomManager,
|
||||
IDBConnection $dbConnection,
|
||||
CalDavBackend $calDavBackend) {
|
||||
public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager,
|
||||
IDBConnection $dbConnection, CalDavBackend $calDavBackend) {
|
||||
$this->resourceManager = $resourceManager;
|
||||
$this->roomManager = $roomManager;
|
||||
$this->dbConnection = $dbConnection;
|
||||
$this->db = $dbConnection;
|
||||
$this->calDavBackend = $calDavBackend;
|
||||
$this->resourceDbTable = 'calendar_resources';
|
||||
$this->resourcePrincipalUri = 'principals/calendar-resources';
|
||||
$this->roomDbTable = 'calendar_rooms';
|
||||
$this->roomPrincipalUri = 'principals/calendar-rooms';
|
||||
|
||||
// run once an hour
|
||||
$this->setInterval(60 * 60);
|
||||
@@ -72,132 +84,211 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
/**
|
||||
* @param $argument
|
||||
*/
|
||||
public function run($argument):void {
|
||||
$this->runForBackend(
|
||||
$this->resourceManager,
|
||||
'calendar_resources',
|
||||
'calendar_resources_md',
|
||||
'resource_id',
|
||||
'principals/calendar-resources'
|
||||
);
|
||||
$this->runForBackend(
|
||||
$this->roomManager,
|
||||
'calendar_rooms',
|
||||
'calendar_rooms_md',
|
||||
'room_id',
|
||||
'principals/calendar-rooms'
|
||||
);
|
||||
public function run($argument) {
|
||||
$this->runResources();
|
||||
$this->runRooms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run background-job for one specific backendManager
|
||||
* either ResourceManager or RoomManager
|
||||
*
|
||||
* @param IResourceManager|IRoomManager $backendManager
|
||||
* @param string $dbTable
|
||||
* @param string $dbTableMetadata
|
||||
* @param string $foreignKey
|
||||
* @param string $principalPrefix
|
||||
* run timed job for resources
|
||||
*/
|
||||
private function runForBackend($backendManager,
|
||||
string $dbTable,
|
||||
string $dbTableMetadata,
|
||||
string $foreignKey,
|
||||
string $principalPrefix):void {
|
||||
$backends = $backendManager->getBackends();
|
||||
|
||||
foreach($backends as $backend) {
|
||||
$backendId = $backend->getBackendIdentifier();
|
||||
private function runResources() {
|
||||
$resourceBackends = $this->resourceManager->getBackends();
|
||||
$cachedResources = $this->getCached($this->resourceDbTable);
|
||||
$cachedResourceIds = $this->getCachedResourceIds($cachedResources);
|
||||
|
||||
$remoteResourceIds = [];
|
||||
foreach($resourceBackends as $resourceBackend) {
|
||||
try {
|
||||
if ($backend instanceof IResourceBackend) {
|
||||
$list = $backend->listAllResources();
|
||||
} else {
|
||||
$list = $backend->listAllRooms();
|
||||
}
|
||||
$remoteResourceIds[$resourceBackend->getBackendIdentifier()] =
|
||||
$resourceBackend->listAllResources();
|
||||
} catch(BackendTemporarilyUnavailableException $ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
|
||||
$newIds = array_diff($list, $cachedList);
|
||||
$deletedIds = array_diff($cachedList, $list);
|
||||
$editedIds = array_intersect($list, $cachedList);
|
||||
|
||||
foreach($newIds as $newId) {
|
||||
try {
|
||||
if ($backend instanceof IResourceBackend) {
|
||||
$resource = $backend->getResource($newId);
|
||||
} else {
|
||||
$resource = $backend->getRoom($newId);
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
if ($resource instanceof IMetadataProvider) {
|
||||
$metadata = $this->getAllMetadataOfBackend($resource);
|
||||
}
|
||||
} catch(BackendTemporarilyUnavailableException $ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $this->addToCache($dbTable, $backendId, $resource);
|
||||
$this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
|
||||
// we don't create the calendar here, it is created lazily
|
||||
// when an event is actually scheduled with this resource / room
|
||||
}
|
||||
|
||||
foreach($deletedIds as $deletedId) {
|
||||
$id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
|
||||
$this->deleteFromCache($dbTable, $id);
|
||||
$this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
|
||||
|
||||
$principalName = implode('-', [$backendId, $deletedId]);
|
||||
$this->deleteCalendarDataForResource($principalPrefix, $principalName);
|
||||
}
|
||||
|
||||
foreach($editedIds as $editedId) {
|
||||
$id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
|
||||
|
||||
try {
|
||||
if ($backend instanceof IResourceBackend) {
|
||||
$resource = $backend->getResource($editedId);
|
||||
} else {
|
||||
$resource = $backend->getRoom($editedId);
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
if ($resource instanceof IMetadataProvider) {
|
||||
$metadata = $this->getAllMetadataOfBackend($resource);
|
||||
}
|
||||
} catch(BackendTemporarilyUnavailableException $ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->updateCache($dbTable, $id, $resource);
|
||||
|
||||
if ($resource instanceof IMetadataProvider) {
|
||||
$cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
|
||||
$this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
|
||||
}
|
||||
// If the backend is temporarily unavailable
|
||||
// ignore this backend in this execution
|
||||
unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]);
|
||||
}
|
||||
}
|
||||
|
||||
$sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds);
|
||||
|
||||
foreach($sortedResources['new'] as $backendId => $newResources) {
|
||||
foreach ($newResources as $newResource) {
|
||||
$backend = $this->resourceManager->getBackend($backendId);
|
||||
if ($backend === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource = $backend->getResource($newResource);
|
||||
$this->addToCache($this->resourceDbTable, $resource);
|
||||
}
|
||||
}
|
||||
foreach($sortedResources['deleted'] as $backendId => $deletedResources) {
|
||||
foreach ($deletedResources as $deletedResource) {
|
||||
$this->deleteFromCache($this->resourceDbTable,
|
||||
$this->resourcePrincipalUri, $backendId, $deletedResource);
|
||||
}
|
||||
}
|
||||
foreach($sortedResources['edited'] as $backendId => $editedResources) {
|
||||
foreach ($editedResources as $editedResource) {
|
||||
$backend = $this->resourceManager->getBackend($backendId);
|
||||
if ($backend === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource = $backend->getResource($editedResource);
|
||||
$this->updateCache($this->resourceDbTable, $resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* run timed job for rooms
|
||||
*/
|
||||
private function runRooms() {
|
||||
$roomBackends = $this->roomManager->getBackends();
|
||||
$cachedRooms = $this->getCached($this->roomDbTable);
|
||||
$cachedRoomIds = $this->getCachedRoomIds($cachedRooms);
|
||||
|
||||
$remoteRoomIds = [];
|
||||
foreach($roomBackends as $roomBackend) {
|
||||
try {
|
||||
$remoteRoomIds[$roomBackend->getBackendIdentifier()] =
|
||||
$roomBackend->listAllRooms();
|
||||
} catch(BackendTemporarilyUnavailableException $ex) {
|
||||
// If the backend is temporarily unavailable
|
||||
// ignore this backend in this execution
|
||||
unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]);
|
||||
}
|
||||
}
|
||||
|
||||
$sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds);
|
||||
|
||||
foreach($sortedRooms['new'] as $backendId => $newRooms) {
|
||||
foreach ($newRooms as $newRoom) {
|
||||
$backend = $this->roomManager->getBackend($backendId);
|
||||
if ($backend === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource = $backend->getRoom($newRoom);
|
||||
$this->addToCache($this->roomDbTable, $resource);
|
||||
}
|
||||
}
|
||||
foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) {
|
||||
foreach ($deletedRooms as $deletedRoom) {
|
||||
$this->deleteFromCache($this->roomDbTable,
|
||||
$this->roomPrincipalUri, $backendId, $deletedRoom);
|
||||
}
|
||||
}
|
||||
foreach($sortedRooms['edited'] as $backendId => $editedRooms) {
|
||||
foreach ($editedRooms as $editedRoom) {
|
||||
$backend = $this->roomManager->getBackend($backendId);
|
||||
if ($backend === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource = $backend->getRoom($editedRoom);
|
||||
$this->updateCache($this->roomDbTable, $resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get cached db rows for resources / rooms
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
private function getCached($tableName):array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')->from($tableName);
|
||||
|
||||
$rows = [];
|
||||
$stmt = $query->execute();
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cachedResources
|
||||
* @return array
|
||||
*/
|
||||
private function getCachedResourceIds(array $cachedResources):array {
|
||||
$cachedResourceIds = [];
|
||||
foreach ($cachedResources as $cachedResource) {
|
||||
if (!isset($cachedResourceIds[$cachedResource['backend_id']])) {
|
||||
$cachedResourceIds[$cachedResource['backend_id']] = [];
|
||||
}
|
||||
|
||||
$cachedResourceIds[$cachedResource['backend_id']][] =
|
||||
$cachedResource['resource_id'];
|
||||
}
|
||||
|
||||
return $cachedResourceIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cachedRooms
|
||||
* @return array
|
||||
*/
|
||||
private function getCachedRoomIds(array $cachedRooms):array {
|
||||
$cachedRoomIds = [];
|
||||
foreach ($cachedRooms as $cachedRoom) {
|
||||
if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) {
|
||||
$cachedRoomIds[$cachedRoom['backend_id']] = [];
|
||||
}
|
||||
|
||||
$cachedRoomIds[$cachedRoom['backend_id']][] =
|
||||
$cachedRoom['resource_id'];
|
||||
}
|
||||
|
||||
return $cachedRoomIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort list of ids by whether they appear only in the backend /
|
||||
* only in the cache / in both
|
||||
*
|
||||
* @param array $cached
|
||||
* @param array $remote
|
||||
* @return array
|
||||
*/
|
||||
private function sortByNewDeletedExisting(array $cached, array $remote):array {
|
||||
$sorted = [
|
||||
'new' => [],
|
||||
'deleted' => [],
|
||||
'edited' => [],
|
||||
];
|
||||
|
||||
$backendIds = array_merge(array_keys($cached), array_keys($remote));
|
||||
foreach($backendIds as $backendId) {
|
||||
if (!isset($cached[$backendId])) {
|
||||
$sorted['new'][$backendId] = $remote[$backendId];
|
||||
} elseif (!isset($remote[$backendId])) {
|
||||
$sorted['deleted'][$backendId] = $cached[$backendId];
|
||||
} else {
|
||||
$sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]);
|
||||
$sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]);
|
||||
$sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* add entry to cache that exists remotely but not yet in cache
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $backendId
|
||||
* @param IResource|IRoom $remote
|
||||
* @return int Insert id
|
||||
*/
|
||||
private function addToCache(string $table,
|
||||
string $backendId,
|
||||
$remote):int {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
private function addToCache($table, $remote) {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->insert($table)
|
||||
->values([
|
||||
'backend_id' => $query->createNamedParameter($backendId),
|
||||
'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()),
|
||||
'resource_id' => $query->createNamedParameter($remote->getId()),
|
||||
'email' => $query->createNamedParameter($remote->getEMail()),
|
||||
'displayname' => $query->createNamedParameter($remote->getDisplayName()),
|
||||
@@ -207,70 +298,37 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
))
|
||||
])
|
||||
->execute();
|
||||
return $query->getLastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $foreignKey
|
||||
* @param int $foreignId
|
||||
* @param array $metadata
|
||||
*/
|
||||
private function addMetadataToCache(string $table,
|
||||
string $foreignKey,
|
||||
int $foreignId,
|
||||
array $metadata):void {
|
||||
foreach($metadata as $key => $value) {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->insert($table)
|
||||
->values([
|
||||
$foreignKey => $query->createNamedParameter($foreignId),
|
||||
'key' => $query->createNamedParameter($key),
|
||||
'value' => $query->createNamedParameter($value),
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete entry from cache that does not exist anymore remotely
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $id
|
||||
* @param string $principalUri
|
||||
* @param string $backendId
|
||||
* @param string $resourceId
|
||||
*/
|
||||
private function deleteFromCache(string $table,
|
||||
int $id):void {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
private function deleteFromCache($table, $principalUri, $backendId, $resourceId) {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->delete($table)
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
|
||||
->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
|
||||
->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $foreignKey
|
||||
* @param int $id
|
||||
*/
|
||||
private function deleteMetadataFromCache(string $table,
|
||||
string $foreignKey,
|
||||
int $id):void {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->delete($table)
|
||||
->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
|
||||
->execute();
|
||||
$calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId]));
|
||||
if ($calendar !== null) {
|
||||
$this->calDavBackend->deleteCalendar($calendar['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update an existing entry in cache
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $id
|
||||
* @param IResource|IRoom $remote
|
||||
*/
|
||||
private function updateCache(string $table,
|
||||
int $id,
|
||||
$remote):void {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
private function updateCache($table, $remote) {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->update($table)
|
||||
->set('email', $query->createNamedParameter($remote->getEMail()))
|
||||
->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
|
||||
@@ -278,57 +336,11 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
$this->serializeGroupRestrictions(
|
||||
$remote->getGroupRestrictions()
|
||||
)))
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
|
||||
->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier())))
|
||||
->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId())))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dbTable
|
||||
* @param string $foreignKey
|
||||
* @param int $id
|
||||
* @param array $metadata
|
||||
* @param array $cachedMetadata
|
||||
*/
|
||||
private function updateMetadataCache(string $dbTable,
|
||||
string $foreignKey,
|
||||
int $id,
|
||||
array $metadata,
|
||||
array $cachedMetadata):void {
|
||||
$newMetadata = array_diff_key($metadata, $cachedMetadata);
|
||||
$deletedMetadata = array_diff_key($cachedMetadata, $metadata);
|
||||
|
||||
foreach ($newMetadata as $key => $value) {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->insert($dbTable)
|
||||
->values([
|
||||
$foreignKey => $query->createNamedParameter($id),
|
||||
'key' => $query->createNamedParameter($key),
|
||||
'value' => $query->createNamedParameter($value),
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
foreach($deletedMetadata as $key => $value) {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->delete($dbTable)
|
||||
->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
|
||||
->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
$existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
|
||||
foreach($existingKeys as $existingKey) {
|
||||
if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->update($dbTable)
|
||||
->set('value', $query->createNamedParameter($metadata[$existingKey]))
|
||||
->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
|
||||
->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* serialize array of group restrictions to store them in database
|
||||
*
|
||||
@@ -338,102 +350,4 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
|
||||
private function serializeGroupRestrictions(array $groups):string {
|
||||
return \json_encode($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all metadata of a backend
|
||||
*
|
||||
* @param IResource|IRoom $resource
|
||||
* @return array
|
||||
*/
|
||||
private function getAllMetadataOfBackend($resource):array {
|
||||
if (!($resource instanceof IMetadataProvider)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = $resource->getAllAvailableMetadataKeys();
|
||||
$metadata = [];
|
||||
foreach($keys as $key) {
|
||||
$metadata[$key] = $resource->getMetadataForKey($key);
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $foreignKey
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
private function getAllMetadataOfCache(string $table,
|
||||
string $foreignKey,
|
||||
int $id):array {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->select(['key', 'value'])
|
||||
->from($table)
|
||||
->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
|
||||
$stmt = $query->execute();
|
||||
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$metadata = [];
|
||||
foreach($rows as $row) {
|
||||
$metadata[$row['key']] = $row['value'];
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all cached rooms / resources by backend
|
||||
*
|
||||
* @param $tableName
|
||||
* @param $backendId
|
||||
* @return array
|
||||
*/
|
||||
private function getAllCachedByBackend(string $tableName,
|
||||
string $backendId):array {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->select('resource_id')
|
||||
->from($tableName)
|
||||
->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
|
||||
$stmt = $query->execute();
|
||||
|
||||
return array_map(function($row) {
|
||||
return $row['resource_id'];
|
||||
}, $stmt->fetchAll(\PDO::FETCH_NAMED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $principalPrefix
|
||||
* @param $principalUri
|
||||
*/
|
||||
private function deleteCalendarDataForResource(string $principalPrefix,
|
||||
string $principalUri):void {
|
||||
$calendar = $this->calDavBackend->getCalendarByUri(
|
||||
implode('/', [$principalPrefix, $principalUri]),
|
||||
CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
|
||||
|
||||
if ($calendar !== null) {
|
||||
$this->calDavBackend->deleteCalendar($calendar['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param $backendId
|
||||
* @param $resourceId
|
||||
* @return int
|
||||
*/
|
||||
private function getIdForBackendAndResource(string $table,
|
||||
string $backendId,
|
||||
string $resourceId):int {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->select('id')
|
||||
->from($table)
|
||||
->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
|
||||
->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
|
||||
$stmt = $query->execute();
|
||||
|
||||
return $stmt->fetch(\PDO::FETCH_NAMED)['id'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
* @copyright Copyright (c) 2016, Georg Ehrke
|
||||
*
|
||||
* @author Achim Königs <garfonso@tratschtante.de>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
@@ -34,7 +33,6 @@ use OCA\DAV\CardDAV\CardDavBackend;
|
||||
use OCA\DAV\DAV\GroupPrincipalBackend;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VCard;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
@@ -43,11 +41,6 @@ use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\Property\VCard\DateAndOrTime;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
/**
|
||||
* Class BirthdayService
|
||||
*
|
||||
* @package OCA\DAV\CalDAV
|
||||
*/
|
||||
class BirthdayService {
|
||||
|
||||
const BIRTHDAY_CALENDAR_URI = 'contact_birthdays';
|
||||
@@ -67,31 +60,20 @@ class BirthdayService {
|
||||
/** @var IDBConnection */
|
||||
private $dbConnection;
|
||||
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/**
|
||||
* BirthdayService constructor.
|
||||
*
|
||||
* @param CalDavBackend $calDavBackEnd
|
||||
* @param CardDavBackend $cardDavBackEnd
|
||||
* @param GroupPrincipalBackend $principalBackend
|
||||
* @param IConfig $config
|
||||
* @param IDBConnection $dbConnection
|
||||
* @param IL10N $l10n
|
||||
* @param IConfig $config;
|
||||
*/
|
||||
public function __construct(CalDavBackend $calDavBackEnd,
|
||||
CardDavBackend $cardDavBackEnd,
|
||||
GroupPrincipalBackend $principalBackend,
|
||||
IConfig $config,
|
||||
IDBConnection $dbConnection,
|
||||
IL10N $l10n) {
|
||||
public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend, IConfig $config, IDBConnection $dbConnection) {
|
||||
$this->calDavBackEnd = $calDavBackEnd;
|
||||
$this->cardDavBackEnd = $cardDavBackEnd;
|
||||
$this->principalBackend = $principalBackend;
|
||||
$this->config = $config;
|
||||
$this->dbConnection = $dbConnection;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +81,7 @@ class BirthdayService {
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
*/
|
||||
public function onCardChanged(int $addressBookId,
|
||||
string $cardUri,
|
||||
string $cardData) {
|
||||
public function onCardChanged($addressBookId, $cardUri, $cardData) {
|
||||
if (!$this->isGloballyEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -110,11 +90,10 @@ class BirthdayService {
|
||||
$book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
|
||||
$targetPrincipals[] = $book['principaluri'];
|
||||
$datesToSync = [
|
||||
['postfix' => '', 'field' => 'BDAY'],
|
||||
['postfix' => '-death', 'field' => 'DEATHDATE'],
|
||||
['postfix' => '-anniversary', 'field' => 'ANNIVERSARY'],
|
||||
['postfix' => '', 'field' => 'BDAY', 'symbol' => '*', 'utfSymbol' => '🎂'],
|
||||
['postfix' => '-death', 'field' => 'DEATHDATE', 'symbol' => "†", 'utfSymbol' => '⚰️'],
|
||||
['postfix' => '-anniversary', 'field' => 'ANNIVERSARY', 'symbol' => "⚭", 'utfSymbol' => '💍'],
|
||||
];
|
||||
|
||||
foreach ($targetPrincipals as $principalUri) {
|
||||
if (!$this->isUserEnabled($principalUri)) {
|
||||
continue;
|
||||
@@ -122,7 +101,7 @@ class BirthdayService {
|
||||
|
||||
$calendar = $this->ensureCalendarExists($principalUri);
|
||||
foreach ($datesToSync as $type) {
|
||||
$this->updateCalendar($cardUri, $cardData, $book, (int) $calendar['id'], $type);
|
||||
$this->updateCalendar($cardUri, $cardData, $book, $calendar['id'], $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,8 +110,7 @@ class BirthdayService {
|
||||
* @param int $addressBookId
|
||||
* @param string $cardUri
|
||||
*/
|
||||
public function onCardDeleted(int $addressBookId,
|
||||
string $cardUri) {
|
||||
public function onCardDeleted($addressBookId, $cardUri) {
|
||||
if (!$this->isGloballyEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +136,7 @@ class BirthdayService {
|
||||
* @return array|null
|
||||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
*/
|
||||
public function ensureCalendarExists(string $principal):?array {
|
||||
public function ensureCalendarExists($principal) {
|
||||
$calendar = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI);
|
||||
if (!is_null($calendar)) {
|
||||
return $calendar;
|
||||
@@ -173,15 +151,14 @@ class BirthdayService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cardData
|
||||
* @param $dateField
|
||||
* @param $postfix
|
||||
* @return VCalendar|null
|
||||
* @throws InvalidDataException
|
||||
* @param string $cardData
|
||||
* @param string $dateField
|
||||
* @param string $postfix
|
||||
* @param string $summarySymbol
|
||||
* @param string $utfSummarySymbol
|
||||
* @return null|VCalendar
|
||||
*/
|
||||
public function buildDateFromContact(string $cardData,
|
||||
string $dateField,
|
||||
string $postfix):?VCalendar {
|
||||
public function buildDateFromContact($cardData, $dateField, $postfix, $summarySymbol, $utfSummarySymbol) {
|
||||
if (empty($cardData)) {
|
||||
return null;
|
||||
}
|
||||
@@ -243,16 +220,23 @@ class BirthdayService {
|
||||
}
|
||||
|
||||
try {
|
||||
if ($birthday instanceof DateAndOrTime) {
|
||||
$date = $birthday->getDateTime();
|
||||
} else {
|
||||
$date = new \DateTimeImmutable($birthday);
|
||||
}
|
||||
$date = new \DateTime($birthday);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$summary = $this->formatTitle($dateField, $doc->FN->getValue(), $originalYear, $this->dbConnection->supports4ByteText());
|
||||
if ($this->dbConnection->supports4ByteText()) {
|
||||
if ($unknownYear) {
|
||||
$summary = $utfSummarySymbol . ' ' . $doc->FN->getValue();
|
||||
} else {
|
||||
$summary = $utfSummarySymbol . ' ' . $doc->FN->getValue() . " ($originalYear)";
|
||||
}
|
||||
} else {
|
||||
if ($unknownYear) {
|
||||
$summary = $doc->FN->getValue() . ' ' . $summarySymbol;
|
||||
} else {
|
||||
$summary = $doc->FN->getValue() . " ($summarySymbol$originalYear)";
|
||||
}
|
||||
}
|
||||
|
||||
$vCal = new VCalendar();
|
||||
$vCal->VERSION = '2.0';
|
||||
@@ -263,13 +247,10 @@ class BirthdayService {
|
||||
);
|
||||
$vEvent->DTSTART['VALUE'] = 'DATE';
|
||||
$vEvent->add('DTEND');
|
||||
|
||||
$dtEndDate = (new \DateTime())->setTimestamp($date->getTimeStamp());
|
||||
$dtEndDate->add(new \DateInterval('P1D'));
|
||||
$date->add(new \DateInterval('P1D'));
|
||||
$vEvent->DTEND->setDateTime(
|
||||
$dtEndDate
|
||||
$date
|
||||
);
|
||||
|
||||
$vEvent->DTEND['VALUE'] = 'DATE';
|
||||
$vEvent->{'UID'} = $doc->UID . $postfix;
|
||||
$vEvent->{'RRULE'} = 'FREQ=YEARLY';
|
||||
@@ -292,7 +273,7 @@ class BirthdayService {
|
||||
/**
|
||||
* @param string $user
|
||||
*/
|
||||
public function resetForUser(string $user):void {
|
||||
public function resetForUser($user) {
|
||||
$principal = 'principals/users/'.$user;
|
||||
$calendar = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI);
|
||||
$calendarObjects = $this->calDavBackEnd->getCalendarObjects($calendar['id'], CalDavBackend::CALENDAR_TYPE_CALENDAR);
|
||||
@@ -304,16 +285,15 @@ class BirthdayService {
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
*/
|
||||
public function syncUser(string $user):void {
|
||||
public function syncUser($user) {
|
||||
$principal = 'principals/users/'.$user;
|
||||
$this->ensureCalendarExists($principal);
|
||||
$books = $this->cardDavBackEnd->getAddressBooksForUser($principal);
|
||||
foreach($books as $book) {
|
||||
$cards = $this->cardDavBackEnd->getCards($book['id']);
|
||||
foreach($cards as $card) {
|
||||
$this->onCardChanged((int) $book['id'], $card['uri'], $card['carddata']);
|
||||
$this->onCardChanged($book['id'], $card['uri'], $card['carddata']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,25 +303,25 @@ class BirthdayService {
|
||||
* @param VCalendar $newCalendarData
|
||||
* @return bool
|
||||
*/
|
||||
public function birthdayEvenChanged(string $existingCalendarData,
|
||||
VCalendar $newCalendarData):bool {
|
||||
public function birthdayEvenChanged($existingCalendarData, $newCalendarData) {
|
||||
try {
|
||||
$existingBirthday = Reader::read($existingCalendarData);
|
||||
} catch (Exception $ex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
$newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() ||
|
||||
if ($newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() ||
|
||||
$newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue()
|
||||
);
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $addressBookId
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getAllAffectedPrincipals(int $addressBookId) {
|
||||
protected function getAllAffectedPrincipals($addressBookId) {
|
||||
$targetPrincipals = [];
|
||||
$shares = $this->cardDavBackEnd->getShares($addressBookId);
|
||||
foreach ($shares as $share) {
|
||||
@@ -359,20 +339,14 @@ class BirthdayService {
|
||||
|
||||
/**
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @param string $cardData
|
||||
* @param array $book
|
||||
* @param int $calendarId
|
||||
* @param array $type
|
||||
* @throws InvalidDataException
|
||||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
* @param string[] $type
|
||||
*/
|
||||
private function updateCalendar(string $cardUri,
|
||||
string $cardData,
|
||||
array $book,
|
||||
int $calendarId,
|
||||
array $type):void {
|
||||
private function updateCalendar($cardUri, $cardData, $book, $calendarId, $type) {
|
||||
$objectUri = $book['uri'] . '-' . $cardUri . $type['postfix'] . '.ics';
|
||||
$calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['postfix']);
|
||||
$calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['postfix'], $type['symbol'], $type['utfSymbol']);
|
||||
$existing = $this->calDavBackEnd->getCalendarObject($calendarId, $objectUri);
|
||||
if (is_null($calendarData)) {
|
||||
if (!is_null($existing)) {
|
||||
@@ -394,17 +368,18 @@ class BirthdayService {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isGloballyEnabled():bool {
|
||||
return $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes') === 'yes';
|
||||
private function isGloballyEnabled() {
|
||||
$isGloballyEnabled = $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes');
|
||||
return $isGloballyEnabled === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user opted-out of birthday calendars
|
||||
* checks if the user opted-out of birthday calendars
|
||||
*
|
||||
* @param string $userPrincipal The user principal to check for
|
||||
* @param $userPrincipal
|
||||
* @return bool
|
||||
*/
|
||||
private function isUserEnabled(string $userPrincipal):bool {
|
||||
private function isUserEnabled($userPrincipal) {
|
||||
if (strpos($userPrincipal, 'principals/users/') === 0) {
|
||||
$userId = substr($userPrincipal, 17);
|
||||
$isEnabled = $this->config->getUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes');
|
||||
@@ -415,69 +390,4 @@ class BirthdayService {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats title of Birthday event
|
||||
*
|
||||
* @param string $field Field name like BDAY, ANNIVERSARY, ...
|
||||
* @param string $name Name of contact
|
||||
* @param int|null $year Year of birth, anniversary, ...
|
||||
* @param bool $supports4Byte Whether or not the database supports 4 byte chars
|
||||
* @return string The formatted title
|
||||
*/
|
||||
private function formatTitle(string $field,
|
||||
string $name,
|
||||
int $year=null,
|
||||
bool $supports4Byte=true):string {
|
||||
if ($supports4Byte) {
|
||||
switch ($field) {
|
||||
case 'BDAY':
|
||||
return implode('', [
|
||||
'🎂 ',
|
||||
$name,
|
||||
$year ? (' (' . $year . ')') : '',
|
||||
]);
|
||||
|
||||
case 'DEATHDATE':
|
||||
return implode('', [
|
||||
$this->l10n->t('Death of %s', [$name]),
|
||||
$year ? (' (' . $year . ')') : '',
|
||||
]);
|
||||
|
||||
case 'ANNIVERSARY':
|
||||
return implode('', [
|
||||
'💍 ',
|
||||
$name,
|
||||
$year ? (' (' . $year . ')') : '',
|
||||
]);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
switch($field) {
|
||||
case 'BDAY':
|
||||
return implode('', [
|
||||
$name,
|
||||
' ',
|
||||
$year ? ('(*' . $year . ')') : '*',
|
||||
]);
|
||||
|
||||
case 'DEATHDATE':
|
||||
return implode('', [
|
||||
$this->l10n->t('Death of %s', [$name]),
|
||||
$year ? (' (' . $year . ')') : '',
|
||||
]);
|
||||
|
||||
case 'ANNIVERSARY':
|
||||
return implode('', [
|
||||
$name,
|
||||
' ',
|
||||
$year ? ('(⚭' . $year . ')') : '⚭',
|
||||
]);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use Sabre\CalDAV\Backend\BackendInterface;
|
||||
use Sabre\DAV\Exception\MethodNotAllowed;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
@@ -196,15 +195,4 @@ class CachedSubscription extends \Sabre\CalDAV\Calendar {
|
||||
public function calendarQuery(array $filters):array {
|
||||
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
return parent::getChanges($syncToken, $syncLevel, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
ISecureRandom $random,
|
||||
ILogger $logger,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
bool $legacyEndpoint = false) {
|
||||
$legacyEndpoint = false) {
|
||||
$this->db = $db;
|
||||
$this->principalBackend = $principalBackend;
|
||||
$this->userManager = $userManager;
|
||||
@@ -1136,6 +1136,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
*/
|
||||
function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType=self::CALENDAR_TYPE_CALENDAR) {
|
||||
$extraData = $this->getDenormalizedData($calendarData);
|
||||
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->update('calendarobjects')
|
||||
->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\DAV\Sharing\IShareable;
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Sabre\CalDAV\Backend\BackendInterface;
|
||||
@@ -47,14 +45,6 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Calendar constructor.
|
||||
*
|
||||
* @param BackendInterface $caldavBackend
|
||||
* @param $calendarInfo
|
||||
* @param IL10N $l10n
|
||||
* @param IConfig $config
|
||||
*/
|
||||
public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config) {
|
||||
parent::__construct($caldavBackend, $calendarInfo);
|
||||
|
||||
@@ -128,58 +118,27 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
|
||||
return $this->calendarInfo['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
$acl = [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
|
||||
]];
|
||||
if ($this->getName() !== BirthdayService::BIRTHDAY_CALENDAR_URI) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
} else {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
];
|
||||
|
||||
if (!$this->isShared()) {
|
||||
return $acl;
|
||||
}
|
||||
@@ -213,13 +172,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
|
||||
}
|
||||
|
||||
$acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl);
|
||||
$allowedPrincipals = [
|
||||
$this->getOwner(),
|
||||
$this->getOwner(). '/calendar-proxy-read',
|
||||
$this->getOwner(). '/calendar-proxy-write',
|
||||
parent::getOwner(),
|
||||
'principals/system/public'
|
||||
];
|
||||
$allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/public'];
|
||||
return array_filter($acl, function($rule) use ($allowedPrincipals) {
|
||||
return \in_array($rule['principal'], $allowedPrincipals, true);
|
||||
});
|
||||
@@ -363,11 +316,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
|
||||
return $this->caldavBackend->getPublishStatus($this);
|
||||
}
|
||||
|
||||
public function canWrite() {
|
||||
if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private function canWrite() {
|
||||
if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) {
|
||||
return !$this->calendarInfo['{http://owncloud.org/ns}read-only'];
|
||||
}
|
||||
@@ -390,14 +339,4 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
|
||||
return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
return parent::getChanges($syncToken, $syncLevel, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @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\DAV\CalDAV\Proxy;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
/**
|
||||
* @method string getOwnerId()
|
||||
* @method void setOwnerId(string $ownerId)
|
||||
* @method string getProxyId()
|
||||
* @method void setProxyId(string $proxyId)
|
||||
* @method int getPermissions()
|
||||
* @method void setPermissions(int $permissions)
|
||||
*/
|
||||
class Proxy extends Entity {
|
||||
|
||||
/** @var string */
|
||||
protected $ownerId;
|
||||
/** @var string */
|
||||
protected $proxyId;
|
||||
/** @var int */
|
||||
protected $permissions;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('ownerId', 'string');
|
||||
$this->addType('proxyId', 'string');
|
||||
$this->addType('permissions', 'int');
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @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\DAV\CalDAV\Proxy;
|
||||
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* Class ProxyMapper
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Proxy
|
||||
*/
|
||||
class ProxyMapper extends QBMapper {
|
||||
|
||||
const PERMISSION_READ = 1;
|
||||
const PERMISSION_WRITE = 2;
|
||||
|
||||
/**
|
||||
* ProxyMapper constructor.
|
||||
*
|
||||
* @param IDBConnection $db
|
||||
*/
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'dav_cal_proxy', Proxy::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $proxyId The principal uri that can act as a proxy for the resulting calendars
|
||||
*
|
||||
* @return Proxy[]
|
||||
*/
|
||||
public function getProxiesFor(string $proxyId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('proxy_id', $qb->createNamedParameter($proxyId)));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ownerId The principal uri that has the resulting proxies for their calendars
|
||||
*
|
||||
* @return Proxy[]
|
||||
*/
|
||||
public function getProxiesOf(string $ownerId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('owner_id', $qb->createNamedParameter($ownerId)));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
||||
@@ -126,10 +126,7 @@ class PublishPlugin extends ServerPlugin {
|
||||
});
|
||||
|
||||
$propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) {
|
||||
$canShare = (!$node->isSubscription() && $node->canWrite());
|
||||
$canPublish = (!$node->isSubscription() && $node->canWrite());
|
||||
|
||||
return new AllowedSharingModes($canShare, $canPublish);
|
||||
return new AllowedSharingModes(!$node->isSubscription(), !$node->isSubscription());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder;
|
||||
|
||||
use OCP\IDBConnection;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
|
||||
/**
|
||||
* Class Backend
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder
|
||||
*/
|
||||
class Backend {
|
||||
|
||||
/** @var IDBConnection */
|
||||
protected $db;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/**
|
||||
* Backend constructor.
|
||||
*
|
||||
* @param IDBConnection $db
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(IDBConnection $db,
|
||||
ITimeFactory $timeFactory) {
|
||||
$this->db = $db;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reminders with a notification date before now
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getRemindersToProcess():array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select(['cr.*', 'co.calendardata', 'c.displayname', 'c.principaluri'])
|
||||
->from('calendar_reminders', 'cr')
|
||||
->where($query->expr()->lte('cr.notification_date', $query->createNamedParameter($this->timeFactory->getTime())))
|
||||
->leftJoin('cr', 'calendarobjects', 'co', $query->expr()->eq('cr.object_id', 'co.id'))
|
||||
->leftJoin('cr', 'calendars', 'c', $query->expr()->eq('cr.calendar_id', 'c.id'));
|
||||
$stmt = $query->execute();
|
||||
|
||||
return array_map(
|
||||
[$this, 'fixRowTyping'],
|
||||
$stmt->fetchAll()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all scheduled reminders for an event
|
||||
*
|
||||
* @param int $objectId
|
||||
* @return array
|
||||
*/
|
||||
public function getAllScheduledRemindersForEvent(int $objectId):array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from('calendar_reminders')
|
||||
->where($query->expr()->eq('object_id', $query->createNamedParameter($objectId)));
|
||||
$stmt = $query->execute();
|
||||
|
||||
return array_map(
|
||||
[$this, 'fixRowTyping'],
|
||||
$stmt->fetchAll()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new reminder into the database
|
||||
*
|
||||
* @param int $calendarId
|
||||
* @param int $objectId
|
||||
* @param string $uid
|
||||
* @param bool $isRecurring
|
||||
* @param int $recurrenceId
|
||||
* @param bool $isRecurrenceException
|
||||
* @param string $eventHash
|
||||
* @param string $alarmHash
|
||||
* @param string $type
|
||||
* @param bool $isRelative
|
||||
* @param int $notificationDate
|
||||
* @param bool $isRepeatBased
|
||||
* @return int The insert id
|
||||
*/
|
||||
public function insertReminder(int $calendarId,
|
||||
int $objectId,
|
||||
string $uid,
|
||||
bool $isRecurring,
|
||||
int $recurrenceId,
|
||||
bool $isRecurrenceException,
|
||||
string $eventHash,
|
||||
string $alarmHash,
|
||||
string $type,
|
||||
bool $isRelative,
|
||||
int $notificationDate,
|
||||
bool $isRepeatBased):int {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->insert('calendar_reminders')
|
||||
->values([
|
||||
'calendar_id' => $query->createNamedParameter($calendarId),
|
||||
'object_id' => $query->createNamedParameter($objectId),
|
||||
'uid' => $query->createNamedParameter($uid),
|
||||
'is_recurring' => $query->createNamedParameter($isRecurring ? 1 : 0),
|
||||
'recurrence_id' => $query->createNamedParameter($recurrenceId),
|
||||
'is_recurrence_exception' => $query->createNamedParameter($isRecurrenceException ? 1 : 0),
|
||||
'event_hash' => $query->createNamedParameter($eventHash),
|
||||
'alarm_hash' => $query->createNamedParameter($alarmHash),
|
||||
'type' => $query->createNamedParameter($type),
|
||||
'is_relative' => $query->createNamedParameter($isRelative ? 1 : 0),
|
||||
'notification_date' => $query->createNamedParameter($notificationDate),
|
||||
'is_repeat_based' => $query->createNamedParameter($isRepeatBased ? 1 : 0),
|
||||
])
|
||||
->execute();
|
||||
|
||||
return $query->getLastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new notificationDate on an existing reminder
|
||||
*
|
||||
* @param int $reminderId
|
||||
* @param int $newNotificationDate
|
||||
*/
|
||||
public function updateReminder(int $reminderId,
|
||||
int $newNotificationDate):void {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->update('calendar_reminders')
|
||||
->set('notification_date', $query->createNamedParameter($newNotificationDate))
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($reminderId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a reminder by it's id
|
||||
*
|
||||
* @param integer $reminderId
|
||||
* @return void
|
||||
*/
|
||||
public function removeReminder(int $reminderId):void {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
|
||||
$query->delete('calendar_reminders')
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($reminderId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans reminders in database
|
||||
*
|
||||
* @param int $objectId
|
||||
*/
|
||||
public function cleanRemindersForEvent(int $objectId):void {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
|
||||
$query->delete('calendar_reminders')
|
||||
->where($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all reminders for a calendar
|
||||
*
|
||||
* @param int $calendarId
|
||||
* @return void
|
||||
*/
|
||||
public function cleanRemindersForCalendar(int $calendarId):void {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
|
||||
$query->delete('calendar_reminders')
|
||||
->where($query->expr()->eq('calendar_id', $query->createNamedParameter($calendarId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
private function fixRowTyping(array $row): array {
|
||||
$row['id'] = (int) $row['id'];
|
||||
$row['calendar_id'] = (int) $row['calendar_id'];
|
||||
$row['object_id'] = (int) $row['object_id'];
|
||||
$row['is_recurring'] = (bool) $row['is_recurring'];
|
||||
$row['recurrence_id'] = (int) $row['recurrence_id'];
|
||||
$row['is_recurrence_exception'] = (bool) $row['is_recurrence_exception'];
|
||||
$row['is_relative'] = (bool) $row['is_relative'];
|
||||
$row['notification_date'] = (int) $row['notification_date'];
|
||||
$row['is_repeat_based'] = (bool) $row['is_repeat_based'];
|
||||
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder;
|
||||
|
||||
use OCP\IUser;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
|
||||
/**
|
||||
* Interface INotificationProvider
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder
|
||||
*/
|
||||
interface INotificationProvider {
|
||||
|
||||
/**
|
||||
* Send notification
|
||||
*
|
||||
* @param VEvent $vevent
|
||||
* @param string $calendarDisplayName
|
||||
* @param IUser[] $users
|
||||
* @return void
|
||||
*/
|
||||
public function send(VEvent $vevent,
|
||||
string $calendarDisplayName,
|
||||
array $users=[]): void;
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
|
||||
|
||||
use OCA\DAV\CalDAV\Reminder\INotificationProvider;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\L10N\IFactory as L10NFactory;
|
||||
use OCP\IUser;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\VObject\Property;
|
||||
|
||||
/**
|
||||
* Class AbstractProvider
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder\NotificationProvider
|
||||
*/
|
||||
abstract class AbstractProvider implements INotificationProvider {
|
||||
|
||||
/** @var string */
|
||||
public const NOTIFICATION_TYPE = '';
|
||||
|
||||
/** @var ILogger */
|
||||
protected $logger;
|
||||
|
||||
/** @var L10NFactory */
|
||||
private $l10nFactory;
|
||||
|
||||
/** @var IL10N[] */
|
||||
private $l10ns;
|
||||
|
||||
/** @var string */
|
||||
private $fallbackLanguage;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
protected $urlGenerator;
|
||||
|
||||
/** @var IConfig */
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @param ILogger $logger
|
||||
* @param L10NFactory $l10nFactory
|
||||
* @param IConfig $config
|
||||
* @param IUrlGenerator $urlGenerator
|
||||
*/
|
||||
public function __construct(ILogger $logger,
|
||||
L10NFactory $l10nFactory,
|
||||
IURLGenerator $urlGenerator,
|
||||
IConfig $config) {
|
||||
$this->logger = $logger;
|
||||
$this->l10nFactory = $l10nFactory;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification
|
||||
*
|
||||
* @param VEvent $vevent
|
||||
* @param string $calendarDisplayName
|
||||
* @param IUser[] $users
|
||||
* @return void
|
||||
*/
|
||||
abstract public function send(VEvent $vevent,
|
||||
string $calendarDisplayName,
|
||||
array $users=[]): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getFallbackLanguage():string {
|
||||
if ($this->fallbackLanguage) {
|
||||
return $this->fallbackLanguage;
|
||||
}
|
||||
|
||||
$fallbackLanguage = $this->l10nFactory->findLanguage();
|
||||
$this->fallbackLanguage = $fallbackLanguage;
|
||||
|
||||
return $fallbackLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lang
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasL10NForLang(string $lang):bool {
|
||||
return $this->l10nFactory->languageExists('dav', $lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lang
|
||||
* @return IL10N
|
||||
*/
|
||||
protected function getL10NForLang(string $lang):IL10N {
|
||||
if (isset($this->l10ns[$lang])) {
|
||||
return $this->l10ns[$lang];
|
||||
}
|
||||
|
||||
$l10n = $this->l10nFactory->get('dav', $lang);
|
||||
$this->l10ns[$lang] = $l10n;
|
||||
|
||||
return $l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return string
|
||||
*/
|
||||
private function getStatusOfEvent(VEvent $vevent):string {
|
||||
if ($vevent->STATUS) {
|
||||
return (string) $vevent->STATUS;
|
||||
}
|
||||
|
||||
// Doesn't say so in the standard,
|
||||
// but we consider events without a status
|
||||
// to be confirmed
|
||||
return 'CONFIRMED';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEventTentative(VEvent $vevent):bool {
|
||||
return $this->getStatusOfEvent($vevent) === 'TENTATIVE';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return Property\ICalendar\DateTime
|
||||
*/
|
||||
protected function getDTEndFromEvent(VEvent $vevent):Property\ICalendar\DateTime {
|
||||
if (isset($vevent->DTEND)) {
|
||||
return $vevent->DTEND;
|
||||
}
|
||||
|
||||
if (isset($vevent->DURATION)) {
|
||||
$isFloating = $vevent->DTSTART->isFloating();
|
||||
/** @var Property\ICalendar\DateTime $end */
|
||||
$end = clone $vevent->DTSTART;
|
||||
$endDateTime = $end->getDateTime();
|
||||
$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
|
||||
$end->setDateTime($endDateTime, $isFloating);
|
||||
|
||||
return $end;
|
||||
}
|
||||
|
||||
if (!$vevent->DTSTART->hasTime()) {
|
||||
$isFloating = $vevent->DTSTART->isFloating();
|
||||
/** @var Property\ICalendar\DateTime $end */
|
||||
$end = clone $vevent->DTSTART;
|
||||
$endDateTime = $end->getDateTime();
|
||||
$endDateTime = $endDateTime->modify('+1 day');
|
||||
$end->setDateTime($endDateTime, $isFloating);
|
||||
|
||||
return $end;
|
||||
}
|
||||
|
||||
return clone $vevent->DTSTART;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
|
||||
|
||||
/**
|
||||
* Class AudioProvider
|
||||
*
|
||||
* This class only extends PushProvider at the moment. It does not provide true
|
||||
* audio-alarms yet, but it's better than no alarm at all right now.
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder\NotificationProvider
|
||||
*/
|
||||
class AudioProvider extends PushProvider {
|
||||
|
||||
/** @var string */
|
||||
public const NOTIFICATION_TYPE = 'AUDIO';
|
||||
}
|
||||
@@ -1,503 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
|
||||
|
||||
use \DateTime;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\L10N\IFactory as L10NFactory;
|
||||
use OCP\Mail\IEMailTemplate;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\IUser;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject;
|
||||
use Sabre\VObject\Parameter;
|
||||
use Sabre\VObject\Property;
|
||||
|
||||
/**
|
||||
* Class EmailProvider
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder\NotificationProvider
|
||||
*/
|
||||
class EmailProvider extends AbstractProvider {
|
||||
|
||||
/** @var string */
|
||||
public const NOTIFICATION_TYPE = 'EMAIL';
|
||||
|
||||
/** @var IMailer */
|
||||
private $mailer;
|
||||
|
||||
/**
|
||||
* @param IConfig $config
|
||||
* @param IMailer $mailer
|
||||
* @param ILogger $logger
|
||||
* @param L10NFactory $l10nFactory
|
||||
* @param IUrlGenerator $urlGenerator
|
||||
*/
|
||||
public function __construct(IConfig $config,
|
||||
IMailer $mailer,
|
||||
ILogger $logger,
|
||||
L10NFactory $l10nFactory,
|
||||
IURLGenerator $urlGenerator) {
|
||||
parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send out notification via email
|
||||
*
|
||||
* @param VEvent $vevent
|
||||
* @param string $calendarDisplayName
|
||||
* @param array $users
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function send(VEvent $vevent,
|
||||
string $calendarDisplayName,
|
||||
array $users=[]):void {
|
||||
$fallbackLanguage = $this->getFallbackLanguage();
|
||||
|
||||
$emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users);
|
||||
$emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent);
|
||||
|
||||
// Quote from php.net:
|
||||
// If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.
|
||||
// => if there are duplicate email addresses, it will always take the system value
|
||||
$emailAddresses = array_merge(
|
||||
$emailAddressesOfAttendees,
|
||||
$emailAddressesOfSharees
|
||||
);
|
||||
|
||||
$sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage);
|
||||
$organizer = $this->getOrganizerEMailAndNameFromEvent($vevent);
|
||||
|
||||
foreach($sortedByLanguage as $lang => $emailAddresses) {
|
||||
if (!$this->hasL10NForLang($lang)) {
|
||||
$lang = $fallbackLanguage;
|
||||
}
|
||||
$l10n = $this->getL10NForLang($lang);
|
||||
$fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply');
|
||||
|
||||
$template = $this->mailer->createEMailTemplate('dav.calendarReminder');
|
||||
$template->addHeader();
|
||||
$this->addSubjectAndHeading($template, $l10n, $vevent);
|
||||
$this->addBulletList($template, $l10n, $calendarDisplayName, $vevent);
|
||||
$template->addFooter();
|
||||
|
||||
foreach ($emailAddresses as $emailAddress) {
|
||||
$message = $this->mailer->createMessage();
|
||||
$message->setFrom([$fromEMail]);
|
||||
if ($organizer) {
|
||||
$message->setReplyTo($organizer);
|
||||
}
|
||||
$message->setTo([$emailAddress]);
|
||||
$message->useTemplate($template);
|
||||
|
||||
try {
|
||||
$failed = $this->mailer->send($message);
|
||||
if ($failed) {
|
||||
$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->logger->logException($ex, ['app' => 'dav']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IEMailTemplate $template
|
||||
* @param IL10N $l10n
|
||||
* @param VEvent $vevent
|
||||
*/
|
||||
private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void {
|
||||
$template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n));
|
||||
$template->addHeading($this->getTitleFromVEvent($vevent, $l10n));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IEMailTemplate $template
|
||||
* @param IL10N $l10n
|
||||
* @param string $calendarDisplayName
|
||||
* @param array $eventData
|
||||
*/
|
||||
private function addBulletList(IEMailTemplate $template,
|
||||
IL10N $l10n,
|
||||
string $calendarDisplayName,
|
||||
VEvent $vevent):void {
|
||||
$template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
|
||||
$this->getAbsoluteImagePath('actions/info.svg'));
|
||||
|
||||
$template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
|
||||
$this->getAbsoluteImagePath('places/calendar.svg'));
|
||||
|
||||
if (isset($vevent->LOCATION)) {
|
||||
$template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'),
|
||||
$this->getAbsoluteImagePath('actions/address.svg'));
|
||||
}
|
||||
if (isset($vevent->DESCRIPTION)) {
|
||||
$template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'),
|
||||
$this->getAbsoluteImagePath('actions/more.svg'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function getAbsoluteImagePath(string $path):string {
|
||||
return $this->urlGenerator->getAbsoluteURL(
|
||||
$this->urlGenerator->imagePath('core', $path)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return array|null
|
||||
*/
|
||||
private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array {
|
||||
if (!$vevent->ORGANIZER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$organizer = $vevent->ORGANIZER;
|
||||
if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$organizerEMail = substr($organizer->getValue(), 7);
|
||||
|
||||
$name = $organizer->offsetGet('CN');
|
||||
if ($name instanceof Parameter) {
|
||||
return [$organizerEMail => $name];
|
||||
}
|
||||
|
||||
return [$organizerEMail];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $emails
|
||||
* @param string $defaultLanguage
|
||||
* @return array
|
||||
*/
|
||||
private function sortEMailAddressesByLanguage(array $emails,
|
||||
string $defaultLanguage):array {
|
||||
$sortedByLanguage = [];
|
||||
|
||||
foreach($emails as $emailAddress => $parameters) {
|
||||
if (isset($parameters['LANG'])) {
|
||||
$lang = $parameters['LANG'];
|
||||
} else {
|
||||
$lang = $defaultLanguage;
|
||||
}
|
||||
|
||||
if (!isset($sortedByLanguage[$lang])) {
|
||||
$sortedByLanguage[$lang] = [];
|
||||
}
|
||||
|
||||
$sortedByLanguage[$lang][] = $emailAddress;
|
||||
}
|
||||
|
||||
return $sortedByLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return array
|
||||
*/
|
||||
private function getAllEMailAddressesFromEvent(VEvent $vevent):array {
|
||||
$emailAddresses = [];
|
||||
|
||||
if (isset($vevent->ATTENDEE)) {
|
||||
foreach ($vevent->ATTENDEE as $attendee) {
|
||||
if (!($attendee instanceof VObject\Property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cuType = $this->getCUTypeOfAttendee($attendee);
|
||||
if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) {
|
||||
// Don't send emails to things
|
||||
continue;
|
||||
}
|
||||
|
||||
$partstat = $this->getPartstatOfAttendee($attendee);
|
||||
if ($partstat === 'DECLINED') {
|
||||
// Don't send out emails to people who declined
|
||||
continue;
|
||||
}
|
||||
if ($partstat === 'DELEGATED') {
|
||||
$delegates = $attendee->offsetGet('DELEGATED-TO');
|
||||
if (!($delegates instanceof VObject\Parameter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$emailAddressesOfDelegates = $delegates->getParts();
|
||||
foreach($emailAddressesOfDelegates as $addressesOfDelegate) {
|
||||
if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) {
|
||||
$emailAddresses[substr($addressesOfDelegate, 7)] = [];
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee);
|
||||
if ($emailAddressOfAttendee !== null) {
|
||||
$properties = [];
|
||||
|
||||
$langProp = $attendee->offsetGet('LANG');
|
||||
if ($langProp instanceof VObject\Parameter) {
|
||||
$properties['LANG'] = $langProp->getValue();
|
||||
}
|
||||
|
||||
$emailAddresses[$emailAddressOfAttendee] = $properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) {
|
||||
$emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = [];
|
||||
}
|
||||
|
||||
return $emailAddresses;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param VObject\Property $attendee
|
||||
* @return string
|
||||
*/
|
||||
private function getCUTypeOfAttendee(VObject\Property $attendee):string {
|
||||
$cuType = $attendee->offsetGet('CUTYPE');
|
||||
if ($cuType instanceof VObject\Parameter) {
|
||||
return strtoupper($cuType->getValue());
|
||||
}
|
||||
|
||||
return 'INDIVIDUAL';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Property $attendee
|
||||
* @return string
|
||||
*/
|
||||
private function getPartstatOfAttendee(VObject\Property $attendee):string {
|
||||
$partstat = $attendee->offsetGet('PARTSTAT');
|
||||
if ($partstat instanceof VObject\Parameter) {
|
||||
return strtoupper($partstat->getValue());
|
||||
}
|
||||
|
||||
return 'NEEDS-ACTION';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Property $attendee
|
||||
* @return bool
|
||||
*/
|
||||
private function hasAttendeeMailURI(VObject\Property $attendee):bool {
|
||||
return stripos($attendee->getValue(), 'mailto:') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Property $attendee
|
||||
* @return string|null
|
||||
*/
|
||||
private function getEMailAddressOfAttendee(VObject\Property $attendee):?string {
|
||||
if (!$this->hasAttendeeMailURI($attendee)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr($attendee->getValue(), 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $users
|
||||
* @return array
|
||||
*/
|
||||
private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array {
|
||||
$emailAddresses = [];
|
||||
|
||||
foreach($users as $user) {
|
||||
$emailAddress = $user->getEMailAddress();
|
||||
if ($emailAddress) {
|
||||
$lang = $this->getLangForUser($user);
|
||||
if ($lang) {
|
||||
$emailAddresses[$emailAddress] = [
|
||||
'LANG' => $lang,
|
||||
];
|
||||
} else {
|
||||
$emailAddresses[$emailAddress] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $emailAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IUser $user
|
||||
* @return string
|
||||
*/
|
||||
private function getLangForUser(IUser $user): ?string {
|
||||
return $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param VEvent $vevent
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function generateDateString(IL10N $l10n, VEvent $vevent):string {
|
||||
$isAllDay = $vevent->DTSTART instanceof Property\ICalendar\Date;
|
||||
|
||||
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
|
||||
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
|
||||
/** @var \DateTimeImmutable $dtstartDt */
|
||||
$dtstartDt = $vevent->DTSTART->getDateTime();
|
||||
/** @var \DateTimeImmutable $dtendDt */
|
||||
$dtendDt = $this->getDTEndFromEvent($vevent)->getDateTime();
|
||||
|
||||
$diff = $dtstartDt->diff($dtendDt);
|
||||
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
$dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
$dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
|
||||
|
||||
if ($isAllDay) {
|
||||
// One day event
|
||||
if ($diff->days === 1) {
|
||||
return $this->getDateString($l10n, $dtstartDt);
|
||||
}
|
||||
|
||||
return implode(' - ', [
|
||||
$this->getDateString($l10n, $dtstartDt),
|
||||
$this->getDateString($l10n, $dtendDt),
|
||||
]);
|
||||
}
|
||||
|
||||
$startTimezone = $endTimezone = null;
|
||||
if (!$vevent->DTSTART->isFloating()) {
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
$startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName();
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
$endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName();
|
||||
}
|
||||
|
||||
$localeStart = implode(', ', [
|
||||
$this->getWeekDayName($l10n, $dtstartDt),
|
||||
$this->getDateTimeString($l10n, $dtstartDt)
|
||||
]);
|
||||
|
||||
// always show full date with timezone if timezones are different
|
||||
if ($startTimezone !== $endTimezone) {
|
||||
$localeEnd = implode(', ', [
|
||||
$this->getWeekDayName($l10n, $dtendDt),
|
||||
$this->getDateTimeString($l10n, $dtendDt)
|
||||
]);
|
||||
|
||||
return $localeStart
|
||||
. ' (' . $startTimezone . ') '
|
||||
. ' - '
|
||||
. $localeEnd
|
||||
. ' (' . $endTimezone . ')';
|
||||
}
|
||||
|
||||
// Show only the time if the day is the same
|
||||
$localeEnd = $this->isDayEqual($dtstartDt, $dtendDt)
|
||||
? $this->getTimeString($l10n, $dtendDt)
|
||||
: implode(', ', [
|
||||
$this->getWeekDayName($l10n, $dtendDt),
|
||||
$this->getDateTimeString($l10n, $dtendDt)
|
||||
]);
|
||||
|
||||
return $localeStart
|
||||
. ' - '
|
||||
. $localeEnd
|
||||
. ' (' . $startTimezone . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dtStart
|
||||
* @param DateTime $dtEnd
|
||||
* @return bool
|
||||
*/
|
||||
private function isDayEqual(DateTime $dtStart,
|
||||
DateTime $dtEnd):bool {
|
||||
return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getWeekDayName(IL10N $l10n, DateTime $dt):string {
|
||||
return $l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getDateString(IL10N $l10n, DateTime $dt):string {
|
||||
return $l10n->l('date', $dt, ['width' => 'medium']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getDateTimeString(IL10N $l10n, DateTime $dt):string {
|
||||
return $l10n->l('datetime', $dt, ['width' => 'medium|short']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getTimeString(IL10N $l10n, DateTime $dt):string {
|
||||
return $l10n->l('time', $dt, ['width' => 'short']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @param IL10N $l10n
|
||||
* @return string
|
||||
*/
|
||||
private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string {
|
||||
if (isset($vevent->SUMMARY)) {
|
||||
return (string)$vevent->SUMMARY;
|
||||
}
|
||||
|
||||
return $l10n->t('Untitled event');
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
*
|
||||
* @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\DAV\CalDAV\Reminder\NotificationProvider;
|
||||
|
||||
class ProviderNotAvailableException extends \Exception {
|
||||
|
||||
/**
|
||||
* ProviderNotAvailableException constructor.
|
||||
*
|
||||
* @since 16.0.0
|
||||
*
|
||||
* @param string $type ReminderType
|
||||
*/
|
||||
public function __construct(string $type) {
|
||||
parent::__construct("No notification provider for type $type available");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
|
||||
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\L10N\IFactory as L10NFactory;
|
||||
use OCP\Notification\IManager;
|
||||
use OCP\IUser;
|
||||
use OCP\Notification\INotification;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\Property;
|
||||
|
||||
/**
|
||||
* Class PushProvider
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder\NotificationProvider
|
||||
*/
|
||||
class PushProvider extends AbstractProvider {
|
||||
|
||||
/** @var string */
|
||||
public const NOTIFICATION_TYPE = 'DISPLAY';
|
||||
|
||||
/** @var IManager */
|
||||
private $manager;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/**
|
||||
* @param IConfig $config
|
||||
* @param IManager $manager
|
||||
* @param ILogger $logger
|
||||
* @param L10NFactory $l10nFactory
|
||||
* @param IUrlGenerator $urlGenerator
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(IConfig $config,
|
||||
IManager $manager,
|
||||
ILogger $logger,
|
||||
L10NFactory $l10nFactory,
|
||||
IURLGenerator $urlGenerator,
|
||||
ITimeFactory $timeFactory) {
|
||||
parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
|
||||
$this->manager = $manager;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send push notification to all users.
|
||||
*
|
||||
* @param VEvent $vevent
|
||||
* @param string $calendarDisplayName
|
||||
* @param IUser[] $users
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function send(VEvent $vevent,
|
||||
string $calendarDisplayName=null,
|
||||
array $users=[]):void {
|
||||
if ($this->config->getAppValue('dav', 'sendEventRemindersPush', 'no') !== 'yes') {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventDetails = $this->extractEventDetails($vevent);
|
||||
$eventDetails['calendar_displayname'] = $calendarDisplayName;
|
||||
$eventUUID = (string) $vevent->UID;
|
||||
// Empty Notification ObjectId will be catched by OC\Notification\Notification
|
||||
$eventUUIDHash = $eventUUID ? hash('sha256', $eventUUID, false) : '';
|
||||
|
||||
foreach($users as $user) {
|
||||
/** @var INotification $notification */
|
||||
$notification = $this->manager->createNotification();
|
||||
$notification->setApp(Application::APP_ID)
|
||||
->setUser($user->getUID())
|
||||
->setDateTime($this->timeFactory->getDateTime())
|
||||
->setObject(Application::APP_ID, $eventUUIDHash)
|
||||
->setSubject('calendar_reminder', [
|
||||
'title' => $eventDetails['title'],
|
||||
'start_atom' => $eventDetails['start_atom']
|
||||
])
|
||||
->setMessage('calendar_reminder', $eventDetails);
|
||||
|
||||
$this->manager->notify($notification);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var VEvent $vevent
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function extractEventDetails(VEvent $vevent):array {
|
||||
/** @var Property\ICalendar\DateTime $start */
|
||||
$start = $vevent->DTSTART;
|
||||
$end = $this->getDTEndFromEvent($vevent);
|
||||
|
||||
return [
|
||||
'title' => isset($vevent->SUMMARY)
|
||||
? ((string) $vevent->SUMMARY)
|
||||
: null,
|
||||
'description' => isset($vevent->DESCRIPTION)
|
||||
? ((string) $vevent->DESCRIPTION)
|
||||
: null,
|
||||
'location' => isset($vevent->LOCATION)
|
||||
? ((string) $vevent->LOCATION)
|
||||
: null,
|
||||
'all_day' => $start instanceof Property\ICalendar\Date,
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
'start_atom' => $start->getDateTime()->format(\DateTime::ATOM),
|
||||
'start_is_floating' => $start->isFloating(),
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
'start_timezone' => $start->getDateTime()->getTimezone()->getName(),
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
'end_atom' => $end->getDateTime()->format(\DateTime::ATOM),
|
||||
'end_is_floating' => $end->isFloating(),
|
||||
/** @phan-suppress-next-line PhanUndeclaredClassMethod */
|
||||
'end_timezone' => $end->getDateTime()->getTimezone()->getName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder;
|
||||
|
||||
/**
|
||||
* Class NotificationProviderManager
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder
|
||||
*/
|
||||
class NotificationProviderManager {
|
||||
|
||||
/** @var INotificationProvider[] */
|
||||
private $providers = [];
|
||||
|
||||
/**
|
||||
* Checks whether a provider for a given ACTION exists
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasProvider(string $type):bool {
|
||||
return (\in_array($type, ReminderService::REMINDER_TYPES, true)
|
||||
&& isset($this->providers[$type]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider for a given ACTION
|
||||
*
|
||||
* @param string $type
|
||||
* @return INotificationProvider
|
||||
* @throws NotificationProvider\ProviderNotAvailableException
|
||||
* @throws NotificationTypeDoesNotExistException
|
||||
*/
|
||||
public function getProvider(string $type):INotificationProvider {
|
||||
if (in_array($type, ReminderService::REMINDER_TYPES, true)) {
|
||||
if (isset($this->providers[$type])) {
|
||||
return $this->providers[$type];
|
||||
}
|
||||
throw new NotificationProvider\ProviderNotAvailableException($type);
|
||||
}
|
||||
throw new NotificationTypeDoesNotExistException($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new provider
|
||||
*
|
||||
* @param string $providerClassName
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function registerProvider(string $providerClassName):void {
|
||||
$provider = \OC::$server->query($providerClassName);
|
||||
|
||||
if (!$provider instanceof INotificationProvider) {
|
||||
throw new \InvalidArgumentException('Invalid notification provider registered');
|
||||
}
|
||||
|
||||
$this->providers[$provider::NOTIFICATION_TYPE] = $provider;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
*
|
||||
* @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\DAV\CalDAV\Reminder;
|
||||
|
||||
class NotificationTypeDoesNotExistException extends \Exception {
|
||||
|
||||
/**
|
||||
* NotificationTypeDoesNotExistException constructor.
|
||||
*
|
||||
* @since 16.0.0
|
||||
*
|
||||
* @param string $type ReminderType
|
||||
*/
|
||||
public function __construct(string $type) {
|
||||
parent::__construct("Type $type is not an accepted type of notification");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Reminder;
|
||||
|
||||
use \DateTime;
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Notification\INotification;
|
||||
use OCP\Notification\INotifier;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
/**
|
||||
* Class Notifier
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\Reminder
|
||||
*/
|
||||
class Notifier implements INotifier {
|
||||
|
||||
/** @var IFactory */
|
||||
private $l10nFactory;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/**
|
||||
* Notifier constructor.
|
||||
*
|
||||
* @param IFactory $factory
|
||||
* @param IURLGenerator $urlGenerator
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(IFactory $factory,
|
||||
IURLGenerator $urlGenerator,
|
||||
ITimeFactory $timeFactory) {
|
||||
$this->l10nFactory = $factory;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier of the notifier, only use [a-z0-9_]
|
||||
*
|
||||
* @return string
|
||||
* @since 17.0.0
|
||||
*/
|
||||
public function getID():string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human readable name describing the notifier
|
||||
*
|
||||
* @return string
|
||||
* @since 17.0.0
|
||||
*/
|
||||
public function getName():string {
|
||||
return $this->l10nFactory->get('dav')->t('Calendar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare sending the notification
|
||||
*
|
||||
* @param INotification $notification
|
||||
* @param string $languageCode The code of the language that should be used to prepare the notification
|
||||
* @return INotification
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function prepare(INotification $notification,
|
||||
string $languageCode):INotification {
|
||||
if ($notification->getApp() !== Application::APP_ID) {
|
||||
throw new \InvalidArgumentException('Notification not from this app');
|
||||
}
|
||||
|
||||
// Read the language from the notification
|
||||
$this->l10n = $this->l10nFactory->get('dav', $languageCode);
|
||||
|
||||
// Handle notifier subjects
|
||||
switch($notification->getSubject()) {
|
||||
case 'calendar_reminder':
|
||||
return $this->prepareReminderNotification($notification);
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown subject');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param INotification $notification
|
||||
* @return INotification
|
||||
*/
|
||||
private function prepareReminderNotification(INotification $notification):INotification {
|
||||
$imagePath = $this->urlGenerator->imagePath('core', 'places/calendar.svg');
|
||||
$iconUrl = $this->urlGenerator->getAbsoluteURL($imagePath);
|
||||
$notification->setIcon($iconUrl);
|
||||
|
||||
$this->prepareNotificationSubject($notification);
|
||||
$this->prepareNotificationMessage($notification);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the notification subject based on the parameters set in PushProvider
|
||||
*
|
||||
* @param INotification $notification
|
||||
*/
|
||||
private function prepareNotificationSubject(INotification $notification): void {
|
||||
$parameters = $notification->getSubjectParameters();
|
||||
|
||||
$startTime = \DateTime::createFromFormat(\DateTime::ATOM, $parameters['start_atom']);
|
||||
$now = $this->timeFactory->getDateTime();
|
||||
$title = $this->getTitleFromParameters($parameters);
|
||||
|
||||
$diff = $startTime->diff($now);
|
||||
if ($diff === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$components = [];
|
||||
if ($diff->y) {
|
||||
$components[] = $this->l10n->n('%n year', '%n years', $diff->y);
|
||||
}
|
||||
if ($diff->m) {
|
||||
$components[] = $this->l10n->n('%n month', '%n months', $diff->m);
|
||||
}
|
||||
if ($diff->d) {
|
||||
$components[] = $this->l10n->n('%n day', '%n days', $diff->d);
|
||||
}
|
||||
if ($diff->h) {
|
||||
$components[] = $this->l10n->n('%n hour', '%n hours', $diff->h);
|
||||
}
|
||||
if ($diff->i) {
|
||||
$components[] = $this->l10n->n('%n minute', '%n minutes', $diff->i);
|
||||
}
|
||||
|
||||
// Limiting to the first three components to prevent
|
||||
// the string from getting too long
|
||||
$firstThreeComponents = array_slice($components, 0, 2);
|
||||
$diffLabel = implode(', ', $firstThreeComponents);
|
||||
|
||||
if ($diff->invert) {
|
||||
$title = $this->l10n->t('%s (in %s)', [$title, $diffLabel]);
|
||||
} else {
|
||||
$title = $this->l10n->t('%s (%s ago)', [$title, $diffLabel]);
|
||||
}
|
||||
|
||||
$notification->setParsedSubject($title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the notification message based on the parameters set in PushProvider
|
||||
*
|
||||
* @param INotification $notification
|
||||
*/
|
||||
private function prepareNotificationMessage(INotification $notification): void {
|
||||
$parameters = $notification->getMessageParameters();
|
||||
|
||||
$description = [
|
||||
$this->l10n->t('Calendar: %s', $parameters['calendar_displayname']),
|
||||
$this->l10n->t('Date: %s', $this->generateDateString($parameters)),
|
||||
];
|
||||
if ($parameters['description']) {
|
||||
$description[] = $this->l10n->t('Description: %s', $parameters['description']);
|
||||
}
|
||||
if ($parameters['location']) {
|
||||
$description[] = $this->l10n->t('Where: %s', $parameters['location']);
|
||||
}
|
||||
|
||||
$message = implode("\r\n", $description);
|
||||
$notification->setParsedMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @return string
|
||||
*/
|
||||
private function getTitleFromParameters(array $parameters):string {
|
||||
return $parameters['title'] ?? $this->l10n->t('Untitled event');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function generateDateString(array $parameters):string {
|
||||
$startDateTime = DateTime::createFromFormat(\DateTime::ATOM, $parameters['start_atom']);
|
||||
$endDateTime = DateTime::createFromFormat(\DateTime::ATOM, $parameters['end_atom']);
|
||||
$isAllDay = $parameters['all_day'];
|
||||
$diff = $startDateTime->diff($endDateTime);
|
||||
|
||||
if ($isAllDay) {
|
||||
// One day event
|
||||
if ($diff->days === 1) {
|
||||
return $this->getDateString($startDateTime);
|
||||
}
|
||||
|
||||
return implode(' - ', [
|
||||
$this->getDateString($startDateTime),
|
||||
$this->getDateString($endDateTime),
|
||||
]);
|
||||
}
|
||||
|
||||
$startTimezone = $endTimezone = null;
|
||||
if (!$parameters['start_is_floating']) {
|
||||
$startTimezone = $parameters['start_timezone'];
|
||||
$endTimezone = $parameters['end_timezone'];
|
||||
}
|
||||
|
||||
$localeStart = implode(', ', [
|
||||
$this->getWeekDayName($startDateTime),
|
||||
$this->getDateTimeString($startDateTime)
|
||||
]);
|
||||
|
||||
// always show full date with timezone if timezones are different
|
||||
if ($startTimezone !== $endTimezone) {
|
||||
$localeEnd = implode(', ', [
|
||||
$this->getWeekDayName($endDateTime),
|
||||
$this->getDateTimeString($endDateTime)
|
||||
]);
|
||||
|
||||
return $localeStart
|
||||
. ' (' . $startTimezone . ') '
|
||||
. ' - '
|
||||
. $localeEnd
|
||||
. ' (' . $endTimezone . ')';
|
||||
}
|
||||
|
||||
// Show only the time if the day is the same
|
||||
$localeEnd = $this->isDayEqual($startDateTime, $endDateTime)
|
||||
? $this->getTimeString($endDateTime)
|
||||
: implode(', ', [
|
||||
$this->getWeekDayName($endDateTime),
|
||||
$this->getDateTimeString($endDateTime)
|
||||
]);
|
||||
|
||||
return $localeStart
|
||||
. ' - '
|
||||
. $localeEnd
|
||||
. ' (' . $startTimezone . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dtStart
|
||||
* @param DateTime $dtEnd
|
||||
* @return bool
|
||||
*/
|
||||
private function isDayEqual(DateTime $dtStart,
|
||||
DateTime $dtEnd):bool {
|
||||
return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getWeekDayName(DateTime $dt):string {
|
||||
return $this->l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getDateString(DateTime $dt):string {
|
||||
return $this->l10n->l('date', $dt, ['width' => 'medium']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getDateTimeString(DateTime $dt):string {
|
||||
return $this->l10n->l('datetime', $dt, ['width' => 'medium|short']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $dt
|
||||
* @return string
|
||||
*/
|
||||
private function getTimeString(DateTime $dt):string {
|
||||
return $this->l10n->l('time', $dt, ['width' => 'short']);
|
||||
}
|
||||
}
|
||||
@@ -1,777 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Thomas Citharel
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Thomas Citharel <tcit@tcit.fr>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Reminder;
|
||||
|
||||
use \DateTimeImmutable;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use Sabre\VObject;
|
||||
use Sabre\VObject\Component\VAlarm;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Recur\EventIterator;
|
||||
use Sabre\VObject\Recur\NoInstancesException;
|
||||
|
||||
class ReminderService {
|
||||
|
||||
/** @var Backend */
|
||||
private $backend;
|
||||
|
||||
/** @var NotificationProviderManager */
|
||||
private $notificationProviderManager;
|
||||
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
/** @var IGroupManager */
|
||||
private $groupManager;
|
||||
|
||||
/** @var CalDavBackend */
|
||||
private $caldavBackend;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
public const REMINDER_TYPE_EMAIL = 'EMAIL';
|
||||
public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
|
||||
public const REMINDER_TYPE_AUDIO = 'AUDIO';
|
||||
|
||||
/**
|
||||
* @var String[]
|
||||
*
|
||||
* Official RFC5545 reminder types
|
||||
*/
|
||||
public const REMINDER_TYPES = [
|
||||
self::REMINDER_TYPE_EMAIL,
|
||||
self::REMINDER_TYPE_DISPLAY,
|
||||
self::REMINDER_TYPE_AUDIO
|
||||
];
|
||||
|
||||
/**
|
||||
* ReminderService constructor.
|
||||
*
|
||||
* @param Backend $backend
|
||||
* @param NotificationProviderManager $notificationProviderManager
|
||||
* @param IUserManager $userManager
|
||||
* @param IGroupManager $groupManager
|
||||
* @param CalDavBackend $caldavBackend
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(Backend $backend,
|
||||
NotificationProviderManager $notificationProviderManager,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
CalDavBackend $caldavBackend,
|
||||
ITimeFactory $timeFactory) {
|
||||
$this->backend = $backend;
|
||||
$this->notificationProviderManager = $notificationProviderManager;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process reminders to activate
|
||||
*
|
||||
* @throws NotificationProvider\ProviderNotAvailableException
|
||||
* @throws NotificationTypeDoesNotExistException
|
||||
*/
|
||||
public function processReminders():void {
|
||||
$reminders = $this->backend->getRemindersToProcess();
|
||||
|
||||
foreach($reminders as $reminder) {
|
||||
$calendarData = is_resource($reminder['calendardata'])
|
||||
? stream_get_contents($reminder['calendardata'])
|
||||
: $reminder['calendardata'];
|
||||
|
||||
$vcalendar = $this->parseCalendarData($calendarData);
|
||||
if (!$vcalendar) {
|
||||
$this->backend->removeReminder($reminder['id']);
|
||||
continue;
|
||||
}
|
||||
|
||||
$vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
|
||||
if (!$vevent) {
|
||||
$this->backend->removeReminder($reminder['id']);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->wasEventCancelled($vevent)) {
|
||||
$this->deleteOrProcessNext($reminder, $vevent);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->notificationProviderManager->hasProvider($reminder['type'])) {
|
||||
$this->deleteOrProcessNext($reminder, $vevent);
|
||||
continue;
|
||||
}
|
||||
|
||||
$users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']);
|
||||
$user = $this->getUserFromPrincipalURI($reminder['principaluri']);
|
||||
if ($user) {
|
||||
$users[] = $user;
|
||||
}
|
||||
|
||||
$notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
|
||||
$notificationProvider->send($vevent, $reminder['displayname'], $users);
|
||||
|
||||
$this->deleteOrProcessNext($reminder, $vevent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @param array $objectData
|
||||
* @throws VObject\InvalidDataException
|
||||
*/
|
||||
public function onTouchCalendarObject(string $action,
|
||||
array $objectData):void {
|
||||
// We only support VEvents for now
|
||||
if (strcasecmp($objectData['component'], 'vevent') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch($action) {
|
||||
case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
|
||||
$this->onCalendarObjectCreate($objectData);
|
||||
break;
|
||||
|
||||
case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject':
|
||||
$this->onCalendarObjectEdit($objectData);
|
||||
break;
|
||||
|
||||
case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject':
|
||||
$this->onCalendarObjectDelete($objectData);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $objectData
|
||||
*/
|
||||
private function onCalendarObjectCreate(array $objectData):void {
|
||||
$calendarData = is_resource($objectData['calendardata'])
|
||||
? stream_get_contents($objectData['calendardata'])
|
||||
: $objectData['calendardata'];
|
||||
|
||||
/** @var VObject\Component\VCalendar $vcalendar */
|
||||
$vcalendar = $this->parseCalendarData($calendarData);
|
||||
if (!$vcalendar) {
|
||||
return;
|
||||
}
|
||||
|
||||
$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
|
||||
if (count($vevents) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = (string) $vevents[0]->UID;
|
||||
$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
|
||||
$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
|
||||
$now = $this->timeFactory->getDateTime();
|
||||
$isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
|
||||
|
||||
foreach($recurrenceExceptions as $recurrenceException) {
|
||||
$eventHash = $this->getEventHash($recurrenceException);
|
||||
|
||||
if (!isset($recurrenceException->VALARM)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($recurrenceException->VALARM as $valarm) {
|
||||
/** @var VAlarm $valarm */
|
||||
$alarmHash = $this->getAlarmHash($valarm);
|
||||
$triggerTime = $valarm->getEffectiveTriggerTime();
|
||||
$diff = $now->diff($triggerTime);
|
||||
if ($diff->invert === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
|
||||
$eventHash, $alarmHash, true, true);
|
||||
$this->writeRemindersToDatabase($alarms);
|
||||
}
|
||||
}
|
||||
|
||||
if ($masterItem) {
|
||||
$processedAlarms = [];
|
||||
$masterAlarms = [];
|
||||
$masterHash = $this->getEventHash($masterItem);
|
||||
|
||||
if (!isset($masterItem->VALARM)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($masterItem->VALARM as $valarm) {
|
||||
$masterAlarms[] = $this->getAlarmHash($valarm);
|
||||
}
|
||||
|
||||
try {
|
||||
$iterator = new EventIterator($vevents, $uid);
|
||||
} catch (NoInstancesException $e) {
|
||||
// This event is recurring, but it doesn't have a single
|
||||
// instance. We are skipping this event from the output
|
||||
// entirely.
|
||||
return;
|
||||
}
|
||||
|
||||
while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
|
||||
$event = $iterator->getEventObject();
|
||||
|
||||
// Recurrence-exceptions are handled separately, so just ignore them here
|
||||
if (\in_array($event, $recurrenceExceptions, true)) {
|
||||
$iterator->next();
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($event->VALARM as $valarm) {
|
||||
/** @var VAlarm $valarm */
|
||||
$alarmHash = $this->getAlarmHash($valarm);
|
||||
if (\in_array($alarmHash, $processedAlarms, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
|
||||
// Action allows x-name, we don't insert reminders
|
||||
// into the database if they are not standard
|
||||
$processedAlarms[] = $alarmHash;
|
||||
continue;
|
||||
}
|
||||
|
||||
$triggerTime = $valarm->getEffectiveTriggerTime();
|
||||
|
||||
// If effective trigger time is in the past
|
||||
// just skip and generate for next event
|
||||
$diff = $now->diff($triggerTime);
|
||||
if ($diff->invert === 1) {
|
||||
// If an absolute alarm is in the past,
|
||||
// just add it to processedAlarms, so
|
||||
// we don't extend till eternity
|
||||
if (!$this->isAlarmRelative($valarm)) {
|
||||
$processedAlarms[] = $alarmHash;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
|
||||
$this->writeRemindersToDatabase($alarms);
|
||||
$processedAlarms[] = $alarmHash;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $objectData
|
||||
*/
|
||||
private function onCalendarObjectEdit(array $objectData):void {
|
||||
// TODO - this can be vastly improved
|
||||
// - get cached reminders
|
||||
// - ...
|
||||
|
||||
$this->onCalendarObjectDelete($objectData);
|
||||
$this->onCalendarObjectCreate($objectData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $objectData
|
||||
*/
|
||||
private function onCalendarObjectDelete(array $objectData):void {
|
||||
$this->backend->cleanRemindersForEvent((int) $objectData['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VAlarm $valarm
|
||||
* @param array $objectData
|
||||
* @param string|null $eventHash
|
||||
* @param string|null $alarmHash
|
||||
* @param bool $isRecurring
|
||||
* @param bool $isRecurrenceException
|
||||
* @return array
|
||||
*/
|
||||
private function getRemindersForVAlarm(VAlarm $valarm,
|
||||
array $objectData,
|
||||
string $eventHash=null,
|
||||
string $alarmHash=null,
|
||||
bool $isRecurring=false,
|
||||
bool $isRecurrenceException=false):array {
|
||||
if ($eventHash === null) {
|
||||
$eventHash = $this->getEventHash($valarm->parent);
|
||||
}
|
||||
if ($alarmHash === null) {
|
||||
$alarmHash = $this->getAlarmHash($valarm);
|
||||
}
|
||||
|
||||
$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
|
||||
$isRelative = $this->isAlarmRelative($valarm);
|
||||
/** @var DateTimeImmutable $notificationDate */
|
||||
$notificationDate = $valarm->getEffectiveTriggerTime();
|
||||
$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
|
||||
$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
|
||||
|
||||
$alarms = [];
|
||||
|
||||
$alarms[] = [
|
||||
'calendar_id' => $objectData['calendarid'],
|
||||
'object_id' => $objectData['id'],
|
||||
'uid' => (string) $valarm->parent->UID,
|
||||
'is_recurring' => $isRecurring,
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'is_recurrence_exception' => $isRecurrenceException,
|
||||
'event_hash' => $eventHash,
|
||||
'alarm_hash' => $alarmHash,
|
||||
'type' => (string) $valarm->ACTION,
|
||||
'is_relative' => $isRelative,
|
||||
'notification_date' => $notificationDate->getTimestamp(),
|
||||
'is_repeat_based' => false,
|
||||
];
|
||||
|
||||
$repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
|
||||
for($i = 0; $i < $repeat; $i++) {
|
||||
if ($valarm->DURATION === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clonedNotificationDate->add($valarm->DURATION->getDateInterval());
|
||||
$alarms[] = [
|
||||
'calendar_id' => $objectData['calendarid'],
|
||||
'object_id' => $objectData['id'],
|
||||
'uid' => (string) $valarm->parent->UID,
|
||||
'is_recurring' => $isRecurring,
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'is_recurrence_exception' => $isRecurrenceException,
|
||||
'event_hash' => $eventHash,
|
||||
'alarm_hash' => $alarmHash,
|
||||
'type' => (string) $valarm->ACTION,
|
||||
'is_relative' => $isRelative,
|
||||
'notification_date' => $clonedNotificationDate->getTimestamp(),
|
||||
'is_repeat_based' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return $alarms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $reminders
|
||||
*/
|
||||
private function writeRemindersToDatabase(array $reminders): void {
|
||||
foreach($reminders as $reminder) {
|
||||
$this->backend->insertReminder(
|
||||
(int) $reminder['calendar_id'],
|
||||
(int) $reminder['object_id'],
|
||||
$reminder['uid'],
|
||||
$reminder['is_recurring'],
|
||||
(int) $reminder['recurrence_id'],
|
||||
$reminder['is_recurrence_exception'],
|
||||
$reminder['event_hash'],
|
||||
$reminder['alarm_hash'],
|
||||
$reminder['type'],
|
||||
$reminder['is_relative'],
|
||||
(int) $reminder['notification_date'],
|
||||
$reminder['is_repeat_based']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $reminder
|
||||
* @param VEvent $vevent
|
||||
*/
|
||||
private function deleteOrProcessNext(array $reminder,
|
||||
VObject\Component\VEvent $vevent):void {
|
||||
if ($reminder['is_repeat_based'] ||
|
||||
!$reminder['is_recurring'] ||
|
||||
!$reminder['is_relative'] ||
|
||||
$reminder['is_recurrence_exception']) {
|
||||
|
||||
$this->backend->removeReminder($reminder['id']);
|
||||
return;
|
||||
}
|
||||
|
||||
$vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
|
||||
$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
|
||||
$now = $this->timeFactory->getDateTime();
|
||||
|
||||
try {
|
||||
$iterator = new EventIterator($vevents, $reminder['uid']);
|
||||
} catch (NoInstancesException $e) {
|
||||
// This event is recurring, but it doesn't have a single
|
||||
// instance. We are skipping this event from the output
|
||||
// entirely.
|
||||
return;
|
||||
}
|
||||
|
||||
while($iterator->valid()) {
|
||||
$event = $iterator->getEventObject();
|
||||
|
||||
// Recurrence-exceptions are handled separately, so just ignore them here
|
||||
if (\in_array($event, $recurrenceExceptions, true)) {
|
||||
$iterator->next();
|
||||
continue;
|
||||
}
|
||||
|
||||
$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
|
||||
if ($reminder['recurrence_id'] >= $recurrenceId) {
|
||||
$iterator->next();
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($event->VALARM as $valarm) {
|
||||
/** @var VAlarm $valarm */
|
||||
$alarmHash = $this->getAlarmHash($valarm);
|
||||
if ($alarmHash !== $reminder['alarm_hash']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$triggerTime = $valarm->getEffectiveTriggerTime();
|
||||
|
||||
// If effective trigger time is in the past
|
||||
// just skip and generate for next event
|
||||
$diff = $now->diff($triggerTime);
|
||||
if ($diff->invert === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->backend->removeReminder($reminder['id']);
|
||||
$alarms = $this->getRemindersForVAlarm($valarm, [
|
||||
'calendarid' => $reminder['calendar_id'],
|
||||
'id' => $reminder['object_id'],
|
||||
], $reminder['event_hash'], $alarmHash, true, false);
|
||||
$this->writeRemindersToDatabase($alarms);
|
||||
|
||||
// Abort generating reminders after creating one successfully
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
$this->backend->removeReminder($reminder['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $calendarId
|
||||
* @return IUser[]
|
||||
*/
|
||||
private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
|
||||
$shares = $this->caldavBackend->getShares($calendarId);
|
||||
|
||||
$users = [];
|
||||
$userIds = [];
|
||||
$groups = [];
|
||||
foreach ($shares as $share) {
|
||||
// Only consider writable shares
|
||||
if ($share['readOnly']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
|
||||
if ($principal[1] === 'users') {
|
||||
$user = $this->userManager->get($principal[2]);
|
||||
if ($user) {
|
||||
$users[] = $user;
|
||||
$userIds[] = $principal[2];
|
||||
}
|
||||
} else if ($principal[1] === 'groups') {
|
||||
$groups[] = $principal[2];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($groups as $gid) {
|
||||
$group = $this->groupManager->get($gid);
|
||||
if ($group instanceof IGroup) {
|
||||
foreach ($group->getUsers() as $user) {
|
||||
if (!\in_array($user->getUID(), $userIds, true)) {
|
||||
$users[] = $user;
|
||||
$userIds[] = $user->getUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hash of the event.
|
||||
* If the hash changes, we have to update all relative alarms.
|
||||
*
|
||||
* @param VEvent $vevent
|
||||
* @return string
|
||||
*/
|
||||
private function getEventHash(VEvent $vevent):string {
|
||||
$properties = [
|
||||
(string) $vevent->DTSTART->serialize(),
|
||||
];
|
||||
|
||||
if ($vevent->DTEND) {
|
||||
$properties[] = (string) $vevent->DTEND->serialize();
|
||||
}
|
||||
if ($vevent->DURATION) {
|
||||
$properties[] = (string) $vevent->DURATION->serialize();
|
||||
}
|
||||
if ($vevent->{'RECURRENCE-ID'}) {
|
||||
$properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
|
||||
}
|
||||
if ($vevent->RRULE) {
|
||||
$properties[] = (string) $vevent->RRULE->serialize();
|
||||
}
|
||||
if ($vevent->EXDATE) {
|
||||
$properties[] = (string) $vevent->EXDATE->serialize();
|
||||
}
|
||||
if ($vevent->RDATE) {
|
||||
$properties[] = (string) $vevent->RDATE->serialize();
|
||||
}
|
||||
|
||||
return md5(implode('::', $properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hash of the alarm.
|
||||
* If the hash changes, we have to update oc_dav_reminders.
|
||||
*
|
||||
* @param VAlarm $valarm
|
||||
* @return string
|
||||
*/
|
||||
private function getAlarmHash(VAlarm $valarm):string {
|
||||
$properties = [
|
||||
(string) $valarm->ACTION->serialize(),
|
||||
(string) $valarm->TRIGGER->serialize(),
|
||||
];
|
||||
|
||||
if ($valarm->DURATION) {
|
||||
$properties[] = (string) $valarm->DURATION->serialize();
|
||||
}
|
||||
if ($valarm->REPEAT) {
|
||||
$properties[] = (string) $valarm->REPEAT->serialize();
|
||||
}
|
||||
|
||||
return md5(implode('::', $properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Component\VCalendar $vcalendar
|
||||
* @param int $recurrenceId
|
||||
* @param bool $isRecurrenceException
|
||||
* @return VEvent|null
|
||||
*/
|
||||
private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
|
||||
int $recurrenceId,
|
||||
bool $isRecurrenceException):?VEvent {
|
||||
$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
|
||||
if (count($vevents) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$uid = (string) $vevents[0]->UID;
|
||||
$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
|
||||
$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
|
||||
|
||||
// Handle recurrence-exceptions first, because recurrence-expansion is expensive
|
||||
if ($isRecurrenceException) {
|
||||
foreach($recurrenceExceptions as $recurrenceException) {
|
||||
if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
|
||||
return $recurrenceException;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($masterItem) {
|
||||
try {
|
||||
$iterator = new EventIterator($vevents, $uid);
|
||||
} catch (NoInstancesException $e) {
|
||||
// This event is recurring, but it doesn't have a single
|
||||
// instance. We are skipping this event from the output
|
||||
// entirely.
|
||||
return null;
|
||||
}
|
||||
|
||||
while ($iterator->valid()) {
|
||||
$event = $iterator->getEventObject();
|
||||
|
||||
// Recurrence-exceptions are handled separately, so just ignore them here
|
||||
if (\in_array($event, $recurrenceExceptions, true)) {
|
||||
$iterator->next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return string
|
||||
*/
|
||||
private function getStatusOfEvent(VEvent $vevent):string {
|
||||
if ($vevent->STATUS) {
|
||||
return (string) $vevent->STATUS;
|
||||
}
|
||||
|
||||
// Doesn't say so in the standard,
|
||||
// but we consider events without a status
|
||||
// to be confirmed
|
||||
return 'CONFIRMED';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Component\VEvent $vevent
|
||||
* @return bool
|
||||
*/
|
||||
private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
|
||||
return $this->getStatusOfEvent($vevent) === 'CANCELLED';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $calendarData
|
||||
* @return VObject\Component\VCalendar|null
|
||||
*/
|
||||
private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
|
||||
try {
|
||||
return VObject\Reader::read($calendarData,
|
||||
VObject\Reader::OPTION_FORGIVING);
|
||||
} catch(ParseException $ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $principalUri
|
||||
* @return IUser|null
|
||||
*/
|
||||
private function getUserFromPrincipalURI(string $principalUri):?IUser {
|
||||
if (!$principalUri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stripos($principalUri, 'principals/users/') !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userId = substr($principalUri, 17);
|
||||
return $this->userManager->get($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VObject\Component\VCalendar $vcalendar
|
||||
* @return VObject\Component\VEvent[]
|
||||
*/
|
||||
private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
|
||||
$vevents = [];
|
||||
|
||||
foreach($vcalendar->children() as $child) {
|
||||
if (!($child instanceof VObject\Component)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($child->name !== 'VEVENT') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vevents[] = $child;
|
||||
}
|
||||
|
||||
return $vevents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $vevents
|
||||
* @return VObject\Component\VEvent[]
|
||||
*/
|
||||
private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
|
||||
return array_values(array_filter($vevents, function(VEvent $vevent) {
|
||||
return $vevent->{'RECURRENCE-ID'} !== null;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $vevents
|
||||
* @return VEvent|null
|
||||
*/
|
||||
private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
|
||||
$elements = array_values(array_filter($vevents, function(VEvent $vevent) {
|
||||
return $vevent->{'RECURRENCE-ID'} === null;
|
||||
}));
|
||||
|
||||
if (count($elements) === 0) {
|
||||
return null;
|
||||
}
|
||||
if (count($elements) > 1) {
|
||||
throw new \TypeError('Multiple master objects');
|
||||
}
|
||||
|
||||
return $elements[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VAlarm $valarm
|
||||
* @return bool
|
||||
*/
|
||||
private function isAlarmRelative(VAlarm $valarm):bool {
|
||||
$trigger = $valarm->TRIGGER;
|
||||
return $trigger instanceof VObject\Property\ICalendar\Duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return int
|
||||
*/
|
||||
private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
|
||||
if (isset($vevent->{'RECURRENCE-ID'})) {
|
||||
return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
|
||||
}
|
||||
|
||||
return $vevent->DTSTART->getDateTime()->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return bool
|
||||
*/
|
||||
private function isRecurring(VEvent $vevent):bool {
|
||||
return isset($vevent->RRULE) || isset($vevent->RDATE);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright 2019, Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2018, Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
@@ -22,8 +22,6 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\ResourceBooking;
|
||||
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\Traits\PrincipalProxyTrait;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
@@ -46,21 +44,12 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
/** @var ProxyMapper */
|
||||
private $proxyMapper;
|
||||
|
||||
/** @var string */
|
||||
private $principalPrefix;
|
||||
|
||||
/** @var string */
|
||||
private $dbTableName;
|
||||
|
||||
/** @var string */
|
||||
private $dbMetaDataTableName;
|
||||
|
||||
/** @var string */
|
||||
private $dbForeignKeyName;
|
||||
|
||||
/** @var string */
|
||||
private $cuType;
|
||||
|
||||
@@ -77,7 +66,6 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
ILogger $logger,
|
||||
ProxyMapper $proxyMapper,
|
||||
string $principalPrefix,
|
||||
string $dbPrefix,
|
||||
string $cuType) {
|
||||
@@ -85,16 +73,11 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
$this->userSession = $userSession;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->logger = $logger;
|
||||
$this->proxyMapper = $proxyMapper;
|
||||
$this->principalPrefix = $principalPrefix;
|
||||
$this->dbTableName = 'calendar_' . $dbPrefix . 's';
|
||||
$this->dbMetaDataTableName = $this->dbTableName . '_md';
|
||||
$this->dbForeignKeyName = $dbPrefix . '_id';
|
||||
$this->dbTableName = 'calendar_' . $dbPrefix;
|
||||
$this->cuType = $cuType;
|
||||
}
|
||||
|
||||
use PrincipalProxyTrait;
|
||||
|
||||
/**
|
||||
* Returns a list of principals based on a prefix.
|
||||
*
|
||||
@@ -117,31 +100,8 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
->from($this->dbTableName);
|
||||
$stmt = $query->execute();
|
||||
|
||||
$metaDataQuery = $this->db->getQueryBuilder();
|
||||
$metaDataQuery->select([$this->dbForeignKeyName, 'key', 'value'])
|
||||
->from($this->dbMetaDataTableName);
|
||||
$metaDataStmt = $metaDataQuery->execute();
|
||||
$metaDataRows = $metaDataStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$metaDataById = [];
|
||||
foreach($metaDataRows as $metaDataRow) {
|
||||
if (!isset($metaDataById[$metaDataRow[$this->dbForeignKeyName]])) {
|
||||
$metaDataById[$metaDataRow[$this->dbForeignKeyName]] = [];
|
||||
}
|
||||
|
||||
$metaDataById[$metaDataRow[$this->dbForeignKeyName]][$metaDataRow['key']] =
|
||||
$metaDataRow['value'];
|
||||
}
|
||||
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$id = $row['id'];
|
||||
|
||||
if (isset($metaDataById[$id])) {
|
||||
$principals[] = $this->rowToPrincipal($row, $metaDataById[$id]);
|
||||
} else {
|
||||
$principals[] = $this->rowToPrincipal($row);
|
||||
}
|
||||
|
||||
$principals[] = $this->rowToPrincipal($row);
|
||||
}
|
||||
|
||||
$stmt->closeCursor();
|
||||
@@ -178,50 +138,40 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metaDataQuery = $this->db->getQueryBuilder();
|
||||
$metaDataQuery->select(['key', 'value'])
|
||||
->from($this->dbMetaDataTableName)
|
||||
->where($metaDataQuery->expr()->eq($this->dbForeignKeyName, $metaDataQuery->createNamedParameter($row['id'])));
|
||||
$metaDataStmt = $metaDataQuery->execute();
|
||||
$metaDataRows = $metaDataStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$metadata = [];
|
||||
|
||||
foreach($metaDataRows as $metaDataRow) {
|
||||
$metadata[$metaDataRow['key']] = $metaDataRow['value'];
|
||||
}
|
||||
|
||||
return $this->rowToPrincipal($row, $metadata);
|
||||
return $this->rowToPrincipal($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return array|null
|
||||
* Returns the list of members for a group-principal
|
||||
*
|
||||
* @param string $principal
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPrincipalById($id):?array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
|
||||
->from($this->dbTableName)
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
|
||||
$stmt = $query->execute();
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
public function getGroupMemberSet($principal) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if(!$row) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Returns the list of groups a principal is a member of
|
||||
*
|
||||
* @param string $principal
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMembership($principal) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$metaDataQuery = $this->db->getQueryBuilder();
|
||||
$metaDataQuery->select(['key', 'value'])
|
||||
->from($this->dbMetaDataTableName)
|
||||
->where($metaDataQuery->expr()->eq($this->dbForeignKeyName, $metaDataQuery->createNamedParameter($row['id'])));
|
||||
$metaDataStmt = $metaDataQuery->execute();
|
||||
$metaDataRows = $metaDataStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$metadata = [];
|
||||
|
||||
foreach($metaDataRows as $metaDataRow) {
|
||||
$metadata[$metaDataRow['key']] = $metaDataRow['value'];
|
||||
}
|
||||
|
||||
return $this->rowToPrincipal($row, $metadata);
|
||||
/**
|
||||
* Updates the list of group members for a group principal.
|
||||
*
|
||||
* The principals should be passed as a list of uri's.
|
||||
*
|
||||
* @param string $principal
|
||||
* @param string[] $members
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setGroupMemberSet($principal, array $members) {
|
||||
throw new Exception('Setting members of the group is not supported yet');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,15 +253,7 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
break;
|
||||
|
||||
default:
|
||||
$rowsByMetadata = $this->searchPrincipalsByMetadataKey($prop, $value);
|
||||
$filteredRows = array_filter($rowsByMetadata, function($row) use ($usersGroups) {
|
||||
return $this->isAllowedToAccessResource($row, $usersGroups);
|
||||
});
|
||||
|
||||
$results[] = array_map(function($row) {
|
||||
return $row['uri'];
|
||||
}, $filteredRows);
|
||||
|
||||
$results[] = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -332,39 +274,6 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches principals based on their metadata keys.
|
||||
* This allows to search for all principals with a specific key.
|
||||
* e.g.:
|
||||
* '{http://nextcloud.com/ns}room-building-address' => 'ABC Street 123, ...'
|
||||
*
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
private function searchPrincipalsByMetadataKey($key, $value):array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select([$this->dbForeignKeyName])
|
||||
->from($this->dbMetaDataTableName)
|
||||
->where($query->expr()->eq('key', $query->createNamedParameter($key)))
|
||||
->andWhere($query->expr()->iLike('value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
|
||||
$stmt = $query->execute();
|
||||
|
||||
$rows = [];
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$id = $row[$this->dbForeignKeyName];
|
||||
|
||||
$principalRow = $this->getPrincipalById($id);
|
||||
if (!$principalRow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = $principalRow;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param string $principalPrefix
|
||||
@@ -429,18 +338,14 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
|
||||
/**
|
||||
* convert database row to principal
|
||||
*
|
||||
* @param String[] $row
|
||||
* @param String[] $metadata
|
||||
* @return Array
|
||||
*/
|
||||
private function rowToPrincipal(array $row, array $metadata=[]):array {
|
||||
return array_merge([
|
||||
private function rowToPrincipal($row) {
|
||||
return [
|
||||
'uri' => $this->principalPrefix . '/' . $row['backend_id'] . '-' . $row['resource_id'],
|
||||
'{DAV:}displayname' => $row['displayname'],
|
||||
'{http://sabredav.org/ns}email-address' => $row['email'],
|
||||
'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => $this->cuType,
|
||||
], $metadata);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,7 +353,7 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
|
||||
* @param $userGroups
|
||||
* @return bool
|
||||
*/
|
||||
private function isAllowedToAccessResource(array $row, array $userGroups):bool {
|
||||
private function isAllowedToAccessResource($row, $userGroups) {
|
||||
if (!isset($row['group_restrictions']) ||
|
||||
$row['group_restrictions'] === null ||
|
||||
$row['group_restrictions'] === '') {
|
||||
|
||||
@@ -22,34 +22,24 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\ResourceBooking;
|
||||
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Class ResourcePrincipalBackend
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\ResourceBooking
|
||||
*/
|
||||
class ResourcePrincipalBackend extends AbstractPrincipalBackend {
|
||||
|
||||
/**
|
||||
* ResourcePrincipalBackend constructor.
|
||||
*
|
||||
* @param IDBConnection $dbConnection
|
||||
* @param IUserSession $userSession
|
||||
* @param IGroupManager $groupManager
|
||||
* @param ILogger $logger
|
||||
* @param ProxyMapper $proxyMapper
|
||||
*/
|
||||
public function __construct(IDBConnection $dbConnection,
|
||||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
ILogger $logger,
|
||||
ProxyMapper $proxyMapper) {
|
||||
ILogger $logger) {
|
||||
parent::__construct($dbConnection, $userSession, $groupManager, $logger,
|
||||
$proxyMapper, 'principals/calendar-resources', 'resource', 'RESOURCE');
|
||||
'principals/calendar-resources', 'resources', 'RESOURCE');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,34 +22,24 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\ResourceBooking;
|
||||
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Class RoomPrincipalBackend
|
||||
*
|
||||
* @package OCA\DAV\CalDAV\ResourceBooking
|
||||
*/
|
||||
class RoomPrincipalBackend extends AbstractPrincipalBackend {
|
||||
|
||||
/**
|
||||
* RoomPrincipalBackend constructor.
|
||||
*
|
||||
* @param IDBConnection $dbConnection
|
||||
* @param IUserSession $userSession
|
||||
* @param IGroupManager $groupManager
|
||||
* @param ILogger $logger
|
||||
* @param ProxyMapper $proxyMapper
|
||||
*/
|
||||
public function __construct(IDBConnection $dbConnection,
|
||||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
ILogger $logger,
|
||||
ProxyMapper $proxyMapper) {
|
||||
ILogger $logger) {
|
||||
parent::__construct($dbConnection, $userSession, $groupManager, $logger,
|
||||
$proxyMapper, 'principals/calendar-rooms', 'room', 'ROOM');
|
||||
'principals/calendar-rooms', 'rooms', 'ROOM');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,42 +239,9 @@ class IMipPlugin extends SabreIMipPlugin {
|
||||
$meetingAttendeeName, $meetingInviteeName);
|
||||
$this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
|
||||
$meetingDescription, $meetingUrl);
|
||||
|
||||
|
||||
// Only add response buttons to invitation requests: Fix Issue #11230
|
||||
if (($method == self::METHOD_REQUEST) && $this->getAttendeeRSVP($attendee)) {
|
||||
|
||||
/*
|
||||
** Only offer invitation accept/reject buttons, which link back to the
|
||||
** nextcloud server, to recipients who can access the nextcloud server via
|
||||
** their internet/intranet. Issue #12156
|
||||
**
|
||||
** The app setting is stored in the appconfig database table.
|
||||
**
|
||||
** For nextcloud servers accessible to the public internet, the default
|
||||
** "invitation_link_recipients" value "yes" (all recipients) is appropriate.
|
||||
**
|
||||
** When the nextcloud server is restricted behind a firewall, accessible
|
||||
** only via an internal network or via vpn, you can set "dav.invitation_link_recipients"
|
||||
** to the email address or email domain, or comma separated list of addresses or domains,
|
||||
** of recipients who can access the server.
|
||||
**
|
||||
** To always deliver URLs, set invitation_link_recipients to "yes".
|
||||
** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
|
||||
*/
|
||||
|
||||
$recipientDomain = substr(strrchr($recipient, "@"), 1);
|
||||
$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getAppValue('dav', 'invitation_link_recipients', 'yes'))));
|
||||
|
||||
if (strcmp('yes', $invitationLinkRecipients[0]) === 0
|
||||
|| in_array(strtolower($recipient), $invitationLinkRecipients)
|
||||
|| in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
|
||||
$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
|
||||
}
|
||||
}
|
||||
$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
|
||||
|
||||
$template->addFooter();
|
||||
|
||||
$message->useTemplate($template);
|
||||
|
||||
$attachment = $this->mailer->createAttachment(
|
||||
@@ -378,21 +345,6 @@ class IMipPlugin extends SabreIMipPlugin {
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property|null $attendee
|
||||
* @return bool
|
||||
*/
|
||||
private function getAttendeeRSVP(Property $attendee = null) {
|
||||
if ($attendee !== null) {
|
||||
$rsvp = $attendee->offsetGet('RSVP');
|
||||
if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// RFC 5545 3.2.17: default RSVP is false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param Property $dtstart
|
||||
@@ -499,6 +451,7 @@ class IMipPlugin extends SabreIMipPlugin {
|
||||
$template->setSubject('Invitation: ' . $summary);
|
||||
$template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -555,7 +508,7 @@ class IMipPlugin extends SabreIMipPlugin {
|
||||
$moreOptionsURL, $l10n->t('More options …')
|
||||
]);
|
||||
$text = $l10n->t('More options at %s', [$moreOptionsURL]);
|
||||
|
||||
|
||||
$template->addBodyText($html, $text);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,36 +24,20 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Schedule;
|
||||
|
||||
use DateTimeZone;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarHome;
|
||||
use Sabre\CalDAV\ICalendar;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\IProperties;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\Xml\Property\LocalHref;
|
||||
use Sabre\DAVACL\IPrincipal;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\VObject\Component;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\VObject\ITip;
|
||||
use Sabre\VObject\Parameter;
|
||||
use Sabre\VObject\Property;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\VObject\FreeBusyGenerator;
|
||||
|
||||
class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
|
||||
/** @var ITip\Message[] */
|
||||
private $schedulingResponses = [];
|
||||
|
||||
/** @var string|null */
|
||||
private $pathOfCalendarObjectChange = null;
|
||||
|
||||
/**
|
||||
* Initializes the plugin
|
||||
*
|
||||
@@ -63,8 +47,6 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
function initialize(Server $server) {
|
||||
parent::initialize($server);
|
||||
$server->on('propFind', [$this, 'propFindDefaultCalendarUrl'], 90);
|
||||
$server->on('afterWriteContent', [$this, 'dispatchSchedulingResponses']);
|
||||
$server->on('afterCreateFile', [$this, 'dispatchSchedulingResponses']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,21 +59,17 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
* @return void
|
||||
*/
|
||||
function propFind(PropFind $propFind, INode $node) {
|
||||
if ($node instanceof IPrincipal) {
|
||||
// overwrite Sabre/Dav's implementation
|
||||
$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function () use ($node) {
|
||||
if ($node instanceof IProperties) {
|
||||
$calendarUserType = '{' . self::NS_CALDAV . '}calendar-user-type';
|
||||
$props = $node->getProperties([$calendarUserType]);
|
||||
// overwrite Sabre/Dav's implementation
|
||||
$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() use ($node) {
|
||||
$calendarUserType = '{' . self::NS_CALDAV . '}calendar-user-type';
|
||||
$props = $node->getProperties([$calendarUserType]);
|
||||
|
||||
if (isset($props[$calendarUserType])) {
|
||||
return $props[$calendarUserType];
|
||||
}
|
||||
}
|
||||
if (isset($props[$calendarUserType])) {
|
||||
return $props[$calendarUserType];
|
||||
}
|
||||
|
||||
return 'INDIVIDUAL';
|
||||
});
|
||||
}
|
||||
return 'INDIVIDUAL';
|
||||
});
|
||||
|
||||
parent::propFind($propFind, $node);
|
||||
}
|
||||
@@ -112,144 +90,6 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param VCalendar $vCal
|
||||
* @param mixed $calendarPath
|
||||
* @param mixed $modified
|
||||
* @param mixed $isNew
|
||||
*/
|
||||
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
|
||||
// Save the first path we get as a calendar-object-change request
|
||||
if (!$this->pathOfCalendarObjectChange) {
|
||||
$this->pathOfCalendarObjectChange = $request->getPath();
|
||||
}
|
||||
|
||||
parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
|
||||
parent::scheduleLocalDelivery($iTipMessage);
|
||||
|
||||
// We only care when the message was successfully delivered locally
|
||||
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only care about request. reply and cancel are properly handled
|
||||
// by parent::scheduleLocalDelivery already
|
||||
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If parent::scheduleLocalDelivery set scheduleStatus to 1.2,
|
||||
// it means that it was successfully delivered locally.
|
||||
// Meaning that the ACL plugin is loaded and that a principial
|
||||
// exists for the given recipient id, no need to double check
|
||||
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
|
||||
$aclPlugin = $this->server->getPlugin('acl');
|
||||
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
|
||||
$calendarUserType = $this->getCalendarUserTypeForPrincipal($principalUri);
|
||||
if (strcasecmp($calendarUserType, 'ROOM') !== 0 && strcasecmp($calendarUserType, 'RESOURCE') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attendee = $this->getCurrentAttendee($iTipMessage);
|
||||
if (!$attendee) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only respond when a response was actually requested
|
||||
$rsvp = $this->getAttendeeRSVP($attendee);
|
||||
if (!$rsvp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($iTipMessage->message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$vcalendar = $iTipMessage->message;
|
||||
if (!isset($vcalendar->VEVENT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Component $vevent */
|
||||
$vevent = $vcalendar->VEVENT;
|
||||
|
||||
// We don't support autoresponses for recurrencing events for now
|
||||
if (isset($vevent->RRULE) || isset($vevent->RDATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dtstart = $vevent->DTSTART;
|
||||
$dtend = $this->getDTEndFromVEvent($vevent);
|
||||
$uid = $vevent->UID->getValue();
|
||||
$sequence = isset($vevent->SEQUENCE) ? $vevent->SEQUENCE->getValue() : 0;
|
||||
$recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->serialize() : '';
|
||||
|
||||
$message = <<<EOF
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
|
||||
METHOD:REPLY
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
ATTENDEE;PARTSTAT=%s:%s
|
||||
ORGANIZER:%s
|
||||
UID:%s
|
||||
SEQUENCE:%s
|
||||
REQUEST-STATUS:2.0;Success
|
||||
%sEND:VEVENT
|
||||
END:VCALENDAR
|
||||
EOF;
|
||||
|
||||
if ($this->isAvailableAtTime($attendee->getValue(), $dtstart->getDateTime(), $dtend->getDateTime(), $uid)) {
|
||||
$partStat = 'ACCEPTED';
|
||||
} else {
|
||||
$partStat = 'DECLINED';
|
||||
}
|
||||
|
||||
$vObject = Reader::read(vsprintf($message, [
|
||||
$partStat,
|
||||
$iTipMessage->recipient,
|
||||
$iTipMessage->sender,
|
||||
$uid,
|
||||
$sequence,
|
||||
$recurrenceId
|
||||
]));
|
||||
|
||||
$responseITipMessage = new ITip\Message();
|
||||
$responseITipMessage->uid = $uid;
|
||||
$responseITipMessage->component = 'VEVENT';
|
||||
$responseITipMessage->method = 'REPLY';
|
||||
$responseITipMessage->sequence = $sequence;
|
||||
$responseITipMessage->sender = $iTipMessage->recipient;
|
||||
$responseITipMessage->recipient = $iTipMessage->sender;
|
||||
$responseITipMessage->message = $vObject;
|
||||
|
||||
// We can't dispatch them now already, because the organizers calendar-object
|
||||
// was not yet created. Hence Sabre/DAV won't find a calendar-object, when we
|
||||
// send our reply.
|
||||
$this->schedulingResponses[] = $responseITipMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
*/
|
||||
public function dispatchSchedulingResponses(string $uri):void {
|
||||
if ($uri !== $this->pathOfCalendarObjectChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->schedulingResponses as $schedulingResponse) {
|
||||
$this->scheduleLocalDelivery($schedulingResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always use the personal calendar as target for scheduled events
|
||||
*
|
||||
@@ -301,236 +141,65 @@ EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of addresses that are associated with a principal.
|
||||
* This method is triggered whenever there was a calendar object gets
|
||||
* created or updated.
|
||||
*
|
||||
* @param string $principal
|
||||
* @return string?
|
||||
* Basically just a copy of parent::calendarObjectChange, with the change
|
||||
* from:
|
||||
* $addresses = $this->getAddressesForPrincipal($calendarNode->getOwner());
|
||||
* to:
|
||||
* $addresses = $this->getAddressesForPrincipal($calendarNode->getPrincipalURI());
|
||||
*
|
||||
* @param RequestInterface $request HTTP request
|
||||
* @param ResponseInterface $response HTTP Response
|
||||
* @param VCalendar $vCal Parsed iCalendar object
|
||||
* @param mixed $calendarPath Path to calendar collection
|
||||
* @param mixed $modified The iCalendar object has been touched.
|
||||
* @param mixed $isNew Whether this was a new item or we're updating one
|
||||
* @return void
|
||||
*/
|
||||
protected function getCalendarUserTypeForPrincipal($principal):?string {
|
||||
$calendarUserType = '{' . self::NS_CALDAV . '}calendar-user-type';
|
||||
$properties = $this->server->getProperties(
|
||||
$principal,
|
||||
[$calendarUserType]
|
||||
function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
|
||||
|
||||
if (!$this->scheduleReply($this->server->httpRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
|
||||
|
||||
$addresses = $this->getAddressesForPrincipal(
|
||||
$calendarNode->getPrincipalURI()
|
||||
);
|
||||
|
||||
// If we can't find this information, we'll stop processing
|
||||
if (!isset($properties[$calendarUserType])) {
|
||||
return null;
|
||||
if (!$isNew) {
|
||||
$node = $this->server->tree->getNodeForPath($request->getPath());
|
||||
$oldObj = Reader::read($node->get());
|
||||
} else {
|
||||
$oldObj = null;
|
||||
}
|
||||
|
||||
$this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
|
||||
|
||||
if ($oldObj) {
|
||||
// Destroy circular references so PHP will GC the object.
|
||||
$oldObj->destroy();
|
||||
}
|
||||
|
||||
return $properties[$calendarUserType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ITip\Message $iTipMessage
|
||||
* @return null|Property
|
||||
*/
|
||||
private function getCurrentAttendee(ITip\Message $iTipMessage):?Property {
|
||||
/** @var VEvent $vevent */
|
||||
$vevent = $iTipMessage->message->VEVENT;
|
||||
$attendees = $vevent->select('ATTENDEE');
|
||||
foreach ($attendees as $attendee) {
|
||||
/** @var Property $attendee */
|
||||
if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
|
||||
return $attendee;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property|null $attendee
|
||||
* This method checks the 'Schedule-Reply' header
|
||||
* and returns false if it's 'F', otherwise true.
|
||||
*
|
||||
* Copied from Sabre/DAV's Schedule plugin, because it's
|
||||
* private for whatever reason
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
private function getAttendeeRSVP(Property $attendee = null):bool {
|
||||
if ($attendee !== null) {
|
||||
$rsvp = $attendee->offsetGet('RSVP');
|
||||
if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// RFC 5545 3.2.17: default RSVP is false
|
||||
return false;
|
||||
}
|
||||
private function scheduleReply(RequestInterface $request) {
|
||||
|
||||
/**
|
||||
* @param VEvent $vevent
|
||||
* @return Property\ICalendar\DateTime
|
||||
*/
|
||||
private function getDTEndFromVEvent(VEvent $vevent):Property\ICalendar\DateTime {
|
||||
if (isset($vevent->DTEND)) {
|
||||
return $vevent->DTEND;
|
||||
}
|
||||
$scheduleReply = $request->getHeader('Schedule-Reply');
|
||||
return $scheduleReply !== 'F';
|
||||
|
||||
if (isset($vevent->DURATION)) {
|
||||
$isFloating = $vevent->DTSTART->isFloating();
|
||||
/** @var Property\ICalendar\DateTime $end */
|
||||
$end = clone $vevent->DTSTART;
|
||||
$endDateTime = $end->getDateTime();
|
||||
$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
|
||||
$end->setDateTime($endDateTime, $isFloating);
|
||||
return $end;
|
||||
}
|
||||
|
||||
if (!$vevent->DTSTART->hasTime()) {
|
||||
$isFloating = $vevent->DTSTART->isFloating();
|
||||
/** @var Property\ICalendar\DateTime $end */
|
||||
$end = clone $vevent->DTSTART;
|
||||
$endDateTime = $end->getDateTime();
|
||||
$endDateTime = $endDateTime->modify('+1 day');
|
||||
$end->setDateTime($endDateTime, $isFloating);
|
||||
return $end;
|
||||
}
|
||||
|
||||
return clone $vevent->DTSTART;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param \DateTimeInterface $start
|
||||
* @param \DateTimeInterface $end
|
||||
* @param string $ignoreUID
|
||||
* @return bool
|
||||
*/
|
||||
private function isAvailableAtTime(string $email, \DateTimeInterface $start, \DateTimeInterface $end, string $ignoreUID):bool {
|
||||
// This method is heavily inspired by Sabre\CalDAV\Schedule\Plugin::scheduleLocalDelivery
|
||||
// and Sabre\CalDAV\Schedule\Plugin::getFreeBusyForEmail
|
||||
|
||||
$aclPlugin = $this->server->getPlugin('acl');
|
||||
$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
|
||||
|
||||
$result = $aclPlugin->principalSearch(
|
||||
['{http://sabredav.org/ns}email-address' => $this->stripOffMailTo($email)],
|
||||
[
|
||||
'{DAV:}principal-URL',
|
||||
'{' . self::NS_CALDAV . '}calendar-home-set',
|
||||
'{' . self::NS_CALDAV . '}schedule-inbox-URL',
|
||||
'{http://sabredav.org/ns}email-address',
|
||||
|
||||
]
|
||||
);
|
||||
$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
|
||||
|
||||
|
||||
// Grabbing the calendar list
|
||||
$objects = [];
|
||||
$calendarTimeZone = new DateTimeZone('UTC');
|
||||
|
||||
$homePath = $result[0][200]['{' . self::NS_CALDAV . '}calendar-home-set']->getHref();
|
||||
foreach ($this->server->tree->getNodeForPath($homePath)->getChildren() as $node) {
|
||||
if (!$node instanceof ICalendar) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Getting the list of object uris within the time-range
|
||||
$urls = $node->calendarQuery([
|
||||
'name' => 'VCALENDAR',
|
||||
'comp-filters' => [
|
||||
[
|
||||
'name' => 'VEVENT',
|
||||
'is-not-defined' => false,
|
||||
'time-range' => [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
],
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'VEVENT',
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [
|
||||
[
|
||||
'name' => 'UID',
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
'text-match' => [
|
||||
'value' => $ignoreUID,
|
||||
'negate-condition' => true,
|
||||
'collation' => 'i;octet',
|
||||
],
|
||||
'param-filters' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'prop-filters' => [],
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
]);
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$objects[] = $node->getChild($url)->get();
|
||||
}
|
||||
}
|
||||
|
||||
$inboxProps = $this->server->getProperties(
|
||||
$result[0][200]['{' . self::NS_CALDAV . '}schedule-inbox-URL']->getHref(),
|
||||
['{' . self::NS_CALDAV . '}calendar-availability']
|
||||
);
|
||||
|
||||
$vcalendar = new VCalendar();
|
||||
$vcalendar->METHOD = 'REPLY';
|
||||
|
||||
$generator = new FreeBusyGenerator();
|
||||
$generator->setObjects($objects);
|
||||
$generator->setTimeRange($start, $end);
|
||||
$generator->setBaseObject($vcalendar);
|
||||
$generator->setTimeZone($calendarTimeZone);
|
||||
|
||||
if (isset($inboxProps['{' . self::NS_CALDAV . '}calendar-availability'])) {
|
||||
$generator->setVAvailability(
|
||||
Reader::read(
|
||||
$inboxProps['{' . self::NS_CALDAV . '}calendar-availability']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$result = $generator->getResult();
|
||||
if (!isset($result->VFREEBUSY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Component $freeBusyComponent */
|
||||
$freeBusyComponent = $result->VFREEBUSY;
|
||||
$freeBusyProperties = $freeBusyComponent->select('FREEBUSY');
|
||||
// If there is no Free-busy property at all, the time-range is empty and available
|
||||
if (count($freeBusyProperties) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If more than one Free-Busy property was returned, it means that an event
|
||||
// starts or ends inside this time-range, so it's not availabe and we return false
|
||||
if (count($freeBusyProperties) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Property $freeBusyProperty */
|
||||
$freeBusyProperty = $freeBusyProperties[0];
|
||||
if (!$freeBusyProperty->offsetExists('FBTYPE')) {
|
||||
// If there is no FBTYPE, it means it's busy
|
||||
return false;
|
||||
}
|
||||
|
||||
$fbTypeParameter = $freeBusyProperty->offsetGet('FBTYPE');
|
||||
if (!($fbTypeParameter instanceof Parameter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (strcasecmp($fbTypeParameter->getValue(), 'FREE') === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
private function stripOffMailTo(string $email): string {
|
||||
if (stripos($email, 'mailto:') === 0) {
|
||||
return substr($email, 7);
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,424 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2020, Thomas Citharel <nextcloud@tcit.fr>
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Citharel <nextcloud@tcit.fr>
|
||||
*
|
||||
* @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\DAV\CalDAV\WebcalCaching;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\DAV\Xml\Property\Href;
|
||||
use Sabre\VObject\Component;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\VObject\Splitter\ICalendar;
|
||||
use Sabre\VObject\UUIDUtil;
|
||||
use function count;
|
||||
|
||||
class RefreshWebcalService {
|
||||
|
||||
/** @var CalDavBackend */
|
||||
private $calDavBackend;
|
||||
|
||||
/** @var IClientService */
|
||||
private $clientService;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
|
||||
public const STRIP_ALARMS = '{http://calendarserver.org/ns/}subscribed-strip-alarms';
|
||||
public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
|
||||
public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
|
||||
|
||||
/**
|
||||
* RefreshWebcalJob constructor.
|
||||
*
|
||||
* @param CalDavBackend $calDavBackend
|
||||
* @param IClientService $clientService
|
||||
* @param IConfig $config
|
||||
* @param ILogger $logger
|
||||
*/
|
||||
public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, ILogger $logger) {
|
||||
$this->calDavBackend = $calDavBackend;
|
||||
$this->clientService = $clientService;
|
||||
$this->config = $config;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $principalUri
|
||||
* @param string $uri
|
||||
*/
|
||||
public function refreshSubscription(string $principalUri, string $uri) {
|
||||
$subscription = $this->getSubscription($principalUri, $uri);
|
||||
$mutations = [];
|
||||
if (!$subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webcalData = $this->queryWebcalFeed($subscription, $mutations);
|
||||
if (!$webcalData) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
|
||||
$stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
|
||||
$stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
|
||||
|
||||
try {
|
||||
$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
|
||||
|
||||
// we wait with deleting all outdated events till we parsed the new ones
|
||||
// in case the new calendar is broken and `new ICalendar` throws a ParseException
|
||||
// the user will still see the old data
|
||||
$this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
|
||||
|
||||
while ($vObject = $splitter->getNext()) {
|
||||
/** @var Component $vObject */
|
||||
$compName = null;
|
||||
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
if ($component->name === 'VTIMEZONE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$compName = $component->name;
|
||||
|
||||
if ($stripAlarms) {
|
||||
unset($component->{'VALARM'});
|
||||
}
|
||||
if ($stripAttachments) {
|
||||
unset($component->{'ATTACH'});
|
||||
}
|
||||
}
|
||||
|
||||
if ($stripTodos && $compName === 'VTODO') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uri = $this->getRandomCalendarObjectUri();
|
||||
$calendarData = $vObject->serialize();
|
||||
try {
|
||||
$this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
} catch(BadRequest $ex) {
|
||||
$this->logger->logException($ex);
|
||||
}
|
||||
}
|
||||
|
||||
$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
|
||||
if ($newRefreshRate) {
|
||||
$mutations[self::REFRESH_RATE] = $newRefreshRate;
|
||||
}
|
||||
|
||||
$this->updateSubscription($subscription, $mutations);
|
||||
} catch(ParseException $ex) {
|
||||
$subscriptionId = $subscription['id'];
|
||||
|
||||
$this->logger->logException($ex);
|
||||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a parsing error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads subscription from backend
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $uri
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSubscription(string $principalUri, string $uri) {
|
||||
$subscriptions = array_values(array_filter(
|
||||
$this->calDavBackend->getSubscriptionsForUser($principalUri),
|
||||
function($sub) use ($uri) {
|
||||
return $sub['uri'] === $uri;
|
||||
}
|
||||
));
|
||||
|
||||
if (count($subscriptions) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $subscriptions[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* gets webcal feed from remote server
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param array &$mutations
|
||||
* @return null|string
|
||||
*/
|
||||
private function queryWebcalFeed(array $subscription, array &$mutations) {
|
||||
$client = $this->clientService->newClient();
|
||||
|
||||
$didBreak301Chain = false;
|
||||
$latestLocation = null;
|
||||
|
||||
$handlerStack = HandlerStack::create();
|
||||
$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
|
||||
return $request
|
||||
->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
|
||||
->withHeader('User-Agent', 'Nextcloud Webcal Crawler');
|
||||
}));
|
||||
$handlerStack->push(Middleware::mapResponse(function(ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
|
||||
if (!$didBreak301Chain) {
|
||||
if ($response->getStatusCode() !== 301) {
|
||||
$didBreak301Chain = true;
|
||||
} else {
|
||||
$latestLocation = $response->getHeader('Location');
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}));
|
||||
|
||||
$allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
|
||||
$subscriptionId = $subscription['id'];
|
||||
$url = $this->cleanURL($subscription['source']);
|
||||
if ($url === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($allowLocalAccess !== 'yes') {
|
||||
$host = strtolower(parse_url($url, PHP_URL_HOST));
|
||||
// remove brackets from IPv6 addresses
|
||||
if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
|
||||
$host = substr($host, 1, -1);
|
||||
}
|
||||
|
||||
// Disallow localhost and local network
|
||||
if ($host === 'localhost' || substr($host, -6) === '.local' || substr($host, -10) === '.localhost') {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disallow hostname only
|
||||
if (substr_count($host, '.') === 0) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((bool)filter_var($host, FILTER_VALIDATE_IP) && !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Also check for IPv6 IPv4 nesting, because that's not covered by filter_var
|
||||
if ((bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && substr_count($host, '.') > 0) {
|
||||
$delimiter = strrpos($host, ':'); // Get last colon
|
||||
$ipv4Address = substr($host, $delimiter + 1);
|
||||
|
||||
if (!filter_var($ipv4Address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$params = [
|
||||
'allow_redirects' => [
|
||||
'redirects' => 10
|
||||
],
|
||||
'handler' => $handlerStack,
|
||||
];
|
||||
|
||||
$user = parse_url($subscription['source'], PHP_URL_USER);
|
||||
$pass = parse_url($subscription['source'], PHP_URL_PASS);
|
||||
if ($user !== null && $pass !== null) {
|
||||
$params['auth'] = [$user, $pass];
|
||||
}
|
||||
|
||||
$response = $client->get($url, $params);
|
||||
$body = $response->getBody();
|
||||
|
||||
if ($latestLocation) {
|
||||
$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
|
||||
}
|
||||
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
$contentType = explode(';', $contentType, 2)[0];
|
||||
switch($contentType) {
|
||||
case 'application/calendar+json':
|
||||
try {
|
||||
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
|
||||
} catch(Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $jCalendar->serialize();
|
||||
|
||||
case 'application/calendar+xml':
|
||||
try {
|
||||
$xCalendar = Reader::readXML($body);
|
||||
} catch(Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $xCalendar->serialize();
|
||||
|
||||
case 'text/calendar':
|
||||
default:
|
||||
try {
|
||||
$vCalendar = Reader::read($body);
|
||||
} catch(Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->debug("Subscription $subscriptionId could not be parsed");
|
||||
return null;
|
||||
}
|
||||
return $vCalendar->serialize();
|
||||
}
|
||||
} catch(Exception $ex) {
|
||||
$this->logger->logException($ex);
|
||||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if:
|
||||
* - current subscription stores a refreshrate
|
||||
* - the webcal feed suggests a refreshrate
|
||||
* - return suggested refreshrate if user didn't set a custom one
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param string $webcalData
|
||||
* @return string|null
|
||||
*/
|
||||
private function checkWebcalDataForRefreshRate($subscription, $webcalData) {
|
||||
// if there is no refreshrate stored in the database, check the webcal feed
|
||||
// whether it suggests any refresh rate and store that in the database
|
||||
if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Component\VCalendar $vCalendar */
|
||||
$vCalendar = Reader::read($webcalData);
|
||||
|
||||
$newRefreshRate = null;
|
||||
if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
|
||||
$newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
|
||||
}
|
||||
if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
|
||||
$newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
|
||||
}
|
||||
|
||||
if (!$newRefreshRate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if new refresh rate is even valid
|
||||
try {
|
||||
DateTimeParser::parseDuration($newRefreshRate);
|
||||
} catch(InvalidDataException $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $newRefreshRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* update subscription stored in database
|
||||
* used to set:
|
||||
* - refreshrate
|
||||
* - source
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param array $mutations
|
||||
*/
|
||||
private function updateSubscription(array $subscription, array $mutations) {
|
||||
if (empty($mutations)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propPatch = new PropPatch($mutations);
|
||||
$this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
|
||||
$propPatch->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will strip authentication information and replace the
|
||||
* 'webcal' or 'webcals' protocol scheme
|
||||
*
|
||||
* @param string $url
|
||||
* @return string|null
|
||||
*/
|
||||
private function cleanURL(string $url) {
|
||||
$parsed = parse_url($url);
|
||||
if ($parsed === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
|
||||
$scheme = 'http';
|
||||
} else {
|
||||
$scheme = 'https';
|
||||
}
|
||||
|
||||
$host = $parsed['host'] ?? '';
|
||||
$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
|
||||
$path = $parsed['path'] ?? '';
|
||||
$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
|
||||
$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
|
||||
|
||||
$cleanURL = "$scheme://$host$port$path$query$fragment";
|
||||
// parse_url is giving some weird results if no url and no :// is given,
|
||||
// so let's test the url again
|
||||
$parsedClean = parse_url($cleanURL);
|
||||
if ($parsedClean === false || !isset($parsedClean['host'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $cleanURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random uri for a calendar-object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRandomCalendarObjectUri():string {
|
||||
return UUIDUtil::getUUID() . '.ics';
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@
|
||||
namespace OCA\DAV\CardDAV;
|
||||
|
||||
use OCA\DAV\DAV\Sharing\IShareable;
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use OCP\IL10N;
|
||||
use Sabre\CardDAV\Backend\BackendInterface;
|
||||
use Sabre\CardDAV\Card;
|
||||
@@ -220,12 +219,4 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
return parent::getChanges($syncToken, $syncLevel, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Arne Hamann <kontakt+github@arne.email>
|
||||
* @author Björn Schießle <bjoern@schiessle.org>
|
||||
* @author Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
* @author labor4 <schreibtisch@labor4.ch>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
@@ -24,7 +19,7 @@
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -102,20 +97,20 @@ class AddressBookImpl implements IAddressBook {
|
||||
/**
|
||||
* @param string $pattern which should match within the $searchProperties
|
||||
* @param array $searchProperties defines the properties within the query pattern should match
|
||||
* @param array $options Options to define the output format and search behavior
|
||||
* - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
|
||||
* @param array $options Options to define the output format
|
||||
* - types boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
|
||||
* example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']]
|
||||
* - 'escape_like_param' - If set to false wildcards _ and % are not escaped
|
||||
* @return array an array of contacts which are arrays of key-value-pairs
|
||||
* example result:
|
||||
* [
|
||||
* ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'],
|
||||
* ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']]
|
||||
* ]
|
||||
* @return array an array of contacts which are arrays of key-value-pairs
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function search($pattern, $searchProperties, $options) {
|
||||
$results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
|
||||
$results = $this->backend->search($this->getKey(), $pattern, $searchProperties);
|
||||
|
||||
$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
|
||||
|
||||
@@ -167,7 +162,7 @@ class AddressBookImpl implements IAddressBook {
|
||||
$permissions = $this->addressBook->getACL();
|
||||
$result = 0;
|
||||
foreach ($permissions as $permission) {
|
||||
switch ($permission['privilege']) {
|
||||
switch($permission['privilege']) {
|
||||
case '{DAV:}read':
|
||||
$result |= Constants::PERMISSION_READ;
|
||||
break;
|
||||
@@ -242,7 +237,6 @@ class AddressBookImpl implements IAddressBook {
|
||||
*
|
||||
* @param string $uri
|
||||
* @param VCard $vCard
|
||||
* @param boolean $withTypes (optional) return the values as arrays of value/type pairs
|
||||
* @return array
|
||||
*/
|
||||
protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
|
||||
@@ -262,7 +256,20 @@ class AddressBookImpl implements IAddressBook {
|
||||
]) . '?photo';
|
||||
|
||||
$result['PHOTO'] = 'VALUE=uri:' . $url;
|
||||
} elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
|
||||
|
||||
} else if ($property->name === 'X-SOCIALPROFILE') {
|
||||
$type = $this->getTypeFromProperty($property);
|
||||
|
||||
// Type is the social network, when it's empty we don't need this.
|
||||
if ($type !== null) {
|
||||
if (!isset($result[$property->name])) {
|
||||
$result[$property->name] = [];
|
||||
}
|
||||
$result[$property->name][$type] = $property->getValue();
|
||||
}
|
||||
|
||||
// The following properties can be set multiple times
|
||||
} else if (in_array($property->name, ['CLOUD', 'EMAIL', 'IMPP', 'TEL', 'URL'])) {
|
||||
if (!isset($result[$property->name])) {
|
||||
$result[$property->name] = [];
|
||||
}
|
||||
@@ -272,10 +279,12 @@ class AddressBookImpl implements IAddressBook {
|
||||
$result[$property->name][] = [
|
||||
'type' => $type,
|
||||
'value' => $property->getValue()
|
||||
];
|
||||
];
|
||||
} else {
|
||||
$result[$property->name][] = $property->getValue();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$result[$property->name] = $property->getValue();
|
||||
}
|
||||
|
||||
@@ -814,7 +814,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
|
||||
$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
|
||||
if ($limit>0) {
|
||||
$query .= " LIMIT " . (int)$limit;
|
||||
$query .= " `LIMIT` " . (int)$limit;
|
||||
}
|
||||
|
||||
// Fetching all changes
|
||||
@@ -903,11 +903,9 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
* @param int $addressBookId
|
||||
* @param string $pattern which should match within the $searchProperties
|
||||
* @param array $searchProperties defines the properties within the query pattern should match
|
||||
* @param array $options = array() to define the search behavior
|
||||
* - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
|
||||
* @return array an array of contacts which are arrays of key-value-pairs
|
||||
*/
|
||||
public function search($addressBookId, $pattern, $searchProperties, $options = array()) {
|
||||
public function search($addressBookId, $pattern, $searchProperties) {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query2 = $this->db->getQueryBuilder();
|
||||
|
||||
@@ -921,11 +919,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
|
||||
// No need for like when the pattern is empty
|
||||
if ('' !== $pattern) {
|
||||
if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
|
||||
$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
|
||||
} else {
|
||||
$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
|
||||
}
|
||||
$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
|
||||
}
|
||||
|
||||
$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
namespace OCA\DAV\Command;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\Connector\Sabre\Principal;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
@@ -79,9 +78,8 @@ class CreateCalendar extends Command {
|
||||
$this->groupManager,
|
||||
\OC::$server->getShareManager(),
|
||||
\OC::$server->getUserSession(),
|
||||
\OC::$server->getAppManager(),
|
||||
\OC::$server->query(ProxyMapper::class),
|
||||
\OC::$server->getConfig()
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getAppManager()
|
||||
);
|
||||
$random = \OC::$server->getSecureRandom();
|
||||
$logger = \OC::$server->getLogger();
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\Command;
|
||||
|
||||
use OCA\DAV\CalDAV\Reminder\ReminderService;
|
||||
use OCP\IConfig;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class SendEventReminders
|
||||
*
|
||||
* @package OCA\DAV\Command
|
||||
*/
|
||||
class SendEventReminders extends Command {
|
||||
|
||||
/** @var ReminderService */
|
||||
protected $reminderService;
|
||||
|
||||
/** @var IConfig */
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @param ReminderService $reminderService
|
||||
* @param IConfig $config
|
||||
*/
|
||||
public function __construct(ReminderService $reminderService,
|
||||
IConfig $config) {
|
||||
parent::__construct();
|
||||
$this->reminderService = $reminderService;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function configure():void {
|
||||
$this
|
||||
->setName('dav:send-event-reminders')
|
||||
->setDescription('Sends event reminders');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output):void {
|
||||
if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') !== 'yes') {
|
||||
$output->writeln('<error>Sending event reminders disabled!</error>');
|
||||
$output->writeln('<info>Please run "php occ config:app:set dav sendEventReminders --value yes"');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->config->getAppValue('dav', 'sendEventRemindersMode', 'backgroundjob') !== 'occ') {
|
||||
$output->writeln('<error>Sending event reminders mode set to background-job!</error>');
|
||||
$output->writeln('<info>Please run "php occ config:app:set dav sendEventRemindersMode --value occ"');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reminderService->processReminders();
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Sabre\DAV\Auth\Backend\AbstractBasic;
|
||||
|
||||
/**
|
||||
@@ -99,9 +98,9 @@ class PublicAuth extends AbstractBasic {
|
||||
// check if the share is password protected
|
||||
if ($share->getPassword() !== null) {
|
||||
|
||||
if ($share->getShareType() === IShare::TYPE_LINK
|
||||
|| $share->getShareType() === IShare::TYPE_EMAIL
|
||||
|| $share->getShareType() === IShare::TYPE_CIRCLE) {
|
||||
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
|
||||
|| $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL
|
||||
|| $share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
|
||||
if ($this->shareManager->checkPassword($share, $password)) {
|
||||
return true;
|
||||
} else if ($this->session->exists('public_link_authenticated')
|
||||
@@ -116,7 +115,7 @@ class PublicAuth extends AbstractBasic {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if ($share->getShareType() === IShare::TYPE_REMOTE) {
|
||||
} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -57,13 +57,7 @@ class AnonymousOptionsPlugin extends ServerPlugin {
|
||||
* @return bool
|
||||
*/
|
||||
public function handleAnonymousOptions(RequestInterface $request, ResponseInterface $response) {
|
||||
$isOffice = preg_match('/Microsoft Office/i', $request->getHeader('User-Agent'));
|
||||
$emptyAuth = $request->getHeader('Authorization') === null
|
||||
|| $request->getHeader('Authorization') === ''
|
||||
|| trim($request->getHeader('Authorization')) === 'Bearer';
|
||||
$isAnonymousOfficeOption = $request->getMethod() === 'OPTIONS' && $isOffice && $emptyAuth;
|
||||
$isOfficeHead = $request->getMethod() === 'HEAD' && $isOffice && $emptyAuth;
|
||||
if ($isAnonymousOfficeOption || $isOfficeHead) {
|
||||
if ($request->getHeader('Authorization') === null && $request->getMethod() === 'OPTIONS' && $this->isRequestInRoot($request->getPath())) {
|
||||
/** @var CorePlugin $corePlugin */
|
||||
$corePlugin = $this->server->getPlugin('core');
|
||||
// setup a fake tree for anonymous access
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Aaron Wood <aaronjwood@gmail.com>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\DAV\Tree;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Exception\ServiceUnavailable;
|
||||
|
||||
class CustomPropertiesBackend implements BackendInterface {
|
||||
|
||||
/**
|
||||
* Ignored properties
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoredProperties = array(
|
||||
'{DAV:}getcontentlength',
|
||||
'{DAV:}getcontenttype',
|
||||
'{DAV:}getetag',
|
||||
'{DAV:}quota-used-bytes',
|
||||
'{DAV:}quota-available-bytes',
|
||||
'{http://owncloud.org/ns}permissions',
|
||||
'{http://owncloud.org/ns}downloadURL',
|
||||
'{http://owncloud.org/ns}dDC',
|
||||
'{http://owncloud.org/ns}size',
|
||||
'{http://nextcloud.org/ns}is-encrypted',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var Tree
|
||||
*/
|
||||
private $tree;
|
||||
|
||||
/**
|
||||
* @var IDBConnection
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var IUser
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Properties cache
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cache = [];
|
||||
|
||||
/**
|
||||
* @param Tree $tree node tree
|
||||
* @param IDBConnection $connection database connection
|
||||
* @param IUser $user owner of the tree and properties
|
||||
*/
|
||||
public function __construct(
|
||||
Tree $tree,
|
||||
IDBConnection $connection,
|
||||
IUser $user) {
|
||||
$this->tree = $tree;
|
||||
$this->connection = $connection;
|
||||
$this->user = $user->getUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches properties for a path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param PropFind $propFind
|
||||
* @return void
|
||||
*/
|
||||
public function propFind($path, PropFind $propFind) {
|
||||
try {
|
||||
$node = $this->tree->getNodeForPath($path);
|
||||
if (!($node instanceof Node)) {
|
||||
return;
|
||||
}
|
||||
} catch (ServiceUnavailable $e) {
|
||||
// might happen for unavailable mount points, skip
|
||||
return;
|
||||
} catch (NotFound $e) {
|
||||
// in some rare (buggy) cases the node might not be found,
|
||||
// we catch the exception to prevent breaking the whole list with a 404
|
||||
// (soft fail)
|
||||
\OC::$server->getLogger()->warning(
|
||||
'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
|
||||
array('app' => 'files')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$requestedProps = $propFind->get404Properties();
|
||||
|
||||
// these might appear
|
||||
$requestedProps = array_diff(
|
||||
$requestedProps,
|
||||
$this->ignoredProperties
|
||||
);
|
||||
|
||||
if (empty($requestedProps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$props = $this->getProperties($node, $requestedProps);
|
||||
foreach ($props as $propName => $propValue) {
|
||||
$propFind->set($propName, $propValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties for a path
|
||||
*
|
||||
* @param string $path
|
||||
* @param PropPatch $propPatch
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function propPatch($path, PropPatch $propPatch) {
|
||||
$node = $this->tree->getNodeForPath($path);
|
||||
if (!($node instanceof Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propPatch->handleRemaining(function($changedProps) use ($node) {
|
||||
return $this->updateProperties($node, $changedProps);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after a node is deleted.
|
||||
*
|
||||
* @param string $path path of node for which to delete properties
|
||||
*/
|
||||
public function delete($path) {
|
||||
$statement = $this->connection->prepare(
|
||||
'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
|
||||
);
|
||||
$statement->execute(array($this->user, '/' . $path));
|
||||
$statement->closeCursor();
|
||||
|
||||
unset($this->cache[$path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after a successful MOVE
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function move($source, $destination) {
|
||||
$statement = $this->connection->prepare(
|
||||
'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
|
||||
' WHERE `userid` = ? AND `propertypath` = ?'
|
||||
);
|
||||
$statement->execute(array('/' . $destination, $this->user, '/' . $source));
|
||||
$statement->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of properties for this nodes.;
|
||||
* @param Node $node
|
||||
* @param array $requestedProperties requested properties or empty array for "all"
|
||||
* @return array
|
||||
* @note The properties list is a list of propertynames the client
|
||||
* requested, encoded as xmlnamespace#tagName, for example:
|
||||
* http://www.example.org/namespace#author If the array is empty, all
|
||||
* properties should be returned
|
||||
*/
|
||||
private function getProperties(Node $node, array $requestedProperties) {
|
||||
$path = $node->getPath();
|
||||
if (isset($this->cache[$path])) {
|
||||
return $this->cache[$path];
|
||||
}
|
||||
|
||||
// TODO: chunking if more than 1000 properties
|
||||
$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
|
||||
|
||||
$whereValues = array($this->user, $path);
|
||||
$whereTypes = array(null, null);
|
||||
|
||||
if (!empty($requestedProperties)) {
|
||||
// request only a subset
|
||||
$sql .= ' AND `propertyname` in (?)';
|
||||
$whereValues[] = $requestedProperties;
|
||||
$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
|
||||
}
|
||||
|
||||
$result = $this->connection->executeQuery(
|
||||
$sql,
|
||||
$whereValues,
|
||||
$whereTypes
|
||||
);
|
||||
|
||||
$props = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$props[$row['propertyname']] = $row['propertyvalue'];
|
||||
}
|
||||
|
||||
$result->closeCursor();
|
||||
|
||||
$this->cache[$path] = $props;
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update properties
|
||||
*
|
||||
* @param Node $node node for which to update properties
|
||||
* @param array $properties array of properties to update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateProperties($node, $properties) {
|
||||
$path = $node->getPath();
|
||||
|
||||
$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
|
||||
' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
|
||||
|
||||
$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
|
||||
' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
|
||||
|
||||
$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
|
||||
' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
|
||||
|
||||
// TODO: use "insert or update" strategy ?
|
||||
$existing = $this->getProperties($node, array());
|
||||
$this->connection->beginTransaction();
|
||||
foreach ($properties as $propertyName => $propertyValue) {
|
||||
// If it was null, we need to delete the property
|
||||
if (is_null($propertyValue)) {
|
||||
if (array_key_exists($propertyName, $existing)) {
|
||||
$this->connection->executeUpdate($deleteStatement,
|
||||
array(
|
||||
$this->user,
|
||||
$path,
|
||||
$propertyName
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!array_key_exists($propertyName, $existing)) {
|
||||
$this->connection->executeUpdate($insertStatement,
|
||||
array(
|
||||
$this->user,
|
||||
$path,
|
||||
$propertyName,
|
||||
$propertyValue
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$this->connection->executeUpdate($updateStatement,
|
||||
array(
|
||||
$propertyValue,
|
||||
$this->user,
|
||||
$path,
|
||||
$propertyName
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->connection->commit();
|
||||
unset($this->cache[$path]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk load properties for directory children
|
||||
*
|
||||
* @param Directory $node
|
||||
* @param array $requestedProperties requested properties
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadChildrenProperties(Directory $node, $requestedProperties) {
|
||||
$path = $node->getPath();
|
||||
if (isset($this->cache[$path])) {
|
||||
// we already loaded them at some point
|
||||
return;
|
||||
}
|
||||
|
||||
$childNodes = $node->getChildren();
|
||||
// pre-fill cache
|
||||
foreach ($childNodes as $childNode) {
|
||||
$this->cache[$childNode->getPath()] = [];
|
||||
}
|
||||
|
||||
$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
|
||||
$sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
|
||||
|
||||
$result = $this->connection->executeQuery(
|
||||
$sql,
|
||||
array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
|
||||
array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
|
||||
);
|
||||
|
||||
$oldPath = null;
|
||||
$props = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$path = $row['propertypath'];
|
||||
if ($oldPath !== $path) {
|
||||
// save previously gathered props
|
||||
$this->cache[$oldPath] = $props;
|
||||
$oldPath = $path;
|
||||
// prepare props for next path
|
||||
$props = [];
|
||||
}
|
||||
$props[$row['propertyname']] = $row['propertyvalue'];
|
||||
}
|
||||
if (!is_null($oldPath)) {
|
||||
// save props from last run
|
||||
$this->cache[$oldPath] = $props;
|
||||
}
|
||||
|
||||
$result->closeCursor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -225,7 +225,7 @@ class File extends Node implements IFile {
|
||||
if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
$expected = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
if ($count !== $expected) {
|
||||
throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
|
||||
throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ class File extends Node implements IFile {
|
||||
}
|
||||
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
|
||||
throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return '"' . $this->info->getEtag() . '"';
|
||||
@@ -502,7 +502,8 @@ class File extends Node implements IFile {
|
||||
$expected = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
if ($bytesWritten !== $expected) {
|
||||
$chunk_handler->remove($info['index']);
|
||||
throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
|
||||
throw new BadRequest(
|
||||
'expected filesize ' . $expected . ' got ' . $bytesWritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,6 @@
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OCA\Circles\Exceptions\CircleDoesNotExistException;
|
||||
use OCA\DAV\CalDAV\Proxy\Proxy;
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\Traits\PrincipalProxyTrait;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\IConfig;
|
||||
@@ -65,6 +62,9 @@ class Principal implements BackendInterface {
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var IAppManager */
|
||||
private $appManager;
|
||||
|
||||
@@ -77,21 +77,12 @@ class Principal implements BackendInterface {
|
||||
/** @var bool */
|
||||
private $hasCircles;
|
||||
|
||||
/** @var ProxyMapper */
|
||||
private $proxyMapper;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Principal constructor.
|
||||
*
|
||||
* @param IUserManager $userManager
|
||||
* @param IGroupManager $groupManager
|
||||
* @param IShareManager $shareManager
|
||||
* @param IUserSession $userSession
|
||||
* @param IAppManager $appManager
|
||||
* @param ProxyMapper $proxyMapper
|
||||
* @param IConfig $config
|
||||
* @param string $principalPrefix
|
||||
*/
|
||||
@@ -99,23 +90,17 @@ class Principal implements BackendInterface {
|
||||
IGroupManager $groupManager,
|
||||
IShareManager $shareManager,
|
||||
IUserSession $userSession,
|
||||
IAppManager $appManager,
|
||||
ProxyMapper $proxyMapper,
|
||||
IConfig $config,
|
||||
string $principalPrefix = 'principals/users/') {
|
||||
IAppManager $appManager,
|
||||
$principalPrefix = 'principals/users/') {
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->userSession = $userSession;
|
||||
$this->config = $config;
|
||||
$this->appManager = $appManager;
|
||||
$this->principalPrefix = trim($principalPrefix, '/');
|
||||
$this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
|
||||
$this->proxyMapper = $proxyMapper;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
use PrincipalProxyTrait {
|
||||
getGroupMembership as protected traitGetGroupMembership;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,21 +139,6 @@ class Principal implements BackendInterface {
|
||||
public function getPrincipalByPath($path) {
|
||||
list($prefix, $name) = \Sabre\Uri\split($path);
|
||||
|
||||
if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') {
|
||||
list($prefix2, $name2) = \Sabre\Uri\split($prefix);
|
||||
|
||||
if ($prefix2 === $this->principalPrefix) {
|
||||
$user = $this->userManager->get($name2);
|
||||
|
||||
if ($user !== null) {
|
||||
return [
|
||||
'uri' => 'principals/users/' . $user->getUID() . '/' . $name,
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($prefix === $this->principalPrefix) {
|
||||
$user = $this->userManager->get($name);
|
||||
|
||||
@@ -185,6 +155,23 @@ class Principal implements BackendInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of members for a group-principal
|
||||
*
|
||||
* @param string $principal
|
||||
* @return string[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getGroupMemberSet($principal) {
|
||||
// TODO: for now the group principal has only one member, the user itself
|
||||
$principal = $this->getPrincipalByPath($principal);
|
||||
if (!$principal) {
|
||||
throw new Exception('Principal not found');
|
||||
}
|
||||
|
||||
return [$principal['uri']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups a principal is a member of
|
||||
*
|
||||
@@ -196,30 +183,36 @@ class Principal implements BackendInterface {
|
||||
public function getGroupMembership($principal, $needGroups = false) {
|
||||
list($prefix, $name) = \Sabre\Uri\split($principal);
|
||||
|
||||
if ($prefix !== $this->principalPrefix) {
|
||||
return [];
|
||||
}
|
||||
if ($prefix === $this->principalPrefix) {
|
||||
$user = $this->userManager->get($name);
|
||||
if (!$user) {
|
||||
throw new Exception('Principal not found');
|
||||
}
|
||||
|
||||
$user = $this->userManager->get($name);
|
||||
if (!$user) {
|
||||
throw new Exception('Principal not found');
|
||||
}
|
||||
if ($this->hasGroups || $needGroups) {
|
||||
$groups = $this->groupManager->getUserGroups($user);
|
||||
$groups = array_map(function($group) {
|
||||
/** @var IGroup $group */
|
||||
return 'principals/groups/' . urlencode($group->getGID());
|
||||
}, $groups);
|
||||
|
||||
$groups = [];
|
||||
|
||||
if ($this->hasGroups || $needGroups) {
|
||||
$userGroups = $this->groupManager->getUserGroups($user);
|
||||
foreach($userGroups as $userGroup) {
|
||||
$groups[] = 'principals/groups/' . urlencode($userGroup->getGID());
|
||||
return $groups;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
$groups = array_unique(array_merge(
|
||||
$groups,
|
||||
$this->traitGetGroupMembership($principal, $needGroups)
|
||||
));
|
||||
|
||||
return $groups;
|
||||
/**
|
||||
* Updates the list of group members for a group principal.
|
||||
*
|
||||
* The principals should be passed as a list of uri's.
|
||||
*
|
||||
* @param string $principal
|
||||
* @param string[] $members
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setGroupMemberSet($principal, array $members) {
|
||||
throw new Exception('Setting members of the group is not supported yet');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,4 +497,5 @@ class Principal implements BackendInterface {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ class ServerFactory {
|
||||
// custom properties plugin must be the last one
|
||||
$server->addPlugin(
|
||||
new \Sabre\DAV\PropertyStorage\Plugin(
|
||||
new \OCA\DAV\DAV\CustomPropertiesBackend(
|
||||
new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend(
|
||||
$objectTree,
|
||||
$this->databaseConnection,
|
||||
$this->userSession->getUser()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @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\DAV\Connector\Sabre;
|
||||
|
||||
use OCP\Share\IShare;
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* This property contains multiple "sharee" elements, each containing a share sharee
|
||||
*/
|
||||
class ShareeList implements XmlSerializable {
|
||||
const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
|
||||
|
||||
/** @var IShare[] */
|
||||
private $shares;
|
||||
|
||||
public function __construct(array $shares) {
|
||||
$this->shares = $shares;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize metod is called during xml writing.
|
||||
*
|
||||
* @param Writer $writer
|
||||
* @return void
|
||||
*/
|
||||
function xmlSerialize(Writer $writer) {
|
||||
foreach ($this->shares as $share) {
|
||||
$writer->startElement('{' . self::NS_NEXTCLOUD . '}sharee');
|
||||
$writer->writeElement('{' . self::NS_NEXTCLOUD . '}id', $share->getSharedWith());
|
||||
$writer->writeElement('{' . self::NS_NEXTCLOUD . '}display-name', $share->getSharedWithDisplayName());
|
||||
$writer->writeElement('{' . self::NS_NEXTCLOUD . '}type', $share->getShareType());
|
||||
$writer->endElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,7 @@ use OCP\Share\IShare;
|
||||
class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
const NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
|
||||
const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
|
||||
const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
|
||||
|
||||
/**
|
||||
* Reference to main server object
|
||||
@@ -68,8 +66,10 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
*/
|
||||
private $userFolder;
|
||||
|
||||
/** @var IShare[] */
|
||||
private $cachedShares = [];
|
||||
/**
|
||||
* @var IShare[]
|
||||
*/
|
||||
private $cachedShareTypes;
|
||||
|
||||
private $cachedFolders = [];
|
||||
|
||||
@@ -89,6 +89,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
$this->shareManager = $shareManager;
|
||||
$this->userFolder = $userFolder;
|
||||
$this->userId = $userSession->getUser()->getUID();
|
||||
$this->cachedShareTypes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,14 +106,20 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
$server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
|
||||
$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
|
||||
$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
|
||||
|
||||
$this->server = $server;
|
||||
$this->server->on('propFind', array($this, 'handleGetProperties'));
|
||||
}
|
||||
|
||||
private function getShare(\OCP\Files\Node $node): array {
|
||||
$result = [];
|
||||
/**
|
||||
* Return a list of share types for outgoing shares
|
||||
*
|
||||
* @param \OCP\Files\Node $node file node
|
||||
*
|
||||
* @return int[] array of share types
|
||||
*/
|
||||
private function getShareTypes(\OCP\Files\Node $node) {
|
||||
$shareTypes = [];
|
||||
$requestedShareTypes = [
|
||||
\OCP\Share::SHARE_TYPE_USER,
|
||||
\OCP\Share::SHARE_TYPE_GROUP,
|
||||
@@ -123,47 +130,40 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
\OCP\Share::SHARE_TYPE_CIRCLE,
|
||||
];
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
// one of each type is enough to find out about the types
|
||||
$shares = $this->shareManager->getSharesBy(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
false,
|
||||
-1
|
||||
1
|
||||
);
|
||||
foreach ($shares as $share) {
|
||||
$result[] = $share;
|
||||
if (!empty($shares)) {
|
||||
$shareTypes[] = $requestedShareType;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
return $shareTypes;
|
||||
}
|
||||
|
||||
private function getSharesFolder(\OCP\Files\Folder $node): array {
|
||||
return $this->shareManager->getSharesInFolder(
|
||||
private function getSharesTypesInFolder(\OCP\Files\Folder $node) {
|
||||
$shares = $this->shareManager->getSharesInFolder(
|
||||
$this->userId,
|
||||
$node,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function getShares(\Sabre\DAV\INode $sabreNode): array {
|
||||
if (isset($this->cachedShares[$sabreNode->getId()])) {
|
||||
$shares = $this->cachedShares[$sabreNode->getId()];
|
||||
} else {
|
||||
list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
|
||||
if ($parentPath === '') {
|
||||
$parentPath = '/';
|
||||
}
|
||||
// if we already cached the folder this file is in we know there are no shares for this file
|
||||
if (array_search($parentPath, $this->cachedFolders) === false) {
|
||||
$node = $this->userFolder->get($sabreNode->getPath());
|
||||
$shares = $this->getShare($node);
|
||||
$this->cachedShares[$sabreNode->getId()] = $shares;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
$shareTypesByFileId = [];
|
||||
|
||||
foreach($shares as $fileId => $sharesForFile) {
|
||||
$types = array_map(function(IShare $share) {
|
||||
return $share->getShareType();
|
||||
}, $sharesForFile);
|
||||
$types = array_unique($types);
|
||||
sort($types);
|
||||
$shareTypesByFileId[$fileId] = $types;
|
||||
}
|
||||
|
||||
return $shares;
|
||||
return $shareTypesByFileId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,34 +183,36 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
// need prefetch ?
|
||||
if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& (
|
||||
!is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ||
|
||||
!is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
|
||||
)
|
||||
&& !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME))
|
||||
) {
|
||||
$folderNode = $this->userFolder->get($sabreNode->getPath());
|
||||
|
||||
$childShares = $this->getSharesTypesInFolder($folderNode);
|
||||
$this->cachedFolders[] = $sabreNode->getPath();
|
||||
$childShares = $this->getSharesFolder($folderNode);
|
||||
$this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode);
|
||||
foreach ($childShares as $id => $shares) {
|
||||
$this->cachedShares[$id] = $shares;
|
||||
$this->cachedShareTypes[$id] = $shares;
|
||||
}
|
||||
}
|
||||
|
||||
$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
|
||||
$shares = $this->getShares($sabreNode);
|
||||
|
||||
$shareTypes = array_unique(array_map(function(IShare $share) {
|
||||
return $share->getShareType();
|
||||
}, $shares));
|
||||
if (isset($this->cachedShareTypes[$sabreNode->getId()])) {
|
||||
$shareTypes = $this->cachedShareTypes[$sabreNode->getId()];
|
||||
} else {
|
||||
list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
|
||||
if ($parentPath === '') {
|
||||
$parentPath = '/';
|
||||
}
|
||||
// if we already cached the folder this file is in we know there are no shares for this file
|
||||
if (array_search($parentPath, $this->cachedFolders) === false) {
|
||||
$node = $this->userFolder->get($sabreNode->getPath());
|
||||
$shareTypes = $this->getShareTypes($node);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return new ShareTypeList($shareTypes);
|
||||
});
|
||||
|
||||
$propFind->handle(self::SHAREES_PROPERTYNAME, function() use ($sabreNode) {
|
||||
$shares = $this->getShares($sabreNode);
|
||||
|
||||
return new ShareeList($shares);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user