mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-11 12:17:01 +00:00
Compare commits
736 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
088edbf708 | ||
|
|
e2f6c8abe7 | ||
|
|
f2735e5fbc | ||
|
|
f8106a81c7 | ||
|
|
1d7bce5343 | ||
|
|
224e0e8625 | ||
|
|
d5b3a7d2ea | ||
|
|
bd0be4542e | ||
|
|
e78052ce33 | ||
|
|
24f79b9206 | ||
|
|
df9633503b | ||
|
|
5e1c12f4b1 | ||
|
|
5699c3d15e | ||
|
|
cd88e8ac7a | ||
|
|
3c83e1dcd3 | ||
|
|
d232838676 | ||
|
|
2ae0f48619 | ||
|
|
170782d17f | ||
|
|
2cbdbbf6fb | ||
|
|
0a750f373f | ||
|
|
92ff5e1859 | ||
|
|
cb3257a890 | ||
|
|
9377cd150d | ||
|
|
e1f2fd8094 | ||
|
|
70dcf6ed69 | ||
|
|
6c62dbc604 | ||
|
|
b6807091a5 | ||
|
|
33f3784b3a | ||
|
|
0e9b552796 | ||
|
|
111e1a278c | ||
|
|
34ecdd7533 | ||
|
|
7ec9c1c5ae | ||
|
|
1055fd8a79 | ||
|
|
03102da373 | ||
|
|
23ea8b418a | ||
|
|
9269fa1933 | ||
|
|
5a1a9f17a0 | ||
|
|
c4ca71a7f6 | ||
|
|
991d111ac3 | ||
|
|
7f18fb0cba | ||
|
|
c4d54ffa71 | ||
|
|
1ee2d5c58b | ||
|
|
94d9d0fc38 | ||
|
|
9252905103 | ||
|
|
e9b49a5317 | ||
|
|
9e50337c99 | ||
|
|
07b68d9b7d | ||
|
|
c7d1fc7c35 | ||
|
|
18b5b99854 | ||
|
|
e60d4ba19b | ||
|
|
dcf2b49b19 | ||
|
|
4aecb4a255 | ||
|
|
7cf7f2ffd8 | ||
|
|
a78161799c | ||
|
|
6a37eee973 | ||
|
|
bb5be010f2 | ||
|
|
dcd4e56aa7 | ||
|
|
12f928c192 | ||
|
|
1b47b94154 | ||
|
|
f64f4ea1b1 | ||
|
|
faa33769f1 | ||
|
|
1b0487eeea | ||
|
|
7ffc06065f | ||
|
|
515720afda | ||
|
|
3f1e6a6628 | ||
|
|
a94deb9f33 | ||
|
|
b2d91b3fe9 | ||
|
|
1f7e23af28 | ||
|
|
0275b7fe4d | ||
|
|
d4b25090aa | ||
|
|
7b14e7fb79 | ||
|
|
1d156e6af6 | ||
|
|
b91e40b0e7 | ||
|
|
fab908c313 | ||
|
|
24e5cd67dd | ||
|
|
7297e0eb83 | ||
|
|
81cce0ddb7 | ||
|
|
8232ef3dd2 | ||
|
|
b930180907 | ||
|
|
ef6866773c | ||
|
|
415dc7607e | ||
|
|
726f2abd96 | ||
|
|
894acad7ec | ||
|
|
d9d7de8804 | ||
|
|
dcd0469977 | ||
|
|
434112eff3 | ||
|
|
ace9186691 | ||
|
|
29cc42e2e8 | ||
|
|
3ae6120067 | ||
|
|
377f51a962 | ||
|
|
dc61e630b4 | ||
|
|
d2ec8eda86 | ||
|
|
376de3b6d9 | ||
|
|
82ad0cb5cd | ||
|
|
df9431670a | ||
|
|
e96f4fcedd | ||
|
|
1e96b90e28 | ||
|
|
46897ec3dd | ||
|
|
ffa8248ac1 | ||
|
|
7d90a0c59b | ||
|
|
e33e77cb9e | ||
|
|
f7a7c3bc72 | ||
|
|
c8371bef3f | ||
|
|
7c3f285342 | ||
|
|
ed3a7d64c6 | ||
|
|
00b4569f8e | ||
|
|
41e0b90850 | ||
|
|
4d35bce189 | ||
|
|
e6631ba1d1 | ||
|
|
6bae952947 | ||
|
|
2e550af196 | ||
|
|
95ecc416f1 | ||
|
|
ee3b0cb53d | ||
|
|
bfa0ea53a4 | ||
|
|
2f338d6eda | ||
|
|
6e59165786 | ||
|
|
a5a6c8a0a1 | ||
|
|
cd0c397b07 | ||
|
|
7ba7f8aa14 | ||
|
|
91d0660cba | ||
|
|
e8bbcb2ae4 | ||
|
|
834d1808a1 | ||
|
|
2d264eccb7 | ||
|
|
3ec0fe1e03 | ||
|
|
2f85c5cb3c | ||
|
|
69cd4386f1 | ||
|
|
effb81837f | ||
|
|
d0139ada06 | ||
|
|
2920793e40 | ||
|
|
570f886a1f | ||
|
|
21dab40b43 | ||
|
|
801ccf94c7 | ||
|
|
7114b99cbc | ||
|
|
94c99f298e | ||
|
|
ac37a4c232 | ||
|
|
5ad4f30505 | ||
|
|
b4773ade9b | ||
|
|
63f422f93a | ||
|
|
1134f89a5a | ||
|
|
fe0b1b62d1 | ||
|
|
d549ccb300 | ||
|
|
06d596b5cf | ||
|
|
1091b21306 | ||
|
|
780b66d5b4 | ||
|
|
10982e1b19 | ||
|
|
068375022e | ||
|
|
d6bd701492 | ||
|
|
223b4ab8aa | ||
|
|
6aff357473 | ||
|
|
3ef24bb5ad | ||
|
|
9e60ea2209 | ||
|
|
86904380bb | ||
|
|
9cdef865fe | ||
|
|
1d611720c6 | ||
|
|
7285e058cd | ||
|
|
e44617e276 | ||
|
|
81972e26ff | ||
|
|
5a7a4608c9 | ||
|
|
9161e64932 | ||
|
|
cfd514c5a5 | ||
|
|
b42f4ee256 | ||
|
|
a5a4fbca2f | ||
|
|
a706811338 | ||
|
|
ec120dfd75 | ||
|
|
fe7b19b96d | ||
|
|
ff5e1dac43 | ||
|
|
e73f7e9807 | ||
|
|
15e99e3063 | ||
|
|
4edc909f2a | ||
|
|
d1fe29adbe | ||
|
|
980226c4a2 | ||
|
|
c4c4306ad3 | ||
|
|
10732c0a90 | ||
|
|
ca2c4b3ee4 | ||
|
|
03671efe11 | ||
|
|
748529afac | ||
|
|
1a003e5905 | ||
|
|
d6c66b8b43 | ||
|
|
813bc05e74 | ||
|
|
ef447b400c | ||
|
|
aca823df64 | ||
|
|
0832faa2f2 | ||
|
|
7573bd1f92 | ||
|
|
35eb013c97 | ||
|
|
110f769384 | ||
|
|
6ce7c6ccda | ||
|
|
350141727b | ||
|
|
c63fed50d5 | ||
|
|
24a22328c9 | ||
|
|
0504410ee9 | ||
|
|
3a1bc73692 | ||
|
|
f02ee87344 | ||
|
|
7cdcb375b3 | ||
|
|
52c07987c6 | ||
|
|
11dee6e984 | ||
|
|
036cf4330f | ||
|
|
4fec73307b | ||
|
|
a58047433b | ||
|
|
e004884352 | ||
|
|
8aa7236dd6 | ||
|
|
1f297aa42e | ||
|
|
b5a0848513 | ||
|
|
c2ea9781ee | ||
|
|
93255071ac | ||
|
|
313293b57b | ||
|
|
687ceca26e | ||
|
|
55b1c1511a | ||
|
|
04ccf36966 | ||
|
|
3eba144998 | ||
|
|
e2bd509aeb | ||
|
|
9be5342220 | ||
|
|
e96c1deffe | ||
|
|
aa53bc89b4 | ||
|
|
e57bd13f73 | ||
|
|
00f30e01d5 | ||
|
|
fcb73fb3a8 | ||
|
|
fba82f8697 | ||
|
|
13c8fc15f4 | ||
|
|
3a3cac54a2 | ||
|
|
303860d509 | ||
|
|
ba2a740d38 | ||
|
|
87f4508d1c | ||
|
|
fea79ba8c9 | ||
|
|
734ca9b488 | ||
|
|
c695e64652 | ||
|
|
77218b788a | ||
|
|
e5063c2016 | ||
|
|
a6cfb38433 | ||
|
|
ae657ada61 | ||
|
|
ee8963ace8 | ||
|
|
aca76ae3a6 | ||
|
|
5e3cdc594d | ||
|
|
959fdbab94 | ||
|
|
f19952af12 | ||
|
|
9b3c59bfa4 | ||
|
|
55dd4ecf74 | ||
|
|
bbff632fbc | ||
|
|
4127be7f2f | ||
|
|
3a77ea83a3 | ||
|
|
2bd70eee4b | ||
|
|
0dd34df7d7 | ||
|
|
41c0567a4f | ||
|
|
dcaa1e209e | ||
|
|
f1e67d943d | ||
|
|
70afd19f7f | ||
|
|
7f6ce95fd6 | ||
|
|
035e1ef9eb | ||
|
|
5988642a89 | ||
|
|
4964fbb1b2 | ||
|
|
2ca3aade8c | ||
|
|
878f33929b | ||
|
|
ae340b3279 | ||
|
|
0da0b8974c | ||
|
|
d7804bfa79 | ||
|
|
e451126af7 | ||
|
|
38049d0ef4 | ||
|
|
31e25047cc | ||
|
|
8d105465a3 | ||
|
|
d8c021def2 | ||
|
|
ba08b8a8af | ||
|
|
6974c00d02 | ||
|
|
5446914131 | ||
|
|
68add0bbd7 | ||
|
|
28d0c4dd6e | ||
|
|
a201f61eeb | ||
|
|
0ec428b1ed | ||
|
|
a088ca0875 | ||
|
|
d412766794 | ||
|
|
3cd2a6b74e | ||
|
|
136ae8ae24 | ||
|
|
d80b8ce76b | ||
|
|
392616bdd3 | ||
|
|
4d6387e69d | ||
|
|
eebe244247 | ||
|
|
dbfb1c696f | ||
|
|
2ed620335f | ||
|
|
09efbd175d | ||
|
|
e4c67a4a6f | ||
|
|
e8dd049baf | ||
|
|
4c0f3413f3 | ||
|
|
a052c65b89 | ||
|
|
1bcc81018b | ||
|
|
81ba1b8790 | ||
|
|
ed946a532e | ||
|
|
db34e75d1f | ||
|
|
bcb588b968 | ||
|
|
474e787234 | ||
|
|
691c62f65d | ||
|
|
a3a4d7bf36 | ||
|
|
0bcb9acb02 | ||
|
|
e148b16882 | ||
|
|
3a764fae00 | ||
|
|
f46c2d6079 | ||
|
|
1847132ded | ||
|
|
5765247f9e | ||
|
|
0ef6c7ed55 | ||
|
|
e2e6398026 | ||
|
|
9cd19228a3 | ||
|
|
4268794990 | ||
|
|
de4dcaaecb | ||
|
|
b7b1e3056a | ||
|
|
458a5250f4 | ||
|
|
5cf3856b6a | ||
|
|
8ddd139111 | ||
|
|
952b94d6fe | ||
|
|
42e68ecd63 | ||
|
|
41fac7ef9d | ||
|
|
253736870e | ||
|
|
569ff9a81b | ||
|
|
3b17174431 | ||
|
|
52436afda9 | ||
|
|
531b4cccca | ||
|
|
e06544cc0f | ||
|
|
c48f4c065c | ||
|
|
c4d7a794ea | ||
|
|
e7c777281d | ||
|
|
f687b1543d | ||
|
|
869bee454d | ||
|
|
83bcb9176b | ||
|
|
f72c902b6a | ||
|
|
e3f68e1e98 | ||
|
|
b002846f7c | ||
|
|
f55cccd7c6 | ||
|
|
929badd208 | ||
|
|
d347fe66ca | ||
|
|
42921ac205 | ||
|
|
58304ceded | ||
|
|
0a7d2055be | ||
|
|
1edc3ead8d | ||
|
|
231bd7699a | ||
|
|
4dee2ebb15 | ||
|
|
16f956bdf8 | ||
|
|
a20ab17236 | ||
|
|
68c5fe94bf | ||
|
|
78f536e7f1 | ||
|
|
ee408300fd | ||
|
|
0f146959e5 | ||
|
|
449d6a6fcd | ||
|
|
63afa92fc1 | ||
|
|
2b786af35b | ||
|
|
231f498eca | ||
|
|
046aafd97e | ||
|
|
d376847d12 | ||
|
|
befd48d053 | ||
|
|
ee294f889c | ||
|
|
5fddac8c6b | ||
|
|
a3d73848a9 | ||
|
|
b829e25f5a | ||
|
|
58a6cc1658 | ||
|
|
f7eef2d3c7 | ||
|
|
b5d007c61c | ||
|
|
8570b6f768 | ||
|
|
b02e2f72b6 | ||
|
|
e546509f53 | ||
|
|
244864c984 | ||
|
|
33af4be469 | ||
|
|
29e3470587 | ||
|
|
2b8e764deb | ||
|
|
f503144c62 | ||
|
|
79f7194429 | ||
|
|
6ac13521e8 | ||
|
|
ca0625f15e | ||
|
|
88c9fbd153 | ||
|
|
7f51b3a31e | ||
|
|
ac0b08fdb7 | ||
|
|
de0e5ea72d | ||
|
|
e06d60a3c3 | ||
|
|
128bfb09ea | ||
|
|
7f0283caf8 | ||
|
|
a00cf5e696 | ||
|
|
65a9a0db41 | ||
|
|
8ac98067de | ||
|
|
f725dc395e | ||
|
|
6e8368c7c5 | ||
|
|
ddd9425463 | ||
|
|
4739cd1510 | ||
|
|
5a3c977759 | ||
|
|
85889ab99d | ||
|
|
a9dc3a62fb | ||
|
|
c2ddf89747 | ||
|
|
9f743b718a | ||
|
|
0db4a9c672 | ||
|
|
2dbdb0da60 | ||
|
|
ba59cb1010 | ||
|
|
ee36f73ec2 | ||
|
|
abcbabfbd8 | ||
|
|
5be76d2657 | ||
|
|
ddb92f66e7 | ||
|
|
f52759cd00 | ||
|
|
b0d9d7779f | ||
|
|
8cd6912fee | ||
|
|
bc58ca647d | ||
|
|
1456569cf6 | ||
|
|
e1e7d3d093 | ||
|
|
495cd0f259 | ||
|
|
f4371efa1c | ||
|
|
d83b05e030 | ||
|
|
bf8267c2a8 | ||
|
|
3bad29ed6d | ||
|
|
cbae4d89e4 | ||
|
|
18f89aebbb | ||
|
|
c2b4caf723 | ||
|
|
c5f3a9e366 | ||
|
|
da826eace1 | ||
|
|
54de942e7f | ||
|
|
0093bf9b79 | ||
|
|
8e9dcb5d62 | ||
|
|
38e909dfca | ||
|
|
6b0276dc22 | ||
|
|
0d19dfa7b8 | ||
|
|
13052b2969 | ||
|
|
1f9de30ff0 | ||
|
|
5b708c1d8d | ||
|
|
ed92d68317 | ||
|
|
2a3bbf13a5 | ||
|
|
330fccad25 | ||
|
|
14243b5ef7 | ||
|
|
9c702814b2 | ||
|
|
ba7acc5443 | ||
|
|
10557ca1ec | ||
|
|
2085701516 | ||
|
|
88ba56618e | ||
|
|
c35545c209 | ||
|
|
585a859340 | ||
|
|
cf214de06c | ||
|
|
2c83edf691 | ||
|
|
2f14c49215 | ||
|
|
72e68e1a6f | ||
|
|
5ff535cab1 | ||
|
|
90e6acf8b9 | ||
|
|
e5e29101fd | ||
|
|
47735aaa0b | ||
|
|
43eb2f8d36 | ||
|
|
1d3c0074d4 | ||
|
|
e8f07e0765 | ||
|
|
54216709b2 | ||
|
|
9f6f034f97 | ||
|
|
e0c74aa2ad | ||
|
|
c083e1b97d | ||
|
|
c4078ed8f1 | ||
|
|
4001e40613 | ||
|
|
a19deac279 | ||
|
|
f655e543b1 | ||
|
|
5031e7f430 | ||
|
|
7f75ee6ed8 | ||
|
|
bde2e0284a | ||
|
|
026826fc84 | ||
|
|
c5f5d88f75 | ||
|
|
3ae4478a70 | ||
|
|
246ece8e3d | ||
|
|
2681acebc6 | ||
|
|
a611301c5a | ||
|
|
3f2ffb851f | ||
|
|
daa37961e4 | ||
|
|
66b336279a | ||
|
|
c99804621d | ||
|
|
2ebbc942d3 | ||
|
|
b81d6962ae | ||
|
|
dc89f75678 | ||
|
|
1ccd70b3fc | ||
|
|
0b3002979e | ||
|
|
25dfa76807 | ||
|
|
db26c579b0 | ||
|
|
623b26f60f | ||
|
|
fec6bf8ee0 | ||
|
|
46760ccfba | ||
|
|
214a32c472 | ||
|
|
ee6a4607d8 | ||
|
|
5a3b3ab2e8 | ||
|
|
5d5000fbbd | ||
|
|
554668f1bd | ||
|
|
7ff8b1ecee | ||
|
|
0858b845fb | ||
|
|
d480113b90 | ||
|
|
8f8370b5bc | ||
|
|
cfcc1b06f9 | ||
|
|
7dbb842085 | ||
|
|
76bb7d3e49 | ||
|
|
3d79861ffb | ||
|
|
236942c902 | ||
|
|
3efe0a0fe0 | ||
|
|
9d933a5d95 | ||
|
|
24eb879352 | ||
|
|
f0141cd725 | ||
|
|
9a82bc7c5a | ||
|
|
b42bfefef9 | ||
|
|
47e881af60 | ||
|
|
442284fee2 | ||
|
|
49a6454999 | ||
|
|
a95e52a10c | ||
|
|
b2f1f00875 | ||
|
|
91b50cdcc4 | ||
|
|
078bd7c80e | ||
|
|
7641918224 | ||
|
|
a90198d50e | ||
|
|
dcff1748b7 | ||
|
|
5f903d8ce2 | ||
|
|
92fdea37d1 | ||
|
|
7e59fafb3f | ||
|
|
ae58224f95 | ||
|
|
857c5eee6f | ||
|
|
adb7f876aa | ||
|
|
6b282c378a | ||
|
|
aaca0fc47b | ||
|
|
4681493b58 | ||
|
|
37b0c15c1a | ||
|
|
f7c29ef4e6 | ||
|
|
15c08c347d | ||
|
|
4bf5f85644 | ||
|
|
dfe09dac0e | ||
|
|
ce26ecc8c1 | ||
|
|
356971cc66 | ||
|
|
82413cd3fd | ||
|
|
f07f3de1bd | ||
|
|
33072072c4 | ||
|
|
94351646b1 | ||
|
|
c766465527 | ||
|
|
b982ab9daf | ||
|
|
114682b49f | ||
|
|
0e5dcb0d40 | ||
|
|
3f491f1a9a | ||
|
|
f5a655b9fc | ||
|
|
62494e9c12 | ||
|
|
c88e34fe8f | ||
|
|
1a5abe6363 | ||
|
|
2f3c775d4f | ||
|
|
7078efcc20 | ||
|
|
e73eb87374 | ||
|
|
471a209d86 | ||
|
|
fabb88d611 | ||
|
|
d7d7752868 | ||
|
|
0fe9041272 | ||
|
|
860680aaa1 | ||
|
|
1640bdf172 | ||
|
|
aced5fcab9 | ||
|
|
f786690f1e | ||
|
|
9a1495199b | ||
|
|
15d264acce | ||
|
|
959e8d3acc | ||
|
|
b2edf702ce | ||
|
|
4837474bb0 | ||
|
|
b052eacba7 | ||
|
|
0129305c5f | ||
|
|
5a725b79c9 | ||
|
|
c4f706e918 | ||
|
|
9673f09c9f | ||
|
|
6a84ccbd4e | ||
|
|
14587ee65c | ||
|
|
ef2d129699 | ||
|
|
580c0bef0a | ||
|
|
eef7fef69b | ||
|
|
4d1776f9ff | ||
|
|
f92c21c9c0 | ||
|
|
fac8730ddd | ||
|
|
a3fe58f61b | ||
|
|
696a25fe82 | ||
|
|
04b28dc88f | ||
|
|
abbffb9b15 | ||
|
|
1cd914e5a7 | ||
|
|
6b7bf55acb | ||
|
|
eddcb797e4 | ||
|
|
3014e9253b | ||
|
|
016489ffba | ||
|
|
96ea9e225c | ||
|
|
c4cfc204e7 | ||
|
|
00f5433518 | ||
|
|
3077589cff | ||
|
|
3e3258a4b7 | ||
|
|
dee0f9937e | ||
|
|
1138a4deb1 | ||
|
|
720b14b804 | ||
|
|
196f328312 | ||
|
|
1eb7215162 | ||
|
|
52762cc738 | ||
|
|
c3c38e3510 | ||
|
|
5dcb112621 | ||
|
|
3a9e5fc0cd | ||
|
|
a789fabe2e | ||
|
|
6c8f5e8a16 | ||
|
|
cf08bbdd00 | ||
|
|
80a7765fdd | ||
|
|
119948938e | ||
|
|
b9563cfdc0 | ||
|
|
97e9786fa6 | ||
|
|
60a4541e24 | ||
|
|
52ae19b896 | ||
|
|
8973e976ca | ||
|
|
5ebee4bec7 | ||
|
|
c34b2acf9b | ||
|
|
cd400e5090 | ||
|
|
b433538ab2 | ||
|
|
0a8cc398fe | ||
|
|
a8e37d9486 | ||
|
|
288b017355 | ||
|
|
91745580d8 | ||
|
|
d0d652dba9 | ||
|
|
96f9f89b7b | ||
|
|
2f093011a2 | ||
|
|
cfe55c1975 | ||
|
|
42dc0d754f | ||
|
|
1623257b17 | ||
|
|
3f27256589 | ||
|
|
52c34b6d35 | ||
|
|
485bcfc039 | ||
|
|
1cbcf7048c | ||
|
|
4d90aec5a6 | ||
|
|
85b2a72624 | ||
|
|
acd5d78d30 | ||
|
|
c81e4a8194 | ||
|
|
81fcd57e3a | ||
|
|
6dd940cfab | ||
|
|
a1bdb6c308 | ||
|
|
525397e64d | ||
|
|
e4af67aa68 | ||
|
|
2d7d8812fd | ||
|
|
726ecefd55 | ||
|
|
69fbf9d1ad | ||
|
|
38e89f4e4d | ||
|
|
b3ff273b7c | ||
|
|
fb0be72b56 | ||
|
|
339fbffbb7 | ||
|
|
5c308bb60c | ||
|
|
514fe6e98d | ||
|
|
a80d7fddba | ||
|
|
ec11a89496 | ||
|
|
61aa5f907d | ||
|
|
ac7ba173cd | ||
|
|
fe7a764935 | ||
|
|
c1190d348b | ||
|
|
a81c8518b8 | ||
|
|
d7f533118a | ||
|
|
252fdc4208 | ||
|
|
c1d28655da | ||
|
|
77c2e8abc4 | ||
|
|
b6179463b1 | ||
|
|
99bd7ae437 | ||
|
|
e968388ba7 | ||
|
|
60be576f9d | ||
|
|
47249776a0 | ||
|
|
c8f4fa437d | ||
|
|
09c1b575a7 | ||
|
|
2437b0d901 | ||
|
|
0a33ad6542 | ||
|
|
7ee626edf8 | ||
|
|
600b58be75 | ||
|
|
5601704ac1 | ||
|
|
4eb6386b6e | ||
|
|
9c78970712 | ||
|
|
e465e25adb | ||
|
|
4d735e9e26 | ||
|
|
28bc720bbc | ||
|
|
0fc170e1c3 | ||
|
|
012d98d303 | ||
|
|
180bc99726 | ||
|
|
e537d64c5b | ||
|
|
03de09c836 | ||
|
|
9dc309390a | ||
|
|
8280f31180 | ||
|
|
61a1227f87 | ||
|
|
02c37924aa | ||
|
|
ddc4baf5e2 | ||
|
|
63f975516a | ||
|
|
9160c8319d | ||
|
|
d268b0335e | ||
|
|
6b355c645c | ||
|
|
951e981d8e | ||
|
|
c4c301d363 | ||
|
|
50821924f2 | ||
|
|
3c01011c28 | ||
|
|
d674176356 | ||
|
|
8c60396197 | ||
|
|
4a5b442f35 | ||
|
|
80ea398660 | ||
|
|
69aae1abfa | ||
|
|
c528a98820 | ||
|
|
89fa12c6db | ||
|
|
34598d73a3 | ||
|
|
c507aeae4a | ||
|
|
356ce3cc42 | ||
|
|
560f145324 | ||
|
|
4308fb3f9b | ||
|
|
a17b66c6dc | ||
|
|
f52a3ba773 | ||
|
|
f865338ad0 | ||
|
|
7221207fe5 | ||
|
|
cbc61b533f | ||
|
|
bdff8767f7 | ||
|
|
26f3a9215d | ||
|
|
15e34647fb | ||
|
|
d121aa5a9f | ||
|
|
7d77f754c5 | ||
|
|
2012c4d161 | ||
|
|
fed9ede878 | ||
|
|
4b12716961 | ||
|
|
db2f387a77 | ||
|
|
cb972fdf17 | ||
|
|
5c34d57012 | ||
|
|
3906aa245c | ||
|
|
c86daf3a68 | ||
|
|
9bafd81093 | ||
|
|
5305a27c60 | ||
|
|
6b30ca05db | ||
|
|
b99c17c7cd | ||
|
|
fabaccd3da | ||
|
|
48c0ae93f4 | ||
|
|
30768071bf | ||
|
|
68b619e276 | ||
|
|
d5a441b0e8 | ||
|
|
a5ed0864be | ||
|
|
7db9fe46ff | ||
|
|
99df1bfbaa | ||
|
|
067a4a95e0 | ||
|
|
2154a51831 | ||
|
|
f0bc58e289 | ||
|
|
5fd2f15a5d | ||
|
|
baeef22f4c | ||
|
|
2f6fef0817 | ||
|
|
9cc97c44d5 | ||
|
|
6350e881da | ||
|
|
ec5a799b31 | ||
|
|
026238b125 | ||
|
|
46712cd967 | ||
|
|
273ca15c17 | ||
|
|
94d8a6039e | ||
|
|
61fde5ed42 | ||
|
|
27a0127556 | ||
|
|
585fedd824 | ||
|
|
49ea990519 | ||
|
|
31e06538a2 | ||
|
|
22657f030c | ||
|
|
3805ecca02 | ||
|
|
31ddef5ddb | ||
|
|
6b4d3e5bbd | ||
|
|
acaeb46d5f | ||
|
|
ce70c3b62a | ||
|
|
55ff63ffeb |
27
.github/CONTRIBUTING.md
vendored
27
.github/CONTRIBUTING.md
vendored
@@ -1,25 +1,24 @@
|
||||
## Issues
|
||||
|
||||
When reporting a possible bug, provide detail steps so that we will be able
|
||||
to reproduce the issue. Try not to use phrases like "very big bug",
|
||||
"huge issue", "useless feature", etc. No need to use exclamation marks as well.
|
||||
|
||||
Steps to reproduce should be clear and unambiguous.
|
||||
|
||||
Note that we don't provide developer help or any kind of support on GitHub.
|
||||
For this, please use our [forum](https://forum.espocrm.com).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We are open for contributions that are bug fixes and small improvements. If you would like to contribute something that is not a small fix, please reach out to maintainers before submitting your PR (by creating a GitHub issue).
|
||||
|
||||
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla).
|
||||
|
||||
It's desirable that one PR solves one specific problem. Do not include code style changes to PRs
|
||||
(unless the main purpose of the PR is a code style fix).
|
||||
|
||||
If you would like to contribute something that is not a small fix, it's reasonable to create an issue first
|
||||
(a bug report or feature request).
|
||||
|
||||
Branches:
|
||||
|
||||
* *master* – the develop branch; new features should be pushed to here;
|
||||
* *fix* – the upcoming maintenance release; small fixes should be pushed to here.
|
||||
|
||||
## Issues
|
||||
|
||||
We'd appreciate if you prefer posting issues on weekdays rather than weekends.
|
||||
|
||||
When reporting a possible bug, please provide detail steps so that we will be able
|
||||
to reproduce the issue. Please try not to use phrases like "very big bug",
|
||||
"huge issue", etc. No need to use exclamation marks as well.
|
||||
|
||||
Note that we don't provide developer help or any kind of support on GitHub.
|
||||
For this, please use our [forum](https://forum.espocrm.com).
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for EspoCRM. For high-level features, consider creating feature requests on the forum. For low-level (framework) – here on GitHub.
|
||||
about: Feature requests are not desired at the moment. Need to polish the system. For high-level features, consider creating feature requests on the forum. For low-level (framework) – here on GitHub.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@
|
||||
/data/.backup/*
|
||||
/data/config.php
|
||||
/data/config-internal.php
|
||||
/data/config-override.php
|
||||
/data/config-internal-override.php
|
||||
/data/tmp/*
|
||||
/build
|
||||
/node_modules
|
||||
|
||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -3,5 +3,6 @@
|
||||
<component name="PhpEntryPointsManager">
|
||||
<pattern value="\Espo\Controllers\*" member="*Action*" />
|
||||
<pattern value="\Espo\Modules\*\Controllers\*" member="*Action*" />
|
||||
<suppressed_annotations>@implements</suppressed_annotations>
|
||||
</component>
|
||||
</project>
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -16,7 +16,7 @@
|
||||
"fileMatch": [
|
||||
"*/Resources/module.json"
|
||||
],
|
||||
"url": "./schema/routes.json"
|
||||
"url": "./schema/module.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
@@ -58,12 +58,6 @@
|
||||
],
|
||||
"url": "./schema/metadata/dashlets.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/metadata//*.json"
|
||||
],
|
||||
"url": "./schema/metadata/.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/metadata/entityAcl/*.json"
|
||||
|
||||
24
README.md
24
README.md
@@ -21,8 +21,8 @@ You can try the CRM on the online [demo](https://www.espocrm.com/demo/).
|
||||
### Requirements
|
||||
|
||||
* PHP 8.1 - 8.3;
|
||||
* MySQL 5.7 (and later), or MariaDB 10.2 (and later).
|
||||
* PostgreSQL 15 (and later) (yet experimental, officially supported soon)
|
||||
* MySQL 5.7 (and later), or MariaDB 10.2 (and later);
|
||||
* PostgreSQL 15 (and later) (yet experimental, officially supported soon).
|
||||
|
||||
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
|
||||
|
||||
@@ -49,16 +49,6 @@ See the [developer documentation](https://docs.espocrm.com/development/).
|
||||
|
||||
We highly recommend using IDE for development. The backend codebase follows SOLID principles, utilizes interfaces, static typing and generics. We recommend to start learning EspoCRM from the Dependency Injection article in the documentation.
|
||||
|
||||
### Contributing
|
||||
|
||||
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
|
||||
|
||||
Branches:
|
||||
|
||||
* *fix* – upcoming maintenance release; minor fixes should be pushed to this branch;
|
||||
* *master* – develop branch; new features should be pushed to this branch;
|
||||
* *stable* – last stable release.
|
||||
|
||||
### Community & Support
|
||||
|
||||
If you have a question regarding some features, need help or customizations, want to get in touch with other EspoCRM users, or add a feature request, please use our [community forum](https://forum.espocrm.com/). We believe that using the forum to ask for help and share experience allows everyone in the community to contribute and use this knowledge later.
|
||||
@@ -67,6 +57,16 @@ If you have a question regarding some features, need help or customizations, wan
|
||||
|
||||
EspoCRM is published under the GNU AGPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).
|
||||
|
||||
### Contributing
|
||||
|
||||
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). See [contributing guidelines](https://github.com/espocrm/espocrm/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
Branches:
|
||||
|
||||
* *fix* – upcoming maintenance release; minor fixes should be pushed to this branch;
|
||||
* *master* – develop branch; new features should be pushed to this branch;
|
||||
* *stable* – last stable release.
|
||||
|
||||
### Language
|
||||
|
||||
If you want to improve existing translation or add a language that is not available yet, you can contribute on our [POEditor](https://poeditor.com/join/project/gLDKZtUF4i) project. See instructions [here](https://www.espocrm.com/blog/how-to-use-poeditor-to-translate-espocrm/). It may be reasonable to let us know about your intention to join the POEditor project by posting on our forum or via the contact form on our website.
|
||||
|
||||
@@ -63,11 +63,6 @@ class Binding implements BindingProcessor
|
||||
'container'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Espo\\Core\\Container',
|
||||
'container'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Psr\\Container\\ContainerInterface',
|
||||
'container'
|
||||
@@ -148,11 +143,6 @@ class Binding implements BindingProcessor
|
||||
'recordServiceContainer'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Espo\\Core\\Record\\HookManager',
|
||||
'recordHookManager'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Espo\\Core\\HookManager',
|
||||
'hookManager'
|
||||
|
||||
@@ -99,10 +99,7 @@ class AccessChecker implements AccessEntityCREDChecker
|
||||
else if ($this->aclManager->checkEntity($user, $parent)) {
|
||||
if (
|
||||
$entity->getTargetField() &&
|
||||
in_array(
|
||||
$entity->getTargetField(),
|
||||
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
|
||||
)
|
||||
!$this->aclManager->checkField($user, $parent->getEntityType(), $entity->getTargetField())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Classes\Acl\Note;
|
||||
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
@@ -141,6 +142,10 @@ class AccessChecker implements AccessEntityCREDChecker
|
||||
return in_array($user->getId(), $entity->getLinkMultipleIdList('users'));
|
||||
}
|
||||
|
||||
if ($entity->getTargetType() === Note::TARGET_PORTALS) {
|
||||
return $this->aclManager->getPermissionLevel($user, 'portal') === Table::LEVEL_YES;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,10 +105,7 @@ class AccessChecker implements AccessEntityCREDChecker
|
||||
else if ($this->aclManager->checkEntity($user, $parent)) {
|
||||
if (
|
||||
$entity->getTargetField() &&
|
||||
in_array(
|
||||
$entity->getTargetField(),
|
||||
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
|
||||
)
|
||||
!$this->aclManager->checkField($user, $parent->getEntityType(), $entity->getTargetField())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
104
application/Espo/Classes/Cleanup/Audit.php
Normal file
104
application/Espo/Classes/Cleanup/Audit.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Cleanup;
|
||||
|
||||
use Espo\Core\Cleanup\Cleanup;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Audit implements Cleanup
|
||||
{
|
||||
private const PERIOD = '3 months';
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private EntityManager $entityManager,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
if (!$this->config->get('cleanupAudit')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityTypeList = $this->getEntityTypeList();
|
||||
|
||||
foreach ($entityTypeList as $scope) {
|
||||
$this->processEntityType($scope);
|
||||
}
|
||||
}
|
||||
|
||||
private function processEntityType(string $entityType): void
|
||||
{
|
||||
$query = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Note::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentType' => $entityType,
|
||||
'createdAt<' => $this->getBefore()->toString(),
|
||||
'type' => [Note::TYPE_UPDATE, Note::TYPE_STATUS],
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getEntityTypeList(): array
|
||||
{
|
||||
/** @var string[] $scopeList */
|
||||
$scopeList = array_keys($this->metadata->get(['scopes']) ?? []);
|
||||
|
||||
$scopeList = array_filter($scopeList, function ($item) {
|
||||
return $this->metadata->get(['scopes', $item, 'entity']) &&
|
||||
!$this->metadata->get(['scopes', $item, 'stream']);
|
||||
});
|
||||
|
||||
return array_values($scopeList);
|
||||
}
|
||||
|
||||
private function getBefore(): DateTime
|
||||
{
|
||||
/** @var string $period */
|
||||
$period = $this->config->get('cleanupAuditPeriod') ?? self::PERIOD;
|
||||
|
||||
return DateTime::createNow()->modify('-' . $period);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\ConsoleCommands;
|
||||
|
||||
use Espo\Core\Console\Command;
|
||||
use Espo\Core\Console\Command\Params;
|
||||
use Espo\Core\Console\IO;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\System;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CheckFilePermissions implements Command
|
||||
{
|
||||
public function __construct(
|
||||
private FileManager $fileManager,
|
||||
private System $system
|
||||
) {}
|
||||
|
||||
public function run(Params $params, IO $io): void
|
||||
{
|
||||
$io->writeLine("\nNote: Run this command under the web server user.\n");
|
||||
|
||||
$io->writeLine('Writable:');
|
||||
$io->writeLine('');
|
||||
|
||||
foreach ($this->fileManager->getPermissionUtils()->getWritableList() as $path) {
|
||||
$fullPath = Util::concatPath($this->system->getRootDir(), $path);
|
||||
|
||||
$isWritable = $this->fileManager->isWritable($fullPath);
|
||||
|
||||
$msg = " " . ($isWritable ? "OK" : "FAIL") . " : $path";
|
||||
|
||||
$io->writeLine($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,46 +27,58 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\FieldProcessing\LeadCapture;
|
||||
|
||||
use Espo\Entities\LeadCapture as LeadCaptureEntity;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Entities\LeadCapture;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\LeadCapture\Service as LeadCaptureService;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @extends Record<LeadCaptureEntity>
|
||||
* @implements Loader<LeadCapture>
|
||||
*/
|
||||
class LeadCapture extends Record
|
||||
class ExampleLoader implements Loader
|
||||
{
|
||||
/** @var string[] */
|
||||
protected $readOnlyAttributeList = ['apiKey'];
|
||||
public function __construct(
|
||||
private FieldUtil $fieldUtil,
|
||||
private Config $config,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param LeadCaptureEntity $entity
|
||||
*/
|
||||
public function prepareEntityForOutput(Entity $entity)
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
parent::prepareEntityForOutput($entity);
|
||||
|
||||
$entity->set('exampleRequestMethod', 'POST');
|
||||
|
||||
$entity->set('exampleRequestHeaders', [
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
|
||||
$this->processRequestUrl($entity);
|
||||
$this->processRequestPayload($entity);
|
||||
}
|
||||
|
||||
private function processRequestUrl(LeadCapture $entity): void
|
||||
{
|
||||
$apiKey = $entity->getApiKey();
|
||||
$siteUrl = $this->config->get('siteUrl');
|
||||
|
||||
if ($apiKey) {
|
||||
$requestUrl = $this->config->getSiteUrl() . '/api/v1/LeadCapture/' . $apiKey;
|
||||
|
||||
$entity->set('exampleRequestUrl', $requestUrl);
|
||||
if (!$apiKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldUtil = $this->fieldUtil;
|
||||
$requestUrl = "$siteUrl/api/v1/LeadCapture/$apiKey";
|
||||
|
||||
$requestPayload = "```{\n";
|
||||
$entity->set('exampleRequestUrl', $requestUrl);
|
||||
}
|
||||
|
||||
private function processRequestPayload(LeadCapture $entity): void
|
||||
{
|
||||
$requestPayload = "```\n{\n";
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
@@ -80,7 +92,7 @@ class LeadCapture extends Record
|
||||
];
|
||||
|
||||
foreach ($entity->getFieldList() as $field) {
|
||||
foreach ($fieldUtil->getActualAttributeList(Lead::ENTITY_TYPE, $field) as $attribute) {
|
||||
foreach ($this->fieldUtil->getActualAttributeList(Lead::ENTITY_TYPE, $field) as $attribute) {
|
||||
if (!in_array($attribute, $attributeIgnoreList)) {
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
@@ -105,20 +117,8 @@ class LeadCapture extends Record
|
||||
$requestPayload .= "\n";
|
||||
}
|
||||
|
||||
$requestPayload .= '}```';
|
||||
$requestPayload .= "}\n```";
|
||||
|
||||
$entity->set('exampleRequestPayload', $requestPayload);
|
||||
}
|
||||
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
$apiKey = $this->createLeadCaptureService()->generateApiKey();
|
||||
|
||||
$entity->set('apiKey', $apiKey);
|
||||
}
|
||||
|
||||
protected function createLeadCaptureService(): LeadCaptureService
|
||||
{
|
||||
return $this->injectableFactory->create(LeadCaptureService::class);
|
||||
}
|
||||
}
|
||||
@@ -29,19 +29,18 @@
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\Note;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Loader<Note>
|
||||
*/
|
||||
class AttachmentsLoader implements Loader
|
||||
class AdditionalFieldsLoader implements Loader
|
||||
{
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
/** @var Note $entity */
|
||||
$entity->loadAttachments();
|
||||
$entity->loadAdditionalFields();
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ use Espo\Entities\AuthToken;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
@@ -44,6 +43,7 @@ use Exception;
|
||||
|
||||
/**
|
||||
* @implements Loader<User>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class LastAccessLoader implements Loader
|
||||
{
|
||||
@@ -58,10 +58,7 @@ class LastAccessLoader implements Loader
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$forbiddenFieldList = $this->acl
|
||||
->getScopeForbiddenFieldList($entity->getEntityType(), Table::ACTION_READ);
|
||||
|
||||
if (in_array('lastAccess', $forbiddenFieldList)) {
|
||||
if (!$this->acl->checkField($entity->getEntityType(), 'lastAccess')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
54
application/Espo/Classes/FieldSanitizers/ArrayFromNull.php
Normal file
54
application/Espo/Classes/FieldSanitizers/ArrayFromNull.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ArrayFromNull implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
if (!$data->has($field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $data->get($field);
|
||||
|
||||
if ($value !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data->set($field, []);
|
||||
}
|
||||
}
|
||||
62
application/Espo/Classes/FieldSanitizers/ArrayStringTrim.php
Normal file
62
application/Espo/Classes/FieldSanitizers/ArrayStringTrim.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ArrayStringTrim implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
if (!$data->has($field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $data->get($field);
|
||||
|
||||
if (!is_array($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($value as $i => $item) {
|
||||
if (!is_string($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value[$i] = trim($item);
|
||||
}
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
70
application/Espo/Classes/FieldSanitizers/Date.php
Normal file
70
application/Espo/Classes/FieldSanitizers/Date.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Espo\Core\Field\Date as DateValue;
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Date implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
$value = $data->get($field);
|
||||
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DateValue::fromString($value);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception) {}
|
||||
|
||||
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
|
||||
|
||||
if ($dateTime === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $dateTime->format(DateTimeUtil::SYSTEM_DATE_FORMAT);
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
73
application/Espo/Classes/FieldSanitizers/Datetime.php
Normal file
73
application/Espo/Classes/FieldSanitizers/Datetime.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use Espo\Core\Field\DateTime as DateTimeValue;
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Datetime implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
$value = $data->get($field);
|
||||
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DateTimeValue::fromString($value);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception) {}
|
||||
|
||||
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
|
||||
|
||||
if ($dateTime === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $dateTime
|
||||
->setTimezone(new DateTimeZone('UTC'))
|
||||
->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class DatetimeOptionalDate implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
$attribute = $field . 'Date';
|
||||
|
||||
$value = $data->get($attribute);
|
||||
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Date::fromString($value);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception) {}
|
||||
|
||||
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
|
||||
|
||||
if ($dateTime === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $dateTime->format(DateTimeUtil::SYSTEM_DATE_FORMAT);
|
||||
|
||||
$data->set($attribute, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EmptyStringToNull implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
if (!$data->has($field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $data->get($field);
|
||||
|
||||
if (!is_string($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value === '') {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
60
application/Espo/Classes/FieldSanitizers/StringTrim.php
Normal file
60
application/Espo/Classes/FieldSanitizers/StringTrim.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class StringTrim implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
if (!$data->has($field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $data->get($field);
|
||||
|
||||
if (!is_string($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
if ($value === '') {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldValidators\InboundEmail\FetchSince;
|
||||
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\Entities\EmailAccount;
|
||||
use Espo\Entities\InboundEmail;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Validator<InboundEmail|EmailAccount>
|
||||
*/
|
||||
class Required implements Validator
|
||||
{
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
if (!$entity->isAvailableForFetching()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entity->get('fetchSince')) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -27,38 +27,36 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
namespace Espo\Classes\FieldValidators\ScheduledJob\Scheduling;
|
||||
|
||||
use Cron\CronExpression;
|
||||
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\Entities\ScheduledJob;
|
||||
use Espo\ORM\Entity;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @extends Record<\Espo\Entities\ScheduledJob>
|
||||
* @implements Validator<ScheduledJob>
|
||||
*/
|
||||
class ScheduledJob extends Record
|
||||
class Valid implements Validator
|
||||
{
|
||||
/** Should not be removed. */
|
||||
protected bool $findLinkedLogCountQueryDisabled = true;
|
||||
|
||||
public function processValidation(Entity $entity, $data)
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
parent::processValidation($entity, $data);
|
||||
$scheduling = $entity->getScheduling();
|
||||
|
||||
$scheduling = $entity->get('scheduling');
|
||||
if ($scheduling === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$cronExpression = CronExpression::factory($scheduling);
|
||||
new CronExpression($scheduling);
|
||||
}
|
||||
catch (Exception) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line*/
|
||||
$cronExpression->getNextRunDate()->format('Y-m-d H:i:s');
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new BadRequest("Not valid scheduling expression.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldValidators\Settings\ThousandSeparator;
|
||||
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Validator<Entity>
|
||||
*/
|
||||
class Valid implements Validator
|
||||
{
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
$value = $entity->get($field);
|
||||
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
if (preg_match('/^[0-9]$/', $value)) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -34,24 +34,24 @@ use Espo\Core\Job\JobDataLess;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AuthTokenControl implements JobDataLess
|
||||
{
|
||||
private Config $config;
|
||||
private EntityManager $entityManager;
|
||||
private const LIMIT = 500;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$authTokenLifetime = $this->config->get('authTokenLifetime');
|
||||
$authTokenMaxIdleTime = $this->config->get('authTokenMaxIdleTime');
|
||||
$authTokenLifetime = (int) ($this->config->get('authTokenLifetime', 0) * 60);
|
||||
$authTokenMaxIdleTime = (int) ($this->config->get('authTokenMaxIdleTime', 0) * 60);
|
||||
|
||||
if (!$authTokenLifetime && !$authTokenMaxIdleTime) {
|
||||
return;
|
||||
@@ -63,28 +63,23 @@ class AuthTokenControl implements JobDataLess
|
||||
|
||||
if ($authTokenLifetime) {
|
||||
$dt = new DateTime();
|
||||
$dt->modify("-$authTokenLifetime minutes");
|
||||
|
||||
$dt->modify('-' . $authTokenLifetime . ' hours');
|
||||
|
||||
$authTokenLifetimeThreshold = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
|
||||
$whereClause['createdAt<'] = $authTokenLifetimeThreshold;
|
||||
$whereClause['createdAt<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
}
|
||||
|
||||
if ($authTokenMaxIdleTime) {
|
||||
$dt = new DateTime();
|
||||
$dt->modify("-$authTokenMaxIdleTime minutes");
|
||||
|
||||
$dt->modify('-' . $authTokenMaxIdleTime . ' hours');
|
||||
|
||||
$authTokenMaxIdleTimeThreshold = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
|
||||
$whereClause['lastAccess<'] = $authTokenMaxIdleTimeThreshold;
|
||||
$whereClause['lastAccess<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
}
|
||||
|
||||
$tokenList = $this->entityManager
|
||||
->getRDBRepository(AuthToken::ENTITY_TYPE)
|
||||
->sth()
|
||||
->where($whereClause)
|
||||
->limit(0, 500)
|
||||
->limit(0, self::LIMIT)
|
||||
->find();
|
||||
|
||||
foreach ($tokenList as $token) {
|
||||
|
||||
156
application/Espo/Classes/Record/Attachment/CreateInputFilter.php
Normal file
156
application/Espo/Classes/Record/Attachment/CreateInputFilter.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Attachment;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Attachment\AccessChecker;
|
||||
use Espo\Tools\Attachment\DetailsObtainer;
|
||||
use Espo\Tools\Attachment\FieldData;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CreateInputFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private AccessChecker $accessChecker,
|
||||
private DetailsObtainer $detailsObtainer
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
$data->clear('parentId');
|
||||
$data->clear('relatedId');
|
||||
|
||||
$contents = $this->handleContents($data);
|
||||
|
||||
$relatedEntityType = $this->getRelatedEntityType($data);
|
||||
|
||||
$field = $data->get('field');
|
||||
$role = $data->get('role') ?? Attachment::ROLE_ATTACHMENT;
|
||||
|
||||
if (!$relatedEntityType || !$field) {
|
||||
throw new BadRequest("No `field` and `parentType`.");
|
||||
}
|
||||
|
||||
$fieldData = new FieldData($field, $data->get('parentType'), $data->get('relatedType'));
|
||||
|
||||
$this->accessChecker->check($fieldData, $role);
|
||||
$this->checkMaxSize($contents, $data, $field, $role);
|
||||
}
|
||||
|
||||
private function getRelatedEntityType(Data $data): ?string
|
||||
{
|
||||
if ($data->get('parentType') !== null) {
|
||||
$data->clear('relatedType');
|
||||
|
||||
return $data->get('parentType');
|
||||
}
|
||||
|
||||
if ($data->get('relatedType') !== null) {
|
||||
return $data->get('relatedType');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function handleContents(Data $data): string
|
||||
{
|
||||
$isBeingUploaded = $data->get('isBeingUploaded') ?? false;
|
||||
|
||||
$contents = '';
|
||||
|
||||
if (!$isBeingUploaded) {
|
||||
if (!$data->has('file')) {
|
||||
throw new BadRequest("No file contents.");
|
||||
}
|
||||
|
||||
$file = $data->get('file');
|
||||
|
||||
if (!is_string($file)) {
|
||||
throw new BadRequest("Non-string file contents.");
|
||||
}
|
||||
|
||||
$arr = explode(',', $file);
|
||||
|
||||
if (count($arr) < 2) {
|
||||
throw new BadRequest("Bad file contents.");
|
||||
}
|
||||
|
||||
$contents = base64_decode($arr[1]);
|
||||
|
||||
if ($contents === false) {
|
||||
throw new BadRequest("Could not decode file contents.");
|
||||
}
|
||||
}
|
||||
|
||||
$data->set('contents', $contents);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function checkMaxSize(string $contents, Data $data, mixed $field, mixed $role): void
|
||||
{
|
||||
$size = mb_strlen($contents, '8bit');
|
||||
|
||||
$dummy = $this->entityManager->getRepositoryByClass(Attachment::class)->getNew();
|
||||
|
||||
$dummy->set([
|
||||
'parentType' => $data->get('parentType'),
|
||||
'relatedType' => $data->get('relatedType'),
|
||||
'field' => $field,
|
||||
'role' => $role,
|
||||
]);
|
||||
|
||||
$maxSize = $this->detailsObtainer->getUploadMaxSize($dummy);
|
||||
|
||||
if ($maxSize && $size > $maxSize * 1024 * 1024) {
|
||||
throw new BadRequest("File size should not exceed $maxSize Mb.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Attachment;
|
||||
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class UpdateInputFilter implements Filter
|
||||
{
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
$data->clear('parentId');
|
||||
$data->clear('parentType');
|
||||
$data->clear('relatedId');
|
||||
$data->clear('relatedType');
|
||||
$data->clear('isBeingUploaded');
|
||||
$data->clear('storage');
|
||||
}
|
||||
}
|
||||
@@ -27,33 +27,26 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\Record\AuthToken;
|
||||
|
||||
use stdClass;
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
|
||||
/**
|
||||
* @extends Record<\Espo\Entities\AuthToken>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AuthToken extends Record
|
||||
class UpdateInputFilter implements Filter
|
||||
{
|
||||
protected $actionHistoryDisabled = true;
|
||||
|
||||
public function filterUpdateInput(stdClass $data): void
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
parent::filterUpdateInput($data);
|
||||
|
||||
$dataArray = get_object_vars($data);
|
||||
|
||||
foreach (array_keys($dataArray) as $attribute) {
|
||||
foreach ($data->getAttributeList() as $attribute) {
|
||||
if ($attribute !== 'isActive') {
|
||||
unset($data->$attribute);
|
||||
|
||||
continue;
|
||||
$data->clear($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data->isActive ?? false) {
|
||||
unset($data->isActive);
|
||||
if ($data->get('isActive')) {
|
||||
$data->clear('isActive');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\InboundEmail;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
use Espo\Core\Utils\Crypt;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PasswordsInputFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private Crypt $crypt
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
$password = $data->get('password');
|
||||
|
||||
if ($password !== null) {
|
||||
if (!is_string($password)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$data->set('password', $this->crypt->encrypt($password));
|
||||
}
|
||||
|
||||
$smtpPassword = $data->get('smtpPassword');
|
||||
|
||||
if ($smtpPassword !== null) {
|
||||
if (!is_string($smtpPassword)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$data->set('smtpPassword', $this->crypt->encrypt($smtpPassword));
|
||||
}
|
||||
}
|
||||
}
|
||||
50
application/Espo/Classes/Record/Note/UpdateInputFilter.php
Normal file
50
application/Espo/Classes/Record/Note/UpdateInputFilter.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Note;
|
||||
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class UpdateInputFilter implements Filter
|
||||
{
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
$data->clear('parentId');
|
||||
$data->clear('parentType');
|
||||
$data->clear('targetType');
|
||||
$data->clear('usersIds');
|
||||
$data->clear('teamsIds');
|
||||
$data->clear('portalsIds');
|
||||
$data->clear('isGlobal');
|
||||
}
|
||||
}
|
||||
59
application/Espo/Classes/Record/Portal/InputFilter.php
Normal file
59
application/Espo/Classes/Record/Portal/InputFilter.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Portal;
|
||||
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class InputFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
if (!$this->config->get('restrictedMode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data->clear('customUrl');
|
||||
}
|
||||
}
|
||||
@@ -27,47 +27,48 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\Record\User;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
|
||||
use Espo\Core\Authentication\Logins\Hmac;
|
||||
use Espo\Core\Record\Output\Filter;
|
||||
use Espo\Core\Utils\ApiKey;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
/**
|
||||
* @extends Record<\Espo\Entities\Role>
|
||||
* @implements Filter<User>
|
||||
*/
|
||||
class Role extends Record implements
|
||||
|
||||
Di\DataManagerAware
|
||||
class OutputFilter implements Filter
|
||||
{
|
||||
use Di\DataManagerSetter;
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private ApiKey $apiKey
|
||||
) {}
|
||||
|
||||
protected $forceSelectAllAttributes = true;
|
||||
|
||||
public function afterCreateEntity(Entity $entity, $data)
|
||||
public function filter(Entity $entity): void
|
||||
{
|
||||
parent::afterCreateEntity($entity, $data);
|
||||
$entity->clear('sendAccessInfo');
|
||||
|
||||
$this->clearRolesCache();
|
||||
$this->filterApiUser($entity);
|
||||
}
|
||||
|
||||
public function afterUpdateEntity(Entity $entity, $data)
|
||||
private function filterApiUser(User $entity): void
|
||||
{
|
||||
parent::afterUpdateEntity($entity, $data);
|
||||
if (!$entity->isApi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearRolesCache();
|
||||
}
|
||||
if ($this->user->isAdmin()) {
|
||||
if ($entity->getAuthMethod() === Hmac::NAME) {
|
||||
$secretKey = $this->apiKey->getSecretKeyForUserId($entity->getId());
|
||||
|
||||
protected function clearRolesCache(): void
|
||||
{
|
||||
$this->createAclCacheClearer()->clearForAllInternalUsers();
|
||||
$entity->set('secretKey', $secretKey);
|
||||
}
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private function createAclCacheClearer(): AclCacheClearer
|
||||
{
|
||||
return $this->injectableFactory->create(AclCacheClearer::class);
|
||||
$entity->clear('apiKey');
|
||||
$entity->clear('secretKey');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Webhook;
|
||||
|
||||
use Espo\Core\Record\Defaults\DefaultPopulator;
|
||||
use Espo\Core\Record\Defaults\Populator;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Webhook;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Populator<Webhook>
|
||||
*/
|
||||
class DefaultsPopulator implements Populator
|
||||
{
|
||||
public function __construct(
|
||||
private DefaultPopulator $defaultsDefaultsPopulator,
|
||||
private User $user
|
||||
) {}
|
||||
|
||||
public function populate(Entity $entity): void
|
||||
{
|
||||
$this->defaultsDefaultsPopulator->populate($entity);
|
||||
|
||||
if ($this->user->isApi()) {
|
||||
$entity->set('userId', $this->user->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Record\Webhook;
|
||||
|
||||
use Espo\Core\Record\Input\Data;
|
||||
use Espo\Core\Record\Input\Filter;
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class UpdateInputFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private User $user
|
||||
) {}
|
||||
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
$data->clear('event');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,18 +27,19 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Crm\Services;
|
||||
namespace Espo\Classes\RecordHooks\Attachment;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Call extends Meeting
|
||||
/**
|
||||
* @implements SaveHook<Attachment>
|
||||
*/
|
||||
class AfterCreate implements SaveHook
|
||||
{
|
||||
protected function afterUpdateEntity(Entity $entity, $data)
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
parent::afterUpdateEntity($entity, $data);
|
||||
|
||||
if (isset($data->contactsIds) || isset($data->leadsIds)) {
|
||||
$this->loadAdditionalFields($entity);
|
||||
}
|
||||
$entity->clear('contents');
|
||||
}
|
||||
}
|
||||
100
application/Espo/Classes/RecordHooks/Attachment/BeforeCreate.php
Normal file
100
application/Espo/Classes/RecordHooks/Attachment/BeforeCreate.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Attachment;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Attachment\Checker;
|
||||
use Espo\Tools\Attachment\DetailsObtainer;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Attachment>
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Metadata $metadata,
|
||||
private DetailsObtainer $detailsObtainer,
|
||||
private Checker $checker
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->processStorage($entity);
|
||||
$this->processRole($entity);
|
||||
$this->processSize($entity);
|
||||
|
||||
$this->checker->checkType($entity);
|
||||
}
|
||||
|
||||
private function processStorage(Attachment $entity): void
|
||||
{
|
||||
$storage = $entity->getStorage();
|
||||
|
||||
$availableStorageList = $this->config->get('attachmentAvailableStorageList') ?? [];
|
||||
|
||||
if (
|
||||
$storage &&
|
||||
(
|
||||
!in_array($storage, $availableStorageList) ||
|
||||
!$this->metadata->get(['app', 'fileStorage', 'implementationClassNameMap', $storage])
|
||||
)
|
||||
) {
|
||||
$entity->clear('storage');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processSize(Attachment $entity): void
|
||||
{
|
||||
$size = $entity->getSize();
|
||||
|
||||
$maxSize = $this->detailsObtainer->getUploadMaxSize($entity);
|
||||
|
||||
// Checking not actual file size but a set value.
|
||||
if ($size && $size > $maxSize) {
|
||||
throw new Forbidden("Attachment size exceeds `attachmentUploadMaxSize`.");
|
||||
}
|
||||
}
|
||||
|
||||
private function processRole(Attachment $entity): void
|
||||
{
|
||||
if (!$entity->getRole()) {
|
||||
$entity->setRole(Attachment::ROLE_ATTACHMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
application/Espo/Classes/RecordHooks/Email/AfterUpdate.php
Normal file
64
application/Espo/Classes/RecordHooks/Email/AfterUpdate.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Mail\Exceptions\NoSmtp;
|
||||
use Espo\Core\Mail\Exceptions\SendingError;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Email\SendService;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class AfterUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private SendService $sendService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Error
|
||||
* @throws NoSmtp
|
||||
* @throws SendingError
|
||||
*/
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($entity->getStatus() === Email::STATUS_SENDING) {
|
||||
$this->sendService->send($entity, $this->user);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
application/Espo/Classes/RecordHooks/Email/BeforeCreate.php
Normal file
50
application/Espo/Classes/RecordHooks/Email/BeforeCreate.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($entity->getStatus() === Email::STATUS_SENDING) {
|
||||
$messageId = Sender::generateMessageId($entity);
|
||||
|
||||
$entity->setMessageId('<' . $messageId . '>');
|
||||
}
|
||||
}
|
||||
}
|
||||
151
application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php
Normal file
151
application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Core\Utils\SystemUser;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class BeforeUpdate implements SaveHook
|
||||
{
|
||||
/** @var string[] */
|
||||
private $allowedForUpdateFieldList = [
|
||||
'parent',
|
||||
'teams',
|
||||
'assignedUser',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private EntityManager $entityManager,
|
||||
private FieldUtil $fieldUtil
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$skipFilter = false;
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
$skipFilter = true;
|
||||
}
|
||||
|
||||
if ($this->isEmailManuallyArchived($entity)) {
|
||||
$skipFilter = true;
|
||||
}
|
||||
else if ($entity->isAttributeChanged('dateSent')) {
|
||||
$entity->set('dateSent', $entity->getFetched('dateSent'));
|
||||
}
|
||||
|
||||
if ($entity->getStatus() === Email::STATUS_DRAFT) {
|
||||
$skipFilter = true;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->getStatus() === Email::STATUS_SENDING &&
|
||||
$entity->getFetched('status') === Email::STATUS_DRAFT
|
||||
) {
|
||||
$skipFilter = true;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->isAttributeChanged('status') &&
|
||||
$entity->getFetched('status') === Email::STATUS_ARCHIVED
|
||||
) {
|
||||
$entity->setStatus(Email::STATUS_ARCHIVED);
|
||||
}
|
||||
|
||||
if (!$skipFilter) {
|
||||
$this->clearEntityForUpdate($entity);
|
||||
}
|
||||
|
||||
if ($entity->getStatus() == Email::STATUS_SENDING) {
|
||||
$messageId = Sender::generateMessageId($entity);
|
||||
|
||||
$entity->setMessageId('<' . $messageId . '>');
|
||||
}
|
||||
}
|
||||
|
||||
private function isEmailManuallyArchived(Email $email): bool
|
||||
{
|
||||
if ($email->getStatus() !== Email::STATUS_ARCHIVED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$userId = $email->getCreatedBy()?->getId();
|
||||
|
||||
if (!$userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $this->entityManager
|
||||
->getRDBRepositoryByClass(User::class)
|
||||
->getById($userId);
|
||||
|
||||
if (!$user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->getUserName() !== SystemUser::NAME;
|
||||
}
|
||||
|
||||
private function clearEntityForUpdate(Email $email): void
|
||||
{
|
||||
$fieldDefsList = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity(Email::ENTITY_TYPE)
|
||||
->getFieldList();
|
||||
|
||||
foreach ($fieldDefsList as $fieldDefs) {
|
||||
$field = $fieldDefs->getName();
|
||||
|
||||
if ($fieldDefs->getParam('isCustom')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($field, $this->allowedForUpdateFieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList = $this->fieldUtil->getAttributeList(Email::ENTITY_TYPE, $field);
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$email->clear($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
application/Espo/Classes/RecordHooks/Email/MarkAsRead.php
Normal file
55
application/Espo/Classes/RecordHooks/Email/MarkAsRead.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Record\Hook\ReadHook;
|
||||
use Espo\Core\Record\ReadParams;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Email\InboxService;
|
||||
|
||||
/**
|
||||
* @implements ReadHook<Email>
|
||||
*/
|
||||
class MarkAsRead implements ReadHook
|
||||
{
|
||||
public function __construct(
|
||||
private InboxService $inboxService
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, ReadParams $params): void
|
||||
{
|
||||
if ($entity->isRead()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->inboxService->markAsRead($entity->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Email\InboxService;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class MarkAsReadBeforeUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private InboxService $inboxService
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($entity->isRead()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->inboxService->markAsRead($entity->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\EmailAccount;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Entities\EmailAccount;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<EmailAccount>
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private Config $config,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->set('assignedUserId', $this->user->getId());
|
||||
|
||||
$count = $this->entityManager
|
||||
->getRDBRepository(EmailAccount::ENTITY_TYPE)
|
||||
->where(['assignedUserId' => $this->user->getId()])
|
||||
->count();
|
||||
|
||||
if ($count >= $this->config->get('maxEmailAccountCount', PHP_INT_MAX)) {
|
||||
throw new Forbidden("Email Account number for user limit exceeded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,32 +27,33 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\RecordHooks\EmailFilter;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\EmailAccount as EmailAccountEntity;
|
||||
use Espo\Entities\EmailFilter as EmailFilterEntity;
|
||||
use Espo\Entities\EmailFilter;
|
||||
use Espo\Entities\InboundEmail as InboundEmailEntity;
|
||||
use Espo\Entities\User as UserEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @extends Record<EmailFilterEntity>
|
||||
* @implements SaveHook<EmailFilter>
|
||||
*/
|
||||
class EmailFilter extends Record
|
||||
class BeforeSave implements SaveHook
|
||||
{
|
||||
/**
|
||||
* @param EmailFilterEntity $entity
|
||||
* @throws Forbidden
|
||||
*/
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeCreateEntity($entity, $data);
|
||||
public function __construct(
|
||||
private Acl $acl
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
// Check if own.
|
||||
if (!$this->acl->checkEntityEdit($entity)) {
|
||||
if ($entity->isNew() && !$this->acl->checkEntityEdit($entity)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
@@ -60,26 +61,17 @@ class EmailFilter extends Record
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EmailFilterEntity $entity
|
||||
* @throws Forbidden
|
||||
*/
|
||||
protected function beforeUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeUpdateEntity($entity, $data);
|
||||
|
||||
$this->controlEntityValues($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function controlEntityValues(EmailFilterEntity $entity): void
|
||||
private function controlEntityValues(EmailFilter $entity): void
|
||||
{
|
||||
if ($entity->isGlobal()) {
|
||||
$entity->set('parentId', null);
|
||||
$entity->set('parentType', null);
|
||||
$entity->setMultiple([
|
||||
'parentType' => null,
|
||||
'parentId' => null,
|
||||
]);
|
||||
|
||||
if ($entity->getAction() !== EmailFilterEntity::ACTION_SKIP) {
|
||||
if ($entity->getAction() !== EmailFilter::ACTION_SKIP) {
|
||||
throw new Forbidden("Not allowed `action`.");
|
||||
}
|
||||
}
|
||||
@@ -93,9 +85,9 @@ class EmailFilter extends Record
|
||||
!in_array(
|
||||
$entity->getAction(),
|
||||
[
|
||||
EmailFilterEntity::ACTION_NONE,
|
||||
EmailFilterEntity::ACTION_SKIP,
|
||||
EmailFilterEntity::ACTION_MOVE_TO_FOLDER,
|
||||
EmailFilter::ACTION_NONE,
|
||||
EmailFilter::ACTION_SKIP,
|
||||
EmailFilter::ACTION_MOVE_TO_FOLDER,
|
||||
]
|
||||
)
|
||||
) {
|
||||
@@ -107,8 +99,8 @@ class EmailFilter extends Record
|
||||
!in_array(
|
||||
$entity->getAction(),
|
||||
[
|
||||
EmailFilterEntity::ACTION_SKIP,
|
||||
EmailFilterEntity::ACTION_MOVE_TO_GROUP_FOLDER,
|
||||
EmailFilter::ACTION_SKIP,
|
||||
EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER,
|
||||
]
|
||||
)
|
||||
) {
|
||||
@@ -117,26 +109,19 @@ class EmailFilter extends Record
|
||||
|
||||
if (
|
||||
$entity->getParentType() === EmailAccountEntity::ENTITY_TYPE &&
|
||||
$entity->getAction() !== EmailFilterEntity::ACTION_SKIP
|
||||
$entity->getAction() !== EmailFilter::ACTION_SKIP
|
||||
) {
|
||||
throw new Forbidden("Not allowed `action`.");
|
||||
}
|
||||
|
||||
if ($entity->getAction() !== EmailFilterEntity::ACTION_MOVE_TO_FOLDER) {
|
||||
if ($entity->getAction() !== EmailFilter::ACTION_MOVE_TO_FOLDER) {
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$entity->set('emailFolderId', null);
|
||||
}
|
||||
|
||||
if ($entity->getAction() !== EmailFilterEntity::ACTION_MOVE_TO_GROUP_FOLDER) {
|
||||
if ($entity->getAction() !== EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER) {
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$entity->set('groupEmailFolderId', null);
|
||||
}
|
||||
}
|
||||
|
||||
public function filterUpdateInput(stdClass $data): void
|
||||
{
|
||||
parent::filterUpdateInput($data);
|
||||
|
||||
unset($data->isGlobal);
|
||||
unset($data->parentId);
|
||||
unset($data->parentType);
|
||||
}
|
||||
}
|
||||
@@ -27,20 +27,27 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\RecordHooks\EmailFolder;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\EmailFolder;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @extends Record<\Espo\Entities\EmailFolder>
|
||||
* @implements SaveHook<EmailFolder>
|
||||
*/
|
||||
class EmailFolder extends Record
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeCreateEntity($entity, $data);
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private Acl $acl
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if (!$this->user->isAdmin() || !$entity->get('assignedUserId')) {
|
||||
$entity->set('assignedUserId', $this->user->getId());
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\LeadCapture;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\LeadCapture;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\LeadCapture\Service;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
* @implements SaveHook<LeadCapture>
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Service $service
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$apiKey = $this->service->generateApiKey();
|
||||
|
||||
$entity->setApiKey($apiKey);
|
||||
}
|
||||
}
|
||||
90
application/Espo/Classes/RecordHooks/Note/AfterCreate.php
Normal file
90
application/Espo/Classes/RecordHooks/Note/AfterCreate.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Note;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\Note as NoteEntity;
|
||||
use Espo\Entities\Preferences;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Stream\Service;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Note>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AfterCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
private Service $streamService
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$parentType = $entity->getParentType();
|
||||
$parentId = $entity->getParentId();
|
||||
|
||||
if (
|
||||
$entity->getType() !== NoteEntity::TYPE_POST ||
|
||||
!$parentType ||
|
||||
!$parentId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $parentType, 'stream'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $this->user->getId());
|
||||
|
||||
if (!$preferences) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$preferences->get('followEntityOnStreamPost')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntityById($parentType, $parentId);
|
||||
|
||||
if (!$parent || $this->user->isSystem() || $this->user->isApi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->streamService->followEntity($parent, $this->user->getId());
|
||||
}
|
||||
}
|
||||
195
application/Espo/Classes/RecordHooks/Note/AssignmentCheck.php
Normal file
195
application/Espo/Classes/RecordHooks/Note/AssignmentCheck.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Note;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table as AclTable;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Repositories\User as UserRepository;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Note>
|
||||
*/
|
||||
class AssignmentCheck implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private Acl $acl,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$targetType = $entity->getTargetType();
|
||||
|
||||
if (!$targetType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userTeamIdList = $this->user->getTeamIdList();
|
||||
|
||||
$userIdList = $entity->getLinkMultipleIdList('users');
|
||||
$portalIdList = $entity->getLinkMultipleIdList('portals');
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
|
||||
/** @var iterable<User> $targetUserList */
|
||||
$targetUserList = [];
|
||||
|
||||
if ($targetType === Note::TARGET_USERS) {
|
||||
/** @var iterable<User> $targetUserList */
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->select(['id', 'type'])
|
||||
->where(['id' => $userIdList])
|
||||
->find();
|
||||
}
|
||||
|
||||
$hasPortalTargetUser = false;
|
||||
$allTargetUsersArePortal = true;
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if (!$user->isPortal()) {
|
||||
$allTargetUsersArePortal = false;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$hasPortalTargetUser = true;
|
||||
}
|
||||
}
|
||||
|
||||
$messagePermission = $this->acl->getPermissionLevel('message');
|
||||
|
||||
if ($messagePermission === AclTable::LEVEL_NO) {
|
||||
if (
|
||||
$targetType !== Note::TARGET_SELF &&
|
||||
$targetType !== Note::TARGET_PORTALS &&
|
||||
!(
|
||||
$targetType === Note::TARGET_USERS &&
|
||||
count($userIdList) === 1 &&
|
||||
$userIdList[0] === $this->user->getId()
|
||||
) &&
|
||||
!(
|
||||
$targetType === Note::TARGET_USERS && $allTargetUsersArePortal
|
||||
)
|
||||
) {
|
||||
throw new Forbidden('Not permitted to post to anybody except self.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_TEAMS) {
|
||||
if (empty($teamIdList)) {
|
||||
throw new BadRequest("No team IDS.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_USERS) {
|
||||
if (empty($userIdList)) {
|
||||
throw new BadRequest("No user IDs.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_PORTALS) {
|
||||
if (empty($portalIdList)) {
|
||||
throw new BadRequest("No portal IDs.");
|
||||
}
|
||||
|
||||
if ($this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES) {
|
||||
throw new Forbidden('Not permitted to post to portal users.');
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$targetType === Note::TARGET_USERS &&
|
||||
$this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES
|
||||
) {
|
||||
if ($hasPortalTargetUser) {
|
||||
throw new Forbidden('Not permitted to post to portal users.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($messagePermission === AclTable::LEVEL_TEAM) {
|
||||
if ($targetType === Note::TARGET_ALL) {
|
||||
throw new Forbidden('Not permitted to post to all.');
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$messagePermission === AclTable::LEVEL_TEAM &&
|
||||
$targetType === Note::TARGET_TEAMS
|
||||
) {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to foreign teams.');
|
||||
}
|
||||
|
||||
foreach ($teamIdList as $teamId) {
|
||||
if (!in_array($teamId, $userTeamIdList)) {
|
||||
throw new Forbidden("Not permitted to post to foreign teams.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$messagePermission === AclTable::LEVEL_TEAM &&
|
||||
$targetType === Note::TARGET_USERS
|
||||
) {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inTeam = $this->getUserRepository()->checkBelongsToAnyOfTeams($user->getId(), $userTeamIdList);
|
||||
|
||||
if (!$inTeam) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getUserRepository(): UserRepository
|
||||
{
|
||||
/** @var UserRepository */
|
||||
return $this->entityManager->getRepository(User::ENTITY_TYPE);
|
||||
}
|
||||
}
|
||||
135
application/Espo/Classes/RecordHooks/Note/BeforeCreate.php
Normal file
135
application/Espo/Classes/RecordHooks/Note/BeforeCreate.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Note;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table as AclTable;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Stream\NoteUtil;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Note>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
private User $user,
|
||||
private NoteUtil $noteUtil
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->checkParent($entity);
|
||||
|
||||
if (!$entity->isPost() && !$this->user->isAdmin()) {
|
||||
throw new Forbidden("Only 'Post' type allowed.");
|
||||
}
|
||||
|
||||
if ($this->user->isPortal()) {
|
||||
$entity->set('isInternal', false);
|
||||
}
|
||||
|
||||
if ($entity->isPost()) {
|
||||
$this->noteUtil->handlePostText($entity);
|
||||
}
|
||||
|
||||
$targetType = $entity->getTargetType();
|
||||
|
||||
$entity->clear('isGlobal');
|
||||
|
||||
switch ($targetType) {
|
||||
case Note::TARGET_ALL:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
$entity->set('isGlobal', true);
|
||||
|
||||
break;
|
||||
|
||||
case Note::TARGET_SELF:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
$entity->set('usersIds', [$this->user->getId()]);
|
||||
$entity->set('isForSelf', true);
|
||||
|
||||
break;
|
||||
|
||||
case Note::TARGET_USERS:
|
||||
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
|
||||
break;
|
||||
|
||||
case Note::TARGET_TEAMS:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('portalsIds');
|
||||
|
||||
break;
|
||||
|
||||
case Note::TARGET_PORTALS:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkParent(Note $entity): void
|
||||
{
|
||||
if (!$entity->getParentType() || !$entity->getParentId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntityById($entity->getParentType(), $entity->getParentId());
|
||||
|
||||
if ($parent && $this->acl->check($parent, AclTable::ACTION_READ)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Forbidden("No access to parent.");
|
||||
}
|
||||
}
|
||||
60
application/Espo/Classes/RecordHooks/Note/BeforeUpdate.php
Normal file
60
application/Espo/Classes/RecordHooks/Note/BeforeUpdate.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Note;
|
||||
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Stream\NoteUtil;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Note>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class BeforeUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private NoteUtil $noteUtil
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($entity->isPost()) {
|
||||
$this->noteUtil->handlePostText($entity);
|
||||
}
|
||||
|
||||
if (!$entity->isPost() && !$this->user->isAdmin()) {
|
||||
throw new ForbiddenSilent("Only 'Post' type allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,45 +27,42 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\RecordHooks\Portal;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Di;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Repositories\Portal as PortalRepository;
|
||||
|
||||
/**
|
||||
* @extends Record<\Espo\Entities\PortalRole>
|
||||
* @implements SaveHook<Portal>
|
||||
*/
|
||||
class PortalRole extends Record implements
|
||||
|
||||
Di\DataManagerAware
|
||||
class AfterUpdate implements SaveHook
|
||||
{
|
||||
use Di\DataManagerSetter;
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
protected $forceSelectAllAttributes = true;
|
||||
|
||||
public function afterCreateEntity(Entity $entity, $data)
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
parent::afterCreateEntity($entity, $data);
|
||||
$this->clearRolesCache();
|
||||
}
|
||||
$this->getPortalRepository()->loadUrlField($entity);
|
||||
|
||||
public function afterUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::afterUpdateEntity($entity, $data);
|
||||
$this->clearRolesCache();
|
||||
}
|
||||
|
||||
protected function clearRolesCache(): void
|
||||
{
|
||||
$this->createAclCacheClearer()->clearForAllPortalUsers();
|
||||
if (!$entity->isAttributeChanged('portalRolesIds')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearer->clearForAllPortalUsers();
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
|
||||
private function createAclCacheClearer(): AclCacheClearer
|
||||
private function getPortalRepository(): PortalRepository
|
||||
{
|
||||
return $this->injectableFactory->create(AclCacheClearer::class);
|
||||
/** @var PortalRepository */
|
||||
return $this->entityManager->getRDBRepositoryByClass(Portal::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\PortalRole;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\PortalRole;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<PortalRole>
|
||||
*/
|
||||
class AfterSave implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->clearer->clearForAllInternalUsers();
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
53
application/Espo/Classes/RecordHooks/Role/AfterSave.php
Normal file
53
application/Espo/Classes/RecordHooks/Role/AfterSave.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Role;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Role;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Role>
|
||||
*/
|
||||
class AfterSave implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->clearer->clearForAllInternalUsers();
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
232
application/Espo/Classes/RecordHooks/Role/BeforeSaveValidate.php
Normal file
232
application/Espo/Classes/RecordHooks/Role/BeforeSaveValidate.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Role;
|
||||
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Portal\Acl\Table as TablePortal;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\PortalRole;
|
||||
use Espo\Entities\Role;
|
||||
use Espo\ORM\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
* @implements SaveHook<Role|PortalRole>
|
||||
*/
|
||||
class BeforeSaveValidate implements SaveHook
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $levelList = [
|
||||
Table::LEVEL_YES,
|
||||
Table::LEVEL_ALL,
|
||||
Table::LEVEL_TEAM,
|
||||
Table::LEVEL_OWN,
|
||||
Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private array $portalLevelList = [
|
||||
Table::LEVEL_YES,
|
||||
Table::LEVEL_ALL,
|
||||
TablePortal::LEVEL_ACCOUNT,
|
||||
TablePortal::LEVEL_CONTACT,
|
||||
Table::LEVEL_OWN,
|
||||
Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->validateData($entity);
|
||||
$this->validateFieldData($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateData(Role|PortalRole $entity): void
|
||||
{
|
||||
if ($entity->get('data') === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = get_object_vars($entity->get('data'));
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
if (!is_bool($item) && !$item instanceof stdClass) {
|
||||
throw new BadRequest("Bad data. Should be bool or object.");
|
||||
}
|
||||
|
||||
$this->validateDataItem($scope, $entity, $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateDataItem(string $scope, Role|PortalRole $entity, bool|stdClass $item): void
|
||||
{
|
||||
$key = $entity instanceof PortalRole ?
|
||||
'aclPortal' : 'acl';
|
||||
|
||||
$type = $this->metadata->get("scopes.$scope.$key");
|
||||
|
||||
if ($type === 'boolean') {
|
||||
if (!is_bool($item)) {
|
||||
throw new BadRequest("Bad data. Value for *$scope* should be be bool.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type === null) {
|
||||
throw new BadRequest("Bad data. Scope *$scope* is not allowed.");
|
||||
}
|
||||
|
||||
if ($item === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_bool($item)) {
|
||||
throw new BadRequest("Bad data. Value for *$scope* should be be false or object.");
|
||||
}
|
||||
|
||||
$actions = [
|
||||
Table::ACTION_CREATE,
|
||||
Table::ACTION_READ,
|
||||
Table::ACTION_EDIT,
|
||||
Table::ACTION_DELETE,
|
||||
Table::ACTION_STREAM,
|
||||
];
|
||||
|
||||
$levels = $entity instanceof PortalRole ?
|
||||
$this->portalLevelList : $this->levelList;
|
||||
|
||||
foreach ($actions as $action) {
|
||||
if (!property_exists($item, $action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $item->$action;
|
||||
|
||||
if (!in_array($level, $levels)) {
|
||||
throw new BadRequest("Level `$level` is not allowed for action *$action* for *$scope*.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateFieldData(Role|PortalRole $entity): void
|
||||
{
|
||||
if ($entity->get('fieldData') === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = get_object_vars($entity->get('fieldData'));
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
if (!$item instanceof stdClass) {
|
||||
throw new BadRequest("Bad field-level data. Should be object.");
|
||||
}
|
||||
|
||||
$this->validateFieldDataItem($scope, $entity, $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateFieldDataItem(string $scope, PortalRole|Role $entity, stdClass $item): void
|
||||
{
|
||||
$disabledKey = $entity instanceof PortalRole ? 'aclPortalFieldLevelDisabled' : 'aclFieldLevelDisabled';
|
||||
$key = $entity instanceof PortalRole ? 'aclPortal' : 'acl';
|
||||
|
||||
if (
|
||||
!$this->metadata->get("scopes.$scope.entity") ||
|
||||
!$this->metadata->get("scopes.$scope.$key") ||
|
||||
$this->metadata->get("scopes.$scope.$disabledKey")
|
||||
) {
|
||||
throw new BadRequest("Bad field-level data. Scope *$scope* is not allowed.");
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = get_object_vars($item);
|
||||
|
||||
foreach ($data as $field => $fieldItem) {
|
||||
if (!$fieldItem instanceof stdClass) {
|
||||
throw new BadRequest("Data for field *$field*, scope *$scope* should be object.");
|
||||
}
|
||||
|
||||
$this->validateFieldDataItemItem($scope, $field, $fieldItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateFieldDataItemItem(string $scope, string $field, stdClass $item): void
|
||||
{
|
||||
if (!$this->metadata->get("entityDefs.$scope.fields.$field")) {
|
||||
throw new BadRequest("Field *$field* does not exist in *$scope*.");
|
||||
}
|
||||
|
||||
$actions = [
|
||||
Table::ACTION_READ,
|
||||
Table::ACTION_EDIT,
|
||||
];
|
||||
|
||||
$levels = [
|
||||
Table::LEVEL_YES,
|
||||
Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
foreach ($actions as $action) {
|
||||
if (!property_exists($item, $action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $item->$action;
|
||||
|
||||
if (!in_array($level, $levels)) {
|
||||
throw new BadRequest("Level `$level` is not allowed for *$scope*, field *$field*.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
application/Espo/Classes/RecordHooks/Team/AfterUpdate.php
Normal file
58
application/Espo/Classes/RecordHooks/Team/AfterUpdate.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Team;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Team>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AfterUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if (!$entity->isAttributeChanged('rolesIds')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearer->clearForAllInternalUsers();
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Team;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\LinkHook;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements LinkHook<Team>
|
||||
*/
|
||||
class ClearCacheAfterLink implements LinkHook
|
||||
{
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, string $link, Entity $foreignEntity): void
|
||||
{
|
||||
if ($link !== 'users' || !$foreignEntity instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearer->clearForUser($foreignEntity);
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Team;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\UnlinkHook;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements UnlinkHook<Team>
|
||||
*/
|
||||
class ClearCacheAfterUnlink implements UnlinkHook
|
||||
{
|
||||
public function __construct(
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, string $link, Entity $foreignEntity): void
|
||||
{
|
||||
if ($link !== 'users' || !$foreignEntity instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearer->clearForUser($foreignEntity);
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
114
application/Espo/Classes/RecordHooks/User/AfterUpdate.php
Normal file
114
application/Espo/Classes/RecordHooks/User/AfterUpdate.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\User;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<User>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AfterUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Clearer $clearer,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->processCache($entity);
|
||||
$this->processContactName($entity);
|
||||
}
|
||||
|
||||
private function processCache(User $entity): void
|
||||
{
|
||||
if (
|
||||
$entity->isAttributeChanged('rolesIds') ||
|
||||
$entity->isAttributeChanged('teamsIds') ||
|
||||
$entity->isAttributeChanged('type') ||
|
||||
$entity->isAttributeChanged('portalRolesIds') ||
|
||||
$entity->isAttributeChanged('portalsIds')
|
||||
) {
|
||||
$this->clearer->clearForUser($entity);
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->isAttributeChanged('portalRolesIds') ||
|
||||
$entity->isAttributeChanged('portalsIds') ||
|
||||
$entity->isAttributeChanged('contactId') ||
|
||||
$entity->isAttributeChanged('accountsIds')
|
||||
) {
|
||||
$this->clearer->clearForAllPortalUsers();
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
private function processContactName(User $entity): void
|
||||
{
|
||||
if (
|
||||
!$entity->isPortal() ||
|
||||
!$entity->getContactId() ||
|
||||
!$entity->isAttributeChanged('firstName') &&
|
||||
!$entity->isAttributeChanged('lastName') &&
|
||||
!$entity->isAttributeChanged('salutationName')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = $this->entityManager->getEntityById(Contact::ENTITY_TYPE, $entity->getContactId());
|
||||
|
||||
if (!$contact) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isAttributeChanged('firstName')) {
|
||||
$contact->set('firstName', $entity->get('firstName'));
|
||||
}
|
||||
|
||||
if ($entity->isAttributeChanged('lastName')) {
|
||||
$contact->set('lastName', $entity->get('lastName'));
|
||||
}
|
||||
|
||||
if ($entity->isAttributeChanged('salutationName')) {
|
||||
$contact->set('salutationName', $entity->get('salutationName'));
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($contact);
|
||||
}
|
||||
}
|
||||
135
application/Espo/Classes/RecordHooks/User/BeforeCreate.php
Normal file
135
application/Espo/Classes/RecordHooks/User/BeforeCreate.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\User;
|
||||
|
||||
use Espo\Core\Authentication\Logins\Hmac;
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\User\UserUtil;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<User>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class BeforeCreate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private User $user,
|
||||
private UserUtil $util
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->processLimitChecking($entity);
|
||||
$this->processUserExistsChecking($entity);
|
||||
$this->processApi($entity);
|
||||
$this->processTypeChecking($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Conflict
|
||||
*/
|
||||
private function processUserExistsChecking(User $entity): void
|
||||
{
|
||||
if ($this->util->checkExists($entity)) {
|
||||
throw new Conflict('userNameExists');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processLimitChecking(User $entity): void
|
||||
{
|
||||
$userLimit = $this->config->get('userLimit');
|
||||
$portalUserLimit = $this->config->get('portalUserLimit');
|
||||
|
||||
if (
|
||||
$userLimit &&
|
||||
!$this->user->isSuperAdmin() &&
|
||||
!$entity->isPortal() && !$entity->isApi()
|
||||
) {
|
||||
$userCount = $this->util->getInternalCount();
|
||||
|
||||
if ($userCount >= $userLimit) {
|
||||
throw new Forbidden("User limit $userLimit is reached.");
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$portalUserLimit &&
|
||||
!$this->user->isSuperAdmin() &&
|
||||
$entity->isPortal()
|
||||
) {
|
||||
$portalUserCount = $this->util->getPortalCount();
|
||||
|
||||
if ($portalUserCount >= $portalUserLimit) {
|
||||
throw new Forbidden("Portal user limit $portalUserLimit is reached.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processApi(User $entity): void
|
||||
{
|
||||
if (!$entity->isApi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->set('apiKey', Util::generateApiKey());
|
||||
|
||||
if ($entity->getAuthMethod() === Hmac::NAME) {
|
||||
$secretKey = Util::generateSecretKey();
|
||||
|
||||
$entity->set('secretKey', $secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processTypeChecking(User $entity): void
|
||||
{
|
||||
if (
|
||||
$entity->isSuperAdmin() ||
|
||||
!$entity->getType() ||
|
||||
in_array($entity->getType(), $this->util->getAllowedUserTypeList())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Forbidden("Not allowed 'type'.");
|
||||
}
|
||||
}
|
||||
171
application/Espo/Classes/RecordHooks/User/BeforeUpdate.php
Normal file
171
application/Espo/Classes/RecordHooks/User/BeforeUpdate.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\User;
|
||||
|
||||
use Espo\Core\Authentication\Logins\Hmac;
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Entities\User as UserEntity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\User\UserUtil;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<User>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class BeforeUpdate implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private User $user,
|
||||
private UserUtil $util
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->processLimitChecking($entity);
|
||||
$this->processUserExistsChecking($entity);
|
||||
$this->processApi($entity);
|
||||
$this->processTypeChecking($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Conflict
|
||||
*/
|
||||
private function processUserExistsChecking(User $entity): void
|
||||
{
|
||||
if (!$entity->isAttributeChanged('userName')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->util->checkExists($entity)) {
|
||||
throw new Conflict('userNameExists');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processLimitChecking(User $entity): void
|
||||
{
|
||||
$userLimit = $this->config->get('userLimit');
|
||||
$portalUserLimit = $this->config->get('portalUserLimit');
|
||||
|
||||
if (
|
||||
$userLimit &&
|
||||
!$this->user->isSuperAdmin() &&
|
||||
(
|
||||
(
|
||||
$entity->isActive() &&
|
||||
$entity->isAttributeChanged('isActive') &&
|
||||
!$entity->isPortal() &&
|
||||
!$entity->isApi()
|
||||
) ||
|
||||
(
|
||||
!$entity->isPortal() &&
|
||||
!$entity->isApi() &&
|
||||
$entity->isAttributeChanged('type') &&
|
||||
(
|
||||
$entity->isRegular() ||
|
||||
$entity->isAdmin()
|
||||
) &&
|
||||
(
|
||||
$entity->getFetched('type') == UserEntity::TYPE_PORTAL ||
|
||||
$entity->getFetched('type') == UserEntity::TYPE_API
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
$userCount = $this->util->getInternalCount();
|
||||
|
||||
if ($userCount >= $userLimit) {
|
||||
throw new Forbidden("User limit $userLimit is reached.");
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$portalUserLimit &&
|
||||
!$this->user->isSuperAdmin() &&
|
||||
(
|
||||
(
|
||||
$entity->isActive() &&
|
||||
$entity->isAttributeChanged('isActive') &&
|
||||
$entity->isPortal()
|
||||
) ||
|
||||
(
|
||||
$entity->isPortal() &&
|
||||
$entity->isAttributeChanged('type')
|
||||
)
|
||||
)
|
||||
) {
|
||||
$portalUserCount = $this->util->getPortalCount();
|
||||
|
||||
if ($portalUserCount >= $portalUserLimit) {
|
||||
throw new Forbidden("Portal user limit $portalUserLimit is reached.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processApi(User $entity): void
|
||||
{
|
||||
if (
|
||||
!$entity->isApi() ||
|
||||
!$entity->isAttributeChanged('authMethod') ||
|
||||
$entity->getAuthMethod() !== Hmac::NAME
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$secretKey = Util::generateSecretKey();
|
||||
|
||||
$entity->set('secretKey', $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processTypeChecking(User $entity): void
|
||||
{
|
||||
if (
|
||||
$entity->isSuperAdmin() ||
|
||||
!$entity->isAttributeChanged('type') ||
|
||||
!$entity->getType() ||
|
||||
in_array($entity->getType(), $this->util->getAllowedUserTypeList())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Forbidden("Can't change type.");
|
||||
}
|
||||
}
|
||||
62
application/Espo/Classes/RecordHooks/Webhook/AfterDelete.php
Normal file
62
application/Espo/Classes/RecordHooks/Webhook/AfterDelete.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Webhook;
|
||||
|
||||
use Espo\Core\Record\DeleteParams;
|
||||
use Espo\Core\Record\Hook\DeleteHook;
|
||||
use Espo\Core\Webhook\Manager;
|
||||
use Espo\Entities\Webhook;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements DeleteHook<Webhook>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AfterDelete implements DeleteHook
|
||||
{
|
||||
public function __construct(
|
||||
private Manager $webhookManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, DeleteParams $params): void
|
||||
{
|
||||
$event = $entity->getEvent();
|
||||
|
||||
if (!$event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->webhookManager->removeEvent($event);
|
||||
}
|
||||
}
|
||||
75
application/Espo/Classes/RecordHooks/Webhook/AfterSave.php
Normal file
75
application/Espo/Classes/RecordHooks/Webhook/AfterSave.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Webhook;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Webhook\Manager;
|
||||
use Espo\Entities\Webhook;
|
||||
use Espo\ORM\Entity;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Webhook>
|
||||
*/
|
||||
class AfterSave implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private Manager $webhookManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$event = $entity->getEvent();
|
||||
|
||||
if (!$event) {
|
||||
throw new RuntimeException("No 'event'.");
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
if ($entity->isActive()) {
|
||||
$this->webhookManager->addEvent($event);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged('isActive')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isActive()) {
|
||||
$this->webhookManager->addEvent($event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->webhookManager->removeEvent($event);
|
||||
}
|
||||
}
|
||||
@@ -27,124 +27,75 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Classes\RecordHooks\Webhook;
|
||||
|
||||
use Espo\Entities\Webhook as WebhookEntity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use stdClass;
|
||||
use Espo\Entities\Webhook;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @extends Record<WebhookEntity>
|
||||
* @implements SaveHook<Webhook>
|
||||
*/
|
||||
class Webhook extends Record implements
|
||||
Di\WebhookManagerAware
|
||||
|
||||
class BeforeSave implements SaveHook
|
||||
{
|
||||
use Di\WebhookManagerSetter;
|
||||
private const WEBHOOK_MAX_COUNT_PER_USER = 50;
|
||||
|
||||
const WEBHOOK_MAX_COUNT_PER_USER = 50;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $eventTypeList = [
|
||||
/** @var string[] */
|
||||
private $eventTypeList = [
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'fieldUpdate',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $onlyAdminAttributeList = ['userId', 'userName'];
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
private Metadata $metadata,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $readOnlyAttributeList = ['secretKey'];
|
||||
|
||||
public function populateDefaults(Entity $entity, stdClass $data): void
|
||||
{
|
||||
parent::populateDefaults($entity, $data);
|
||||
|
||||
if ($this->user->isApi()) {
|
||||
$entity->set('userId', $this->user->getId());
|
||||
}
|
||||
}
|
||||
|
||||
protected function filterInput($data)
|
||||
{
|
||||
parent::filterInput($data);
|
||||
|
||||
unset($data->entityType);
|
||||
unset($data->field);
|
||||
unset($data->type);
|
||||
}
|
||||
|
||||
public function filterUpdateInput(stdClass $data): void
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
unset($data->event);
|
||||
}
|
||||
|
||||
parent::filterUpdateInput($data);
|
||||
}
|
||||
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->checkEntityUserIsApi($entity);
|
||||
$this->processEntityEventData($entity);
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
if ($entity->isNew() && !$this->user->isAdmin()) {
|
||||
$this->checkMaxCount();
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkMaxCount(): void
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkEntityUserIsApi(Webhook $entity): void
|
||||
{
|
||||
$maxCount = $this->config->get('webhookMaxCountPerUser', self::WEBHOOK_MAX_COUNT_PER_USER);
|
||||
|
||||
$count = $this->entityManager
|
||||
->getRDBRepository(WebhookEntity::ENTITY_TYPE)
|
||||
->where([
|
||||
'userId' => $this->user->getId(),
|
||||
])
|
||||
->count();
|
||||
|
||||
if ($maxCount && $count >= $maxCount) {
|
||||
throw new Forbidden("Webhook number per user exceeded the limit.");
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
$this->checkEntityUserIsApi($entity);
|
||||
$this->processEntityEventData($entity);
|
||||
}
|
||||
|
||||
protected function checkEntityUserIsApi(Entity $entity): void
|
||||
{
|
||||
$userId = $entity->get('userId');
|
||||
$userId = $entity->getUserId();
|
||||
|
||||
if (!$userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $this->entityManager->getEntity(User::ENTITY_TYPE, $userId);
|
||||
$user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId);
|
||||
|
||||
if (!$user || !$user->isApi()) {
|
||||
throw new Forbidden("User must be an API User.");
|
||||
if ($user && $user->isApi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Forbidden("User must be an API User.");
|
||||
}
|
||||
|
||||
protected function processEntityEventData(Entity $entity): void
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processEntityEventData(Webhook $entity): void
|
||||
{
|
||||
$event = $entity->get('event');
|
||||
|
||||
@@ -152,10 +103,8 @@ class Webhook extends Record implements
|
||||
throw new Forbidden("Event is empty.");
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->isAttributeChanged('event')) {
|
||||
throw new Forbidden("Event can't be changed.");
|
||||
}
|
||||
if (!$entity->isNew() && $entity->isAttributeChanged('event')) {
|
||||
throw new Forbidden("Event can't be changed.");
|
||||
}
|
||||
|
||||
$arr = explode('.', $event);
|
||||
@@ -164,7 +113,6 @@ class Webhook extends Record implements
|
||||
throw new Forbidden("Not supported event.");
|
||||
}
|
||||
|
||||
$arr = explode('.', $event);
|
||||
$entityType = $arr[0];
|
||||
$type = $arr[1];
|
||||
|
||||
@@ -185,7 +133,7 @@ class Webhook extends Record implements
|
||||
throw new Forbidden("Not existing Entity Type.");
|
||||
}
|
||||
|
||||
if (!$this->acl->checkScope($entityType, 'read')) {
|
||||
if (!$this->acl->checkScope($entityType, Acl\Table::ACTION_READ)) {
|
||||
throw new Forbidden("Entity type is forbidden.");
|
||||
}
|
||||
|
||||
@@ -204,43 +152,35 @@ class Webhook extends Record implements
|
||||
throw new Forbidden("Field is empty.");
|
||||
}
|
||||
|
||||
$forbiddenFieldList = $this->getAcl()->getScopeForbiddenFieldList($entityType);
|
||||
|
||||
if (in_array($field, $forbiddenFieldList)) {
|
||||
if (!$this->acl->checkField($entityType, $field)) {
|
||||
throw new Forbidden("Field is forbidden.");
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['entityDefs', $entityType, 'fields', $field])) {
|
||||
throw new Forbidden("Field does not exist.");
|
||||
}
|
||||
} else {
|
||||
$entity->set('field', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$entity->set('field', null);
|
||||
}
|
||||
|
||||
protected function afterCreateEntity(Entity $entity, $data)
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkMaxCount(): void
|
||||
{
|
||||
if ($entity->get('isActive')) {
|
||||
$this->webhookManager->addEvent($entity->get('event'));
|
||||
}
|
||||
}
|
||||
$maxCount = $this->config->get('webhookMaxCountPerUser', self::WEBHOOK_MAX_COUNT_PER_USER);
|
||||
|
||||
protected function afterDeleteEntity(Entity $entity)
|
||||
{
|
||||
if ($entity->get('isActive')) {
|
||||
$this->webhookManager->removeEvent($entity->get('event'));
|
||||
}
|
||||
}
|
||||
$count = $this->entityManager
|
||||
->getRDBRepositoryByClass(Webhook::class)
|
||||
->where(['userId' => $this->user->getId()])
|
||||
->count();
|
||||
|
||||
protected function afterUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
if (isset($data->isActive)) {
|
||||
if ($entity->get('isActive')) {
|
||||
$this->webhookManager->addEvent($entity->get('event'));
|
||||
}
|
||||
else {
|
||||
$this->webhookManager->removeEvent($entity->get('event'));
|
||||
}
|
||||
if ($maxCount && $count >= $maxCount) {
|
||||
throw new Forbidden("Webhook number per user exceeded the limit.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
namespace Espo\Classes\Select\Attachment\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Settings;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
class Orphan implements Filter
|
||||
@@ -37,32 +39,35 @@ class Orphan implements Filter
|
||||
public function apply(SelectBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'role' => ['Attachment', 'Inline Attachment'],
|
||||
'role' => [
|
||||
Attachment::ROLE_ATTACHMENT,
|
||||
Attachment::ROLE_INLINE_ATTACHMENT,
|
||||
],
|
||||
[
|
||||
'OR' => [
|
||||
[
|
||||
'parentId' => null,
|
||||
'parentType!=' => null,
|
||||
'relatedType=' => null,
|
||||
'parentId' => null,
|
||||
'relatedType' => null,
|
||||
],
|
||||
[
|
||||
'parentType' => null,
|
||||
'relatedId' => null,
|
||||
'relatedType!=' => null,
|
||||
'relatedId' => null,
|
||||
'parentType' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'OR' => [
|
||||
'relatedType!=' => 'Settings',
|
||||
'relatedType=' => null,
|
||||
'relatedType!=' => Settings::ENTITY_TYPE,
|
||||
'relatedType' => null,
|
||||
],
|
||||
],
|
||||
'attachmentChild.id' => null,
|
||||
]);
|
||||
|
||||
$queryBuilder->leftJoin(
|
||||
'Attachment',
|
||||
Attachment::ENTITY_TYPE,
|
||||
'attachmentChild',
|
||||
[
|
||||
'attachmentChild.sourceId:' => 'attachment.id',
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Classes\Select\Email\AdditionalAppliers;
|
||||
|
||||
use Espo\Core\Select\Applier\AdditionalApplier;
|
||||
use Espo\Core\Select\Primary\Filters\One;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Classes\Select\Email\Helpers\JoinHelper;
|
||||
use Espo\Entities\Email;
|
||||
@@ -57,6 +58,10 @@ class Main implements AdditionalApplier
|
||||
|
||||
private function applyIndexes(?string $folder, SelectBuilder $queryBuilder, SearchParams $searchParams): void
|
||||
{
|
||||
if ($searchParams->getPrimaryFilter() === One::NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($searchParams->getTextFilter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,6 @@ class GoogleMaps implements Helper
|
||||
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($c, \CURLOPT_BINARYTRANSFER, 1);
|
||||
|
||||
$raw = curl_exec($c);
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@ use Espo\Tools\ExportCustom\Service as ExportCustomService;
|
||||
use Espo\Tools\LinkManager\LinkManager;
|
||||
use stdClass;
|
||||
|
||||
use const FILTER_SANITIZE_STRING;
|
||||
|
||||
class EntityManager
|
||||
{
|
||||
/**
|
||||
@@ -66,7 +64,7 @@ class EntityManager
|
||||
* @throws Error
|
||||
* @throws Conflict
|
||||
*/
|
||||
public function postActionCreateEntity(Request $request): bool
|
||||
public function postActionCreateEntity(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
@@ -79,9 +77,6 @@ class EntityManager
|
||||
$name = $data['name'];
|
||||
$type = $data['type'];
|
||||
|
||||
$name = filter_var($name, FILTER_SANITIZE_STRING);
|
||||
$type = filter_var($type, FILTER_SANITIZE_STRING);
|
||||
|
||||
if (!is_string($name) || !is_string($type)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
@@ -142,9 +137,9 @@ class EntityManager
|
||||
$params['kanbanStatusIgnoreList'] = $data['kanbanStatusIgnoreList'];
|
||||
}
|
||||
|
||||
$this->entityManagerTool->create($name, $type, $params);
|
||||
$name = $this->entityManagerTool->create($name, $type, $params);
|
||||
|
||||
return true;
|
||||
return (object) ['name' => $name];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,8 +158,6 @@ class EntityManager
|
||||
|
||||
$name = $data['name'];
|
||||
|
||||
$name = filter_var($name, FILTER_SANITIZE_STRING);
|
||||
|
||||
if (!is_string($name)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
@@ -191,8 +184,6 @@ class EntityManager
|
||||
|
||||
$name = $data['name'];
|
||||
|
||||
$name = filter_var($name, FILTER_SANITIZE_STRING);
|
||||
|
||||
if (!is_string($name)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
@@ -234,11 +225,15 @@ class EntityManager
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
|
||||
$params[$item] = htmlspecialchars($data[$item]);
|
||||
}
|
||||
|
||||
foreach ($additionalParamList as $item) {
|
||||
$params[$item] = filter_var($data[$item] ?? null, FILTER_SANITIZE_STRING);
|
||||
$params[$item] = $data[$item];
|
||||
|
||||
if (is_string($params[$item])) {
|
||||
$params[$item] = htmlspecialchars($params[$item]);
|
||||
}
|
||||
}
|
||||
|
||||
$params['labelForeign'] = $params['labelForeign'] ?? $params['linkForeign'];
|
||||
@@ -275,21 +270,31 @@ class EntityManager
|
||||
$params['layoutForeign'] = $data['layoutForeign'];
|
||||
}
|
||||
|
||||
if (array_key_exists('selectFilter', $data)) {
|
||||
$params['selectFilter'] = $data['selectFilter'];
|
||||
}
|
||||
|
||||
if (array_key_exists('selectFilterForeign', $data)) {
|
||||
$params['selectFilterForeign'] = $data['selectFilterForeign'];
|
||||
}
|
||||
|
||||
/** @var array{
|
||||
* linkType: string,
|
||||
* entity: string,
|
||||
* link: string,
|
||||
* entityForeign: string,
|
||||
* linkForeign: string,
|
||||
* label: string,
|
||||
* labelForeign: string,
|
||||
* relationName?: ?string,
|
||||
* linkMultipleField?: bool,
|
||||
* linkMultipleFieldForeign?: bool,
|
||||
* audited?: bool,
|
||||
* auditedForeign?: bool,
|
||||
* layout?: string,
|
||||
* layoutForeign?: string,
|
||||
* linkType: string,
|
||||
* entity: string,
|
||||
* link: string,
|
||||
* entityForeign: string,
|
||||
* linkForeign: string,
|
||||
* label: string,
|
||||
* labelForeign: string,
|
||||
* relationName?: ?string,
|
||||
* linkMultipleField?: bool,
|
||||
* linkMultipleFieldForeign?: bool,
|
||||
* audited?: bool,
|
||||
* auditedForeign?: bool,
|
||||
* layout?: string,
|
||||
* layoutForeign?: string,
|
||||
* selectFilter?: string,
|
||||
* selectFilterForeign?: string,
|
||||
* } $params
|
||||
*/
|
||||
|
||||
@@ -321,7 +326,7 @@ class EntityManager
|
||||
|
||||
foreach ($paramList as $item) {
|
||||
if (array_key_exists($item, $data)) {
|
||||
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
|
||||
$params[$item] = htmlspecialchars($data[$item]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,26 +357,36 @@ class EntityManager
|
||||
$params['layout'] = $data['layout'];
|
||||
}
|
||||
|
||||
if (array_key_exists('auditedForeign', $data)) {
|
||||
if (array_key_exists('layoutForeign', $data)) {
|
||||
$params['layoutForeign'] = $data['layoutForeign'];
|
||||
}
|
||||
|
||||
if (array_key_exists('selectFilter', $data)) {
|
||||
$params['selectFilter'] = $data['selectFilter'];
|
||||
}
|
||||
|
||||
if (array_key_exists('selectFilterForeign', $data)) {
|
||||
$params['selectFilterForeign'] = $data['selectFilterForeign'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array{
|
||||
* entity: string,
|
||||
* link: string,
|
||||
* entityForeign?: ?string,
|
||||
* linkForeign?: ?string,
|
||||
* label?: string,
|
||||
* labelForeign?: string,
|
||||
* linkMultipleField?: bool,
|
||||
* linkMultipleFieldForeign?: bool,
|
||||
* audited?: bool,
|
||||
* auditedForeign?: bool,
|
||||
* parentEntityTypeList?: string[],
|
||||
* foreignLinkEntityTypeList?: string[],
|
||||
* layout?: string,
|
||||
* layoutForeign?: string,
|
||||
* entity: string,
|
||||
* link: string,
|
||||
* entityForeign?: ?string,
|
||||
* linkForeign?: ?string,
|
||||
* label?: string,
|
||||
* labelForeign?: string,
|
||||
* linkMultipleField?: bool,
|
||||
* linkMultipleFieldForeign?: bool,
|
||||
* audited?: bool,
|
||||
* auditedForeign?: bool,
|
||||
* parentEntityTypeList?: string[],
|
||||
* foreignLinkEntityTypeList?: string[],
|
||||
* layout?: string,
|
||||
* layoutForeign?: string,
|
||||
* selectFilter?: string,
|
||||
* selectFilterForeign?: string,
|
||||
* } $params
|
||||
*/
|
||||
|
||||
@@ -398,7 +413,7 @@ class EntityManager
|
||||
$params = [];
|
||||
|
||||
foreach ($paramList as $item) {
|
||||
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
|
||||
$params[$item] = htmlspecialchars($data[$item]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\FieldManager\FieldManager as FieldManagerTool;
|
||||
use Espo\Core\Api\Request;
|
||||
@@ -38,6 +39,9 @@ use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FieldManager
|
||||
{
|
||||
/**
|
||||
@@ -46,7 +50,8 @@ class FieldManager
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private DataManager $dataManager,
|
||||
private FieldManagerTool $fieldManagerTool
|
||||
private FieldManagerTool $fieldManagerTool,
|
||||
private Config $config
|
||||
) {
|
||||
$this->checkControllerAccess();
|
||||
}
|
||||
@@ -97,18 +102,18 @@ class FieldManager
|
||||
|
||||
$fieldManagerTool = $this->fieldManagerTool;
|
||||
|
||||
$fieldManagerTool->create($scope, $name, get_object_vars($data));
|
||||
$name = $fieldManagerTool->create($scope, $name, get_object_vars($data));
|
||||
|
||||
try {
|
||||
$this->dataManager->rebuild([$scope]);
|
||||
$this->rebuild($scope);
|
||||
}
|
||||
catch (Error $e) {
|
||||
$fieldManagerTool->delete($scope, $data->name);
|
||||
$fieldManagerTool->delete($scope, $name);
|
||||
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
|
||||
return $fieldManagerTool->read($scope, $data->name);
|
||||
return $fieldManagerTool->read($scope, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +147,7 @@ class FieldManager
|
||||
$fieldManagerTool->update($scope, $name, get_object_vars($data));
|
||||
|
||||
if ($fieldManagerTool->isChanged()) {
|
||||
$this->dataManager->rebuild([$scope]);
|
||||
$this->rebuild($scope);
|
||||
} else {
|
||||
$this->dataManager->clearCache();
|
||||
}
|
||||
@@ -163,12 +168,12 @@ class FieldManager
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$result = $this->fieldManagerTool->delete($scope, $name);
|
||||
$this->fieldManagerTool->delete($scope, $name);
|
||||
|
||||
$this->dataManager->clearCache();
|
||||
$this->dataManager->rebuildMetadata();
|
||||
|
||||
return $result;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,8 +197,18 @@ class FieldManager
|
||||
|
||||
$this->fieldManagerTool->resetToDefault($scope, $name);
|
||||
|
||||
$this->dataManager->rebuild([$scope]);
|
||||
$this->rebuild($scope);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function rebuild(string $scope): void
|
||||
{
|
||||
$argument = $this->config->get('database.platform') === 'Postgresql' ? null : [$scope];
|
||||
|
||||
$this->dataManager->rebuild($argument);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
@@ -97,7 +95,7 @@ class LeadCapture extends Record
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws NotFound
|
||||
* @throws ForbiddenSilent
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function postActionGenerateNewApiKey(Request $request): stdClass
|
||||
{
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\Metadata as MetadataUtil;
|
||||
use Espo\Entities\User as UserEntity;
|
||||
use Espo\Tools\App\MetadataService as Service;
|
||||
@@ -53,16 +55,21 @@ class Metadata
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getActionRead(): stdClass
|
||||
public function getActionRead(Request $request): mixed
|
||||
{
|
||||
$key = $request->getQueryParam('key');
|
||||
|
||||
if (is_string($key)) {
|
||||
return $this->service->getDataForFrontendByKey($key);
|
||||
}
|
||||
|
||||
return $this->service->getDataForFrontend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function getActionGet(Request $request)
|
||||
public function getActionGet(Request $request, Response $response): void
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
@@ -70,6 +77,8 @@ class Metadata
|
||||
|
||||
$key = $request->getQueryParam('key');
|
||||
|
||||
return $this->metadata->get($key, false);
|
||||
$value = $this->metadata->get($key);
|
||||
|
||||
$response->writeBody(Json::encode($value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,22 +41,18 @@ use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\Entities\User as UserEntity;
|
||||
use Espo\Tools\Stream\RecordService;
|
||||
|
||||
use Espo\Tools\Stream\UserRecordService;
|
||||
use stdClass;
|
||||
|
||||
class Stream
|
||||
{
|
||||
public static string $defaultAction = 'list';
|
||||
|
||||
private RecordService $service;
|
||||
private SearchParamsFetcher $searchParamsFetcher;
|
||||
|
||||
public function __construct(
|
||||
RecordService $service,
|
||||
SearchParamsFetcher $searchParamsFetcher
|
||||
) {
|
||||
$this->service = $service;
|
||||
$this->searchParamsFetcher = $searchParamsFetcher;
|
||||
}
|
||||
private RecordService $service,
|
||||
private UserRecordService $userRecordService,
|
||||
private SearchParamsFetcher $searchParamsFetcher
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
@@ -79,7 +75,7 @@ class Stream
|
||||
$searchParams = $this->fetchSearchParams($request);
|
||||
|
||||
$result = $scope === UserEntity::ENTITY_TYPE ?
|
||||
$this->service->findUser($id, $searchParams) :
|
||||
$this->userRecordService->find($id, $searchParams) :
|
||||
$this->service->find($scope, $id ?? '', $searchParams);
|
||||
|
||||
return (object) [
|
||||
@@ -110,7 +106,7 @@ class Stream
|
||||
->withPrimaryFilter('posts');
|
||||
|
||||
$result = $scope === UserEntity::ENTITY_TYPE ?
|
||||
$this->service->findUser($id, $searchParams) :
|
||||
$this->userRecordService->find($id, $searchParams) :
|
||||
$this->service->find($scope, $id ?? '', $searchParams);
|
||||
|
||||
return (object) [
|
||||
@@ -119,6 +115,30 @@ class Stream
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getActionListUpdates(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getRouteParam('id');
|
||||
$scope = $request->getRouteParam('scope');
|
||||
|
||||
if ($scope === null || $id === null) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$searchParams = $this->fetchSearchParams($request);
|
||||
|
||||
$result = $this->service->findUpdates($scope, $id, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $result->getTotal(),
|
||||
'list' => $result->getValueMapList(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
@@ -150,6 +170,20 @@ class Stream
|
||||
$searchParams = $searchParams->withBoolFilterAdded('skipOwn');
|
||||
}
|
||||
|
||||
$beforeNumber = $request->getQueryParam('beforeNumber');
|
||||
|
||||
if ($beforeNumber) {
|
||||
$searchParams = $searchParams
|
||||
->withWhereAdded(
|
||||
WhereItem
|
||||
::createBuilder()
|
||||
->setAttribute('number')
|
||||
->setType(WhereItem\Type::LESS_THAN)
|
||||
->setValue($beforeNumber)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
return $searchParams;
|
||||
}
|
||||
}
|
||||
|
||||
61
application/Espo/Controllers/WebhookQueueItem.php
Normal file
61
application/Espo/Controllers/WebhookQueueItem.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Controllers\RecordBase;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class WebhookQueueItem extends RecordBase
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postActionCreate(Request $request, Response $response): stdClass
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
public function putActionUpdate(Request $request, Response $response): stdClass
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ class Acl
|
||||
* Get an access level for a specific scope and action.
|
||||
*
|
||||
* @param Table::ACTION_* $action
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getLevel(string $scope, string $action): string
|
||||
{
|
||||
@@ -158,6 +159,7 @@ class Acl
|
||||
*/
|
||||
public function checkEntityRead(Entity $entity): bool
|
||||
{
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
return $this->checkEntity($entity, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
@@ -215,6 +217,7 @@ class Acl
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope,
|
||||
@@ -232,6 +235,7 @@ class Acl
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope,
|
||||
@@ -250,6 +254,7 @@ class Acl
|
||||
* @param string $field A field to check.
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @return bool
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function checkField(string $scope, string $field, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
@@ -262,6 +267,7 @@ class Acl
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
string $scope,
|
||||
@@ -354,6 +360,7 @@ class Acl
|
||||
*/
|
||||
public function checkUser(string $permission, User $entity): bool
|
||||
{
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
return $this->aclManager->checkUser($this->user, $permission, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ namespace Espo\Core;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Acl\AccessChecker;
|
||||
use Espo\Core\Acl\AccessChecker\AccessCheckerFactory;
|
||||
use Espo\Core\Acl\AccessCreateChecker;
|
||||
@@ -184,6 +182,7 @@ class AclManager
|
||||
* Get an access level for a specific scope and action.
|
||||
*
|
||||
* @param Table::ACTION_* $action
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
{
|
||||
@@ -201,7 +200,7 @@ class AclManager
|
||||
*/
|
||||
public function getPermissionLevel(User $user, string $permission): string
|
||||
{
|
||||
if (substr($permission, -10) === 'Permission') {
|
||||
if (str_ends_with($permission, 'Permission')) {
|
||||
$permission = substr($permission, 0, -10);
|
||||
}
|
||||
|
||||
@@ -280,7 +279,7 @@ class AclManager
|
||||
try {
|
||||
return $this->check($user, $subject, $action);
|
||||
}
|
||||
catch (NotImplemented $e) {
|
||||
catch (NotImplemented) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +323,7 @@ class AclManager
|
||||
return $checker->checkEntity($user, $entity, $data, $action);
|
||||
}
|
||||
|
||||
throw new NotImplemented("No entity access checker for '{$scope}' action '{$action}'.");
|
||||
throw new NotImplemented("No entity access checker for '$scope' action '$action'.");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,6 +333,7 @@ class AclManager
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity): bool
|
||||
{
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
return $this->checkEntity($user, $entity, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ class AclManager
|
||||
$methodName = 'checkScope';
|
||||
|
||||
if (!method_exists($checker, $methodName)) {
|
||||
throw new NotImplemented("No access checker for '{$scope}' action '{$action}'.");
|
||||
throw new NotImplemented("No access checker for '$scope' action '$action'.");
|
||||
}
|
||||
|
||||
return $checker->$methodName($user, $data, $action);
|
||||
@@ -476,6 +476,7 @@ class AclManager
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
User $user,
|
||||
@@ -505,6 +506,7 @@ class AclManager
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
User $user,
|
||||
@@ -534,6 +536,8 @@ class AclManager
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return string[]
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
User $user,
|
||||
@@ -556,6 +560,7 @@ class AclManager
|
||||
* @param string $field A field to check.
|
||||
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
|
||||
* @return bool
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function checkField(User $user, string $scope, string $field, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
@@ -591,7 +596,6 @@ class AclManager
|
||||
}
|
||||
|
||||
if ($permission === Table::LEVEL_TEAM) {
|
||||
/** @var string[] $teamIdList */
|
||||
$teamIdList = $user->getLinkMultipleIdList('teams');
|
||||
|
||||
/** @var \Espo\Repositories\User $userRepository */
|
||||
@@ -622,10 +626,8 @@ class AclManager
|
||||
{
|
||||
$className = $this->userAclClassName;
|
||||
|
||||
$acl = new $className($this, $user);
|
||||
|
||||
/** @var Acl */
|
||||
return $acl;
|
||||
return new $className($this, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -636,48 +638,40 @@ class AclManager
|
||||
*/
|
||||
public function getScopeRestrictedFieldList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
$typeList = !is_array($type) ? [$type] : $type;
|
||||
|
||||
$list = [];
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedFieldList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique($list);
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedFieldList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->globalRestriction->getScopeRestrictedFieldList($scope, $type);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a restricted attribute list for a specific scope by a restriction type.
|
||||
*
|
||||
* @param GlobalRestriction::TYPE_*|array<int,GlobalRestriction::TYPE_*> $type
|
||||
* @param GlobalRestriction::TYPE_*|array<int, GlobalRestriction::TYPE_*> $type
|
||||
* @return string[]
|
||||
*/
|
||||
public function getScopeRestrictedAttributeList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
$typeList = !is_array($type) ? [$type] : $type;
|
||||
|
||||
$list = [];
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedAttributeList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique($list);
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedAttributeList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->globalRestriction->getScopeRestrictedAttributeList($scope, $type);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -688,22 +682,18 @@ class AclManager
|
||||
*/
|
||||
public function getScopeRestrictedLinkList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
$typeList = !is_array($type) ? [$type] : $type;
|
||||
|
||||
$list = [];
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedLinkList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique($list);
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestriction->getScopeRestrictedLinkList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->globalRestriction->getScopeRestrictedLinkList($scope, $type);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -733,6 +723,7 @@ class AclManager
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Access checkers not to be exposed.
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getImplementation(string $scope): object
|
||||
{
|
||||
@@ -764,7 +755,7 @@ class AclManager
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->get($user, $permission) === Table::LEVEL_TEAM) {
|
||||
if ($this->getPermissionLevel($user, $permission) === Table::LEVEL_TEAM) {
|
||||
if ($target->getId() === $user->getId()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Core\Api;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
@@ -59,6 +60,7 @@ class ActionHandler implements RequestHandlerInterface
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws Conflict
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
@@ -93,6 +95,7 @@ class ActionHandler implements RequestHandlerInterface
|
||||
|
||||
$response->setHeader('X-App-Timestamp', (string) ($this->config->get('appTimestamp') ?? '0'));
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
return $response instanceof ResponseWrapper ?
|
||||
$response->toPsr7() :
|
||||
self::responseToPsr7($response);
|
||||
|
||||
@@ -33,7 +33,6 @@ use Espo\Core\InjectableFactory;
|
||||
|
||||
class AuthBuilderFactory
|
||||
{
|
||||
|
||||
public function __construct(private InjectableFactory $injectableFactory)
|
||||
{}
|
||||
|
||||
|
||||
@@ -156,7 +156,6 @@ class ControllerActionProcessor
|
||||
$type = $params[0]->getType();
|
||||
|
||||
if (
|
||||
!$type ||
|
||||
!$type instanceof ReflectionNamedType ||
|
||||
$type->isBuiltin()
|
||||
) {
|
||||
|
||||
@@ -90,7 +90,7 @@ class ErrorOutput
|
||||
?string $route = null
|
||||
): void {
|
||||
|
||||
$this->processInternal($request, $response, $exception, $route, false);
|
||||
$this->processInternal($request, $response, $exception, $route);
|
||||
}
|
||||
|
||||
public function processWithBodyPrinting(
|
||||
@@ -133,7 +133,7 @@ class ErrorOutput
|
||||
$logMessageItemList = [];
|
||||
|
||||
if ($message) {
|
||||
$logMessageItemList[] = "{$message}";
|
||||
$logMessageItemList[] = "$message";
|
||||
}
|
||||
|
||||
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Espo\Core\Api;
|
||||
use Espo\Core\Api\Request as ApiRequest;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
use Slim\Psr7\Factory\UriFactory;
|
||||
|
||||
use stdClass;
|
||||
@@ -49,6 +48,7 @@ class RequestNull implements ApiRequest
|
||||
|
||||
/**
|
||||
* @return null
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getQueryParam(string $name): ?string
|
||||
{
|
||||
|
||||
@@ -275,6 +275,7 @@ class RequestWrapper implements ApiRequest
|
||||
return $this->getMethod() === 'PUT';
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function isUpdate(): bool
|
||||
{
|
||||
return $this->getMethod() === 'UPDATE';
|
||||
|
||||
@@ -202,7 +202,7 @@ class RouteProcessor
|
||||
$action = $crudMethodActionMap[strtolower($method)] ?? null;
|
||||
|
||||
if (!$action) {
|
||||
throw new BadRequest("No action for method `{$method}`.");
|
||||
throw new BadRequest("No action for method `$method`.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class RunnerRunner
|
||||
public function run(string $className, ?Params $params = null): void
|
||||
{
|
||||
if (!class_exists($className)) {
|
||||
$this->log->error("Application runner '{$className}' does not exist.");
|
||||
$this->log->error("Application runner '$className' does not exist.");
|
||||
|
||||
throw new RunnerException();
|
||||
}
|
||||
|
||||
@@ -38,14 +38,8 @@ use Espo\Core\Utils\Config;
|
||||
*/
|
||||
class Client implements Runner
|
||||
{
|
||||
private ClientManager $clientManager;
|
||||
private Config $config;
|
||||
|
||||
public function __construct(ClientManager $clientManager, Config $config)
|
||||
{
|
||||
$this->clientManager = $clientManager;
|
||||
$this->config = $config;
|
||||
}
|
||||
public function __construct(private ClientManager $clientManager, private Config $config)
|
||||
{}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
|
||||
@@ -60,6 +60,9 @@ class PortalClient implements RunnerParameterized
|
||||
private ErrorOutput $errorOutput
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function run(Params $params): void
|
||||
{
|
||||
$id = $params->get('id') ??
|
||||
|
||||
@@ -43,6 +43,9 @@ class Preload implements Runner
|
||||
{
|
||||
use Cli;
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$preload = new PreloadUtil();
|
||||
@@ -59,7 +62,7 @@ class Preload implements Runner
|
||||
$count = $preload->getCount();
|
||||
|
||||
echo "Success." . PHP_EOL;
|
||||
echo "Files loaded: " . (string) $count . "." . PHP_EOL;
|
||||
echo "Files loaded: " . $count . "." . PHP_EOL;
|
||||
}
|
||||
|
||||
protected function processException(Throwable $e): void
|
||||
@@ -69,13 +72,13 @@ class Preload implements Runner
|
||||
$msg = $e->getMessage();
|
||||
|
||||
if ($msg) {
|
||||
echo "Message: {$msg}" . PHP_EOL;
|
||||
echo "Message: $msg" . PHP_EOL;
|
||||
}
|
||||
|
||||
$file = $e->getFile();
|
||||
|
||||
if ($file) {
|
||||
echo "File: {$file}" . PHP_EOL;
|
||||
echo "File: $file" . PHP_EOL;
|
||||
}
|
||||
|
||||
echo "Line: " . $e->getLine() . PHP_EOL;
|
||||
|
||||
@@ -41,6 +41,8 @@ use RuntimeException;
|
||||
* in another storage. E.g. a single Redis data store can be utilized with
|
||||
* multiple Espo replicas (for scalability purposes).
|
||||
* Defined at metadata > app > containerServices > authTokenManager.
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EspoManager implements Manager
|
||||
{
|
||||
@@ -56,8 +58,7 @@ class EspoManager implements Manager
|
||||
|
||||
public function get(string $token): ?AuthToken
|
||||
{
|
||||
/** @var ?AuthTokenEntity $authToken */
|
||||
$authToken = $this->repository
|
||||
return $this->repository
|
||||
->select([
|
||||
'id',
|
||||
'isActive',
|
||||
@@ -72,13 +73,10 @@ class EspoManager implements Manager
|
||||
])
|
||||
->where(['token' => $token])
|
||||
->findOne();
|
||||
|
||||
return $authToken;
|
||||
}
|
||||
|
||||
public function create(Data $data): AuthToken
|
||||
{
|
||||
/** @var AuthTokenEntity $authToken */
|
||||
$authToken = $this->repository->getNew();
|
||||
|
||||
$authToken
|
||||
@@ -102,6 +100,7 @@ class EspoManager implements Manager
|
||||
|
||||
public function inactivate(AuthToken $authToken): void
|
||||
{
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$authToken instanceof AuthTokenEntity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
@@ -115,6 +114,7 @@ class EspoManager implements Manager
|
||||
|
||||
public function renew(AuthToken $authToken): void
|
||||
{
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$authToken instanceof AuthTokenEntity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
@@ -159,11 +159,11 @@ class EspoManager implements Manager
|
||||
$length = self::TOKEN_RANDOM_LENGTH;
|
||||
|
||||
if (function_exists('random_bytes')) {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
return bin2hex(random_bytes($length));
|
||||
}
|
||||
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
/** @var string $randomValue */
|
||||
$randomValue = openssl_random_pseudo_bytes($length);
|
||||
|
||||
return bin2hex($randomValue);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Espo\Core\Authentication\AuthToken;
|
||||
interface Manager
|
||||
{
|
||||
/**
|
||||
* Get an auth token. If does not exist then returns NULL.
|
||||
* Get an auth token. If it does not exist, then returns NULL.
|
||||
*/
|
||||
public function get(string $token): ?AuthToken;
|
||||
|
||||
|
||||
@@ -34,20 +34,13 @@ namespace Espo\Core\Authentication;
|
||||
*/
|
||||
class AuthenticationData
|
||||
{
|
||||
private ?string $username;
|
||||
private ?string $password;
|
||||
private ?string $method;
|
||||
private bool $byTokenOnly = false;
|
||||
|
||||
public function __construct(
|
||||
?string $username = null,
|
||||
?string $password = null,
|
||||
?string $method = null
|
||||
) {
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->method = $method;
|
||||
}
|
||||
private ?string $username = null,
|
||||
private ?string $password = null,
|
||||
private ?string $method = null
|
||||
) {}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
|
||||
@@ -34,21 +34,13 @@ use Espo\Core\Authentication\Logins\Espo;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ConfigDataProvider
|
||||
{
|
||||
private const FAILED_ATTEMPTS_PERIOD = '60 seconds';
|
||||
private const MAX_FAILED_ATTEMPT_NUMBER = 10;
|
||||
|
||||
private Config $config;
|
||||
private Metadata $metadata;
|
||||
|
||||
public function __construct(Config $config, Metadata $metadata)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
public function __construct(private Config $config, private Metadata $metadata)
|
||||
{}
|
||||
|
||||
/**
|
||||
* A period for max failed attempts checking.
|
||||
@@ -155,16 +147,4 @@ class ConfigDataProvider
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function getMethodLoginMetadataParams(string $method): MetadataParams
|
||||
{
|
||||
/** @var ?array<string, mixed> $data */
|
||||
$data = $this->metadata->get(['authenticationMethods', $method]);
|
||||
|
||||
if ($data === null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return MetadataParams::fromRaw($method, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,60 +32,46 @@ namespace Espo\Core\Authentication\Helper;
|
||||
use Espo\Core\Authentication\Logins\ApiKey;
|
||||
use Espo\Core\Authentication\Logins\Hmac;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class UserFinder
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
public function __construct(private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function find(string $username, string $hash): ?User
|
||||
{
|
||||
/** @var ?User $user */
|
||||
$user = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(User::class)
|
||||
->where([
|
||||
'userName' => $username,
|
||||
'password' => $hash,
|
||||
'type!=' => [User::TYPE_API, User::TYPE_SYSTEM],
|
||||
])
|
||||
->findOne();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function findApiHmac(string $apiKey): ?User
|
||||
{
|
||||
/** @var ?User $user */
|
||||
$user = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(User::class)
|
||||
->where([
|
||||
'type' => User::TYPE_API,
|
||||
'apiKey' => $apiKey,
|
||||
'authMethod' => Hmac::NAME,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function findApiApiKey(string $apiKey): ?User
|
||||
{
|
||||
/** @var ?User $user */
|
||||
$user = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(User::class)
|
||||
->where([
|
||||
'type' => User::TYPE_API,
|
||||
'apiKey' => $apiKey,
|
||||
'authMethod' => ApiKey::NAME,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,12 @@ use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\AuthLogRecord;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class FailedAttemptsLimit implements BeforeLogin
|
||||
{
|
||||
public function __construct(
|
||||
@@ -70,7 +75,12 @@ class FailedAttemptsLimit implements BeforeLogin
|
||||
|
||||
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
|
||||
|
||||
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
|
||||
try {
|
||||
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
|
||||
$ip = $this->util->obtainIpFromRequest($request);
|
||||
|
||||
@@ -99,7 +109,7 @@ class FailedAttemptsLimit implements BeforeLogin
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '{$ip}'.");
|
||||
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '$ip'.");
|
||||
|
||||
throw new Forbidden("Max failed login attempts exceeded.");
|
||||
}
|
||||
|
||||
@@ -31,21 +31,15 @@ namespace Espo\Core\Authentication\Hook;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\InjectableFactory;
|
||||
|
||||
use Espo\Core\Authentication\AuthenticationData;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Authentication\Result;
|
||||
|
||||
class Manager
|
||||
{
|
||||
private Metadata $metadata;
|
||||
private InjectableFactory $injectableFactory;
|
||||
|
||||
public function __construct(Metadata $metadata, InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
public function __construct(private Metadata $metadata, private InjectableFactory $injectableFactory)
|
||||
{}
|
||||
|
||||
public function processBeforeLogin(AuthenticationData $data, Request $request): void
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ class Hmac implements SignatureVerifier
|
||||
$this->key = $key;
|
||||
|
||||
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
|
||||
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
|
||||
throw new RuntimeException("Unsupported algorithm $algorithm.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class Rsa implements SignatureVerifier
|
||||
$this->keys = $keys;
|
||||
|
||||
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
|
||||
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
|
||||
throw new RuntimeException("Unsupported algorithm $algorithm.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,12 @@ class Rsa implements SignatureVerifier
|
||||
'e' => new BigInteger('0x' . bin2hex(Util::base64UrlDecode($key->getE())), 16),
|
||||
]);
|
||||
|
||||
return $publicKey->toString('PKCS8');
|
||||
$pem = $publicKey->toString('PKCS8');
|
||||
|
||||
if (!is_string($pem)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $pem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class Header
|
||||
try {
|
||||
$parsed = Json::decode($raw);
|
||||
}
|
||||
catch (JsonException $e) {}
|
||||
catch (JsonException) {}
|
||||
|
||||
if (!$parsed instanceof stdClass) {
|
||||
throw new RuntimeException();
|
||||
@@ -88,23 +88,25 @@ class Header
|
||||
);
|
||||
}
|
||||
|
||||
/** @noinspection PhpSameParameterValueInspection */
|
||||
private static function obtainFromParsedString(stdClass $parsed, string $name): string
|
||||
{
|
||||
$value = $parsed->$name ?? null;
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new RuntimeException("No or bad `{$name}` in JWT header.");
|
||||
throw new RuntimeException("No or bad `$name` in JWT header.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** @noinspection PhpSameParameterValueInspection */
|
||||
private static function obtainFromParsedStringNull(stdClass $parsed, string $name): ?string
|
||||
{
|
||||
$value = $parsed->$name ?? null;
|
||||
|
||||
if ($value !== null && !is_string($value)) {
|
||||
throw new RuntimeException("Bad `{$name}` in JWT header.");
|
||||
throw new RuntimeException("Bad `$name` in JWT header.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
@@ -123,6 +123,7 @@ class Payload
|
||||
return $this->authTime;
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function getSid(): ?string
|
||||
{
|
||||
return $this->sid;
|
||||
@@ -143,7 +144,7 @@ class Payload
|
||||
try {
|
||||
$parsed = Json::decode($raw);
|
||||
}
|
||||
catch (JsonException $e) {}
|
||||
catch (JsonException) {}
|
||||
|
||||
if (!$parsed instanceof stdClass) {
|
||||
throw new RuntimeException();
|
||||
|
||||
@@ -29,4 +29,6 @@
|
||||
|
||||
namespace Espo\Core\Authentication\Ldap;
|
||||
|
||||
class Client extends \Laminas\Ldap\Ldap {}
|
||||
use Laminas\Ldap\Ldap;
|
||||
|
||||
class Client extends Ldap {}
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Authentication\Ldap;
|
||||
|
||||
use Laminas\Ldap\Exception\LdapException;
|
||||
|
||||
class ClientFactory
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
* @throws \Laminas\Ldap\Exception\LdapException
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function create(array $options): Client
|
||||
{
|
||||
|
||||
@@ -52,7 +52,12 @@ use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\PasswordHash;
|
||||
use Espo\Entities\User;
|
||||
use Exception;
|
||||
use Laminas\Ldap\Exception\LdapException;
|
||||
use Laminas\Ldap\Ldap;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class LdapLogin implements Login
|
||||
{
|
||||
private LDAPUtils $utils;
|
||||
@@ -81,6 +86,7 @@ class LdapLogin implements Login
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
* @noinspection PhpUnusedPrivateFieldInspection
|
||||
*/
|
||||
private $ldapFieldMap = [
|
||||
'userName' => 'userNameAttribute',
|
||||
@@ -93,6 +99,7 @@ class LdapLogin implements Login
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
* @noinspection PhpUnusedPrivateFieldInspection
|
||||
*/
|
||||
private $userFieldMap = [
|
||||
'teamsIds' => 'userTeamsIds',
|
||||
@@ -101,12 +108,16 @@ class LdapLogin implements Login
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
* @noinspection PhpUnusedPrivateFieldInspection
|
||||
*/
|
||||
private $portalUserFieldMap = [
|
||||
'portalsIds' => 'portalUserPortalsIds',
|
||||
'portalRolesIds' => 'portalUserRolesIds',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function login(Data $data, Request $request): Result
|
||||
{
|
||||
$username = $data->getUsername();
|
||||
@@ -219,7 +230,7 @@ class LdapLogin implements Login
|
||||
if (!isset($user)) {
|
||||
if (!$this->utils->getOption('createEspoUser')) {
|
||||
$this->log->warning(
|
||||
"LDAP: Authentication success for user {$username}, but user is not created in EspoCRM."
|
||||
"LDAP: Authentication success for user $username, but user is not created in EspoCRM."
|
||||
);
|
||||
|
||||
return Result::fail(FailReason::USER_NOT_FOUND);
|
||||
@@ -369,13 +380,14 @@ class LdapLogin implements Login
|
||||
$user->setAsNotNew();
|
||||
$user->updateFetchedValues();
|
||||
|
||||
/** @var ?User */
|
||||
return $this->entityManager->getEntityById(User::ENTITY_TYPE, $user->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find LDAP user DN by his username.
|
||||
*
|
||||
* @throws \Laminas\Ldap\Exception\LdapException
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function findLdapUserDnByUsername(string $username): ?string
|
||||
{
|
||||
@@ -395,7 +407,8 @@ class LdapLogin implements Login
|
||||
$loginFilterString . ')';
|
||||
|
||||
/** @var array<int, array{dn: string}> $result */
|
||||
$result = $ldapClient->search($searchString, null, Client::SEARCH_SCOPE_SUB);
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$result = $ldapClient->search($searchString, null, Ldap::SEARCH_SCOPE_SUB);
|
||||
|
||||
$this->log->debug('LDAP: user search string: "' . $searchString . '"');
|
||||
|
||||
@@ -413,11 +426,11 @@ class LdapLogin implements Login
|
||||
{
|
||||
$filter = trim($filter);
|
||||
|
||||
if (substr($filter, 0, 1) != '(') {
|
||||
if (!str_starts_with($filter, '(')) {
|
||||
$filter = '(' . $filter;
|
||||
}
|
||||
|
||||
if (substr($filter, -1) != ')') {
|
||||
if (!str_ends_with($filter, ')')) {
|
||||
$filter = $filter . ')';
|
||||
}
|
||||
|
||||
|
||||
@@ -188,8 +188,6 @@ class Utils
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
|
||||
$zendOptions = array_diff_key($options, array_flip($this->permittedEspoOptions));
|
||||
|
||||
return $zendOptions;
|
||||
return array_diff_key($options, array_flip($this->permittedEspoOptions));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user