mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-10 10:47:01 +00:00
Compare commits
593 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
128d7ec94d | ||
|
|
e0277038cd | ||
|
|
845a50a99f | ||
|
|
a30a353156 | ||
|
|
27a7e2f6f5 | ||
|
|
febf78dab6 | ||
|
|
fab0efa5e5 | ||
|
|
72794768af | ||
|
|
75c71d4b28 | ||
|
|
81364eefb3 | ||
|
|
07a09dd08e | ||
|
|
673370b9a5 | ||
|
|
c398ab0a40 | ||
|
|
86f3801513 | ||
|
|
678be304c5 | ||
|
|
0243a2a5a0 | ||
|
|
15d95ddac9 | ||
|
|
33a15bdbd6 | ||
|
|
b71a237cad | ||
|
|
11d9d2aee4 | ||
|
|
6a1a3b67c8 | ||
|
|
d1bb6be354 | ||
|
|
55a387c785 | ||
|
|
6efa058293 | ||
|
|
8f054a475c | ||
|
|
5b1919b760 | ||
|
|
50ded4b933 | ||
|
|
c4b0629e4f | ||
|
|
3d5991495b | ||
|
|
1ee853890c | ||
|
|
400347740d | ||
|
|
eae84b9d8f | ||
|
|
1ba4937f32 | ||
|
|
205091dc6f | ||
|
|
d704d089a3 | ||
|
|
4cf6e4f9d3 | ||
|
|
88565e2e19 | ||
|
|
3a295bcbad | ||
|
|
3cf87f8bd9 | ||
|
|
469f0e1913 | ||
|
|
6e8e2abd5f | ||
|
|
a7016ca153 | ||
|
|
7359367569 | ||
|
|
e394779737 | ||
|
|
9bb1b1a36c | ||
|
|
7ac977ef6c | ||
|
|
aabe966bcf | ||
|
|
ffbe6f5efa | ||
|
|
68f568949e | ||
|
|
3fbaeab264 | ||
|
|
bc0bcf3594 | ||
|
|
0258f5e8b1 | ||
|
|
5e2ba0d28e | ||
|
|
ed03e6637f | ||
|
|
ccb17b1170 | ||
|
|
8ce3f4dcc1 | ||
|
|
dbf8f15333 | ||
|
|
51d838e1b2 | ||
|
|
c5381488ce | ||
|
|
662b63e47b | ||
|
|
3e8459d5c2 | ||
|
|
dcf0d4265b | ||
|
|
ef3330d0fb | ||
|
|
def0bbd55e | ||
|
|
44c3aa0f19 | ||
|
|
96c8382143 | ||
|
|
6e980eaea9 | ||
|
|
42b703204a | ||
|
|
5ebad1c816 | ||
|
|
4e230845f9 | ||
|
|
8855161518 | ||
|
|
4418133ec0 | ||
|
|
5b9ac837dd | ||
|
|
2a570dd573 | ||
|
|
a2e34ff6ce | ||
|
|
d75ff87824 | ||
|
|
9fd4d78485 | ||
|
|
b9a5f1cbff | ||
|
|
123605c442 | ||
|
|
0c539faaee | ||
|
|
a571e283d3 | ||
|
|
0a17b974c9 | ||
|
|
cf3497c817 | ||
|
|
22665ee951 | ||
|
|
416adafaa5 | ||
|
|
96bf57fea7 | ||
|
|
a41374ef74 | ||
|
|
e2800c7dbf | ||
|
|
8a8b800302 | ||
|
|
48ed42c29c | ||
|
|
d8722a4087 | ||
|
|
ac7eb466fc | ||
|
|
839c31dbb8 | ||
|
|
dbaa7adf20 | ||
|
|
98628a273b | ||
|
|
90bcd57d2c | ||
|
|
e1acf4a2b8 | ||
|
|
449a8fcbb4 | ||
|
|
268615d8fe | ||
|
|
2b98eef915 | ||
|
|
39e7b397e9 | ||
|
|
16576623f6 | ||
|
|
cc5c979af6 | ||
|
|
3fd33d3269 | ||
|
|
39cefe99e6 | ||
|
|
d8d9c3eb66 | ||
|
|
1502361fa3 | ||
|
|
61b50b5288 | ||
|
|
2303a81751 | ||
|
|
0e7c405862 | ||
|
|
23eb78d5c3 | ||
|
|
5e2c134194 | ||
|
|
3dcbff1d08 | ||
|
|
4559f2746e | ||
|
|
963fb40b2b | ||
|
|
1682075267 | ||
|
|
3032b2be87 | ||
|
|
2624c15763 | ||
|
|
981be27151 | ||
|
|
79b06df170 | ||
|
|
0eafe2ba35 | ||
|
|
11c6b79a4d | ||
|
|
b2ce89754f | ||
|
|
1c1aac38c2 | ||
|
|
587ab9a492 | ||
|
|
3a38c8245b | ||
|
|
99cb0ed8ce | ||
|
|
1d685dc772 | ||
|
|
cea34c95b5 | ||
|
|
17bfe2d969 | ||
|
|
7e4254ab42 | ||
|
|
f7d0e54f7a | ||
|
|
41ea5e22ca | ||
|
|
85907320b4 | ||
|
|
a52a5e31e5 | ||
|
|
21bf43e33d | ||
|
|
61c5ad9802 | ||
|
|
6b58d30eec | ||
|
|
84f7fc562c | ||
|
|
56f66976c7 | ||
|
|
16cc10bca0 | ||
|
|
b18ccfcb7b | ||
|
|
3c3ea2135b | ||
|
|
f860a8ecba | ||
|
|
8fa5240dcd | ||
|
|
9935c86538 | ||
|
|
8ec1d5353f | ||
|
|
85ccb0da15 | ||
|
|
b431f40f9f | ||
|
|
85bb013a0e | ||
|
|
aeeb779ab0 | ||
|
|
ed1f07d872 | ||
|
|
97938b8fcd | ||
|
|
54d73fa073 | ||
|
|
2404dfff75 | ||
|
|
e401f3aef2 | ||
|
|
33914a9b61 | ||
|
|
46c23cec26 | ||
|
|
71cb29c507 | ||
|
|
034a0b8ff1 | ||
|
|
f4c6c16df7 | ||
|
|
1bfc8a2b38 | ||
|
|
601cf8fdfd | ||
|
|
4be5829805 | ||
|
|
66ae50b129 | ||
|
|
daa94f53d0 | ||
|
|
42c54f4c84 | ||
|
|
633ac6e63f | ||
|
|
8cfa9bd8af | ||
|
|
7c63eabd76 | ||
|
|
1dafe99fa9 | ||
|
|
35431ede0d | ||
|
|
869d73511c | ||
|
|
3fab26158f | ||
|
|
b1b0df004e | ||
|
|
53bbfde2b2 | ||
|
|
6c32b94376 | ||
|
|
8dd35ec9d3 | ||
|
|
55c7cff79d | ||
|
|
06bd8ef175 | ||
|
|
1894c5cc9a | ||
|
|
927fd7a589 | ||
|
|
fe70eba7d6 | ||
|
|
4a4618ef56 | ||
|
|
3af94e3545 | ||
|
|
80ad254cf6 | ||
|
|
e4154a70ff | ||
|
|
0fe44b0885 | ||
|
|
ad00a8cff8 | ||
|
|
8a71c651ae | ||
|
|
c09c1a8298 | ||
|
|
c950559038 | ||
|
|
251700eb50 | ||
|
|
7bf3406735 | ||
|
|
30e123ccbb | ||
|
|
d993f99ad1 | ||
|
|
e9fbb1d4d4 | ||
|
|
6f9ba6c7f6 | ||
|
|
08f5bbeaba | ||
|
|
520d55b98d | ||
|
|
d57cea662d | ||
|
|
2f5b78f887 | ||
|
|
ef9795f6e4 | ||
|
|
f8166a7109 | ||
|
|
bcfd735844 | ||
|
|
54f91df984 | ||
|
|
19843c19d1 | ||
|
|
e620317966 | ||
|
|
224187ba11 | ||
|
|
9e9af5769a | ||
|
|
1224e778d4 | ||
|
|
0ee1cbeaaf | ||
|
|
d0c6400644 | ||
|
|
19ae8f2499 | ||
|
|
0aae87a248 | ||
|
|
fa1bd30ac3 | ||
|
|
73d0203b8f | ||
|
|
caf3a729d1 | ||
|
|
7e49e6491d | ||
|
|
ff045692d0 | ||
|
|
2e0f0fb7db | ||
|
|
7763ddb802 | ||
|
|
eafd465ba2 | ||
|
|
512ddc0d76 | ||
|
|
b2156f36b9 | ||
|
|
a56ccbd1f8 | ||
|
|
e91a98d0d5 | ||
|
|
3d914d0591 | ||
|
|
b95c1c8878 | ||
|
|
f18ed3a531 | ||
|
|
d2feca38d8 | ||
|
|
41b0dd4e78 | ||
|
|
e92b200eb3 | ||
|
|
c1ae7dff95 | ||
|
|
6cbf6cea73 | ||
|
|
b8e1515015 | ||
|
|
399bc60525 | ||
|
|
913dc48bfe | ||
|
|
2af34da28f | ||
|
|
9043d37a08 | ||
|
|
72cd6583d5 | ||
|
|
03969a20fe | ||
|
|
f2a5e103dd | ||
|
|
45d66461e8 | ||
|
|
79fb3eaa29 | ||
|
|
946ad47765 | ||
|
|
488d5ab6ae | ||
|
|
9c8d7d2062 | ||
|
|
ce6abcd3f0 | ||
|
|
8f2ccb45a2 | ||
|
|
415e9503f7 | ||
|
|
aec3e459b5 | ||
|
|
48ce79811f | ||
|
|
0d2c30bcd9 | ||
|
|
c939deb589 | ||
|
|
5cdcb8bd74 | ||
|
|
e57018732f | ||
|
|
4869d5f5b6 | ||
|
|
7ffcb9031b | ||
|
|
474fd25d3c | ||
|
|
22d50aee21 | ||
|
|
9fec3a57b2 | ||
|
|
d3c480d481 | ||
|
|
58e091b45e | ||
|
|
c4303483b7 | ||
|
|
c742c0859b | ||
|
|
45ab1653a4 | ||
|
|
bc10efa640 | ||
|
|
757e2fbda1 | ||
|
|
a09959a969 | ||
|
|
c39052a0d3 | ||
|
|
e321d61237 | ||
|
|
678b3bb47d | ||
|
|
489a5befde | ||
|
|
c0a16d8b7c | ||
|
|
2ce99b3702 | ||
|
|
8008741e35 | ||
|
|
47edd2215f | ||
|
|
91740192d2 | ||
|
|
bd900d0b48 | ||
|
|
ca5aa8edf0 | ||
|
|
81937ca606 | ||
|
|
ba15bf8d7e | ||
|
|
e529a72a00 | ||
|
|
080a7864b5 | ||
|
|
3db7261c0e | ||
|
|
bb47ab3261 | ||
|
|
0ae0365ee5 | ||
|
|
cbcc560bd3 | ||
|
|
ee819fb58a | ||
|
|
69eb1a1948 | ||
|
|
a71bf16c51 | ||
|
|
2a2e0cc15d | ||
|
|
fe99cf8db1 | ||
|
|
5e7a089cb0 | ||
|
|
a198cf8c65 | ||
|
|
57e5202a28 | ||
|
|
cf3cc2c4a5 | ||
|
|
9eda4c48e9 | ||
|
|
06153536c1 | ||
|
|
77fdd9e194 | ||
|
|
0e383acfe3 | ||
|
|
9e65549eb3 | ||
|
|
a6c0e2aa0e | ||
|
|
67702f5a55 | ||
|
|
4270a452a7 | ||
|
|
5178d72850 | ||
|
|
832cfb8915 | ||
|
|
79cd6ad7b9 | ||
|
|
a445c4d2a9 | ||
|
|
4a1cb6a9d5 | ||
|
|
5069547464 | ||
|
|
335a62cb10 | ||
|
|
e74e9e519b | ||
|
|
3f8fcbcb74 | ||
|
|
90db4b29c4 | ||
|
|
8fce28df3b | ||
|
|
6ac4e7ef18 | ||
|
|
9945080bec | ||
|
|
7dac68b6c0 | ||
|
|
209e828a49 | ||
|
|
398743fe63 | ||
|
|
92f6759591 | ||
|
|
368c2fb866 | ||
|
|
985c6fb64b | ||
|
|
9b9ec31f0f | ||
|
|
946d0137af | ||
|
|
37d2d8cf4f | ||
|
|
3072c2a236 | ||
|
|
a38803389c | ||
|
|
4708cf00c0 | ||
|
|
4d525ae8ef | ||
|
|
57b69fe5a9 | ||
|
|
6ee1387fcf | ||
|
|
5cfbdb21e9 | ||
|
|
2abf1ccb73 | ||
|
|
09b977a798 | ||
|
|
80fac162af | ||
|
|
c5e43e6e49 | ||
|
|
08571e805a | ||
|
|
dd5691ef7f | ||
|
|
6e43dc87f2 | ||
|
|
06dc8ef281 | ||
|
|
b05b4c78e8 | ||
|
|
9726d63d0b | ||
|
|
4ca7d27d39 | ||
|
|
29b2304b25 | ||
|
|
8bca3b2113 | ||
|
|
7181d7196f | ||
|
|
28ebc6ea81 | ||
|
|
f3c4e631cd | ||
|
|
da14a41387 | ||
|
|
cc14c69a5e | ||
|
|
5ae385527b | ||
|
|
b6aec6360b | ||
|
|
5a5528f78f | ||
|
|
a3f11919c9 | ||
|
|
3847afc50f | ||
|
|
a3e7f5c5fa | ||
|
|
1fe2c74691 | ||
|
|
7b5789e44c | ||
|
|
61e03fd12a | ||
|
|
5f6bd7b748 | ||
|
|
1e9a59f084 | ||
|
|
9e11cecfa5 | ||
|
|
8a76e70fcb | ||
|
|
92e4d03495 | ||
|
|
a9d059c0df | ||
|
|
2b14e11259 | ||
|
|
50cde83341 | ||
|
|
0283f781d7 | ||
|
|
ddd54c321e | ||
|
|
01a40e311d | ||
|
|
6209733497 | ||
|
|
5e37bc2d62 | ||
|
|
8477416063 | ||
|
|
5020c132b5 | ||
|
|
b70a354556 | ||
|
|
92f5afef3c | ||
|
|
9a407eef6d | ||
|
|
e635a28f16 | ||
|
|
adbed86617 | ||
|
|
eb556b978b | ||
|
|
5b1647a42f | ||
|
|
153c09f282 | ||
|
|
8350bebb50 | ||
|
|
75e5701a3b | ||
|
|
eb7ca6188c | ||
|
|
e80a885208 | ||
|
|
b1076ba1bd | ||
|
|
ed281abd92 | ||
|
|
e63111c93f | ||
|
|
e87ba0c4e1 | ||
|
|
79f7243e70 | ||
|
|
b19f148ee8 | ||
|
|
4ee79811bc | ||
|
|
e79e9d6ce1 | ||
|
|
ddae4383dd | ||
|
|
683751ac0c | ||
|
|
8407d27ea0 | ||
|
|
4126b99a7b | ||
|
|
69003eae7f | ||
|
|
ec5e173e40 | ||
|
|
06603bd2b3 | ||
|
|
11263671b2 | ||
|
|
e9f0880f01 | ||
|
|
2fac2328d0 | ||
|
|
3ef7382ab0 | ||
|
|
fb4379ea34 | ||
|
|
e7bea7e7fe | ||
|
|
20ea95238b | ||
|
|
1098540ae4 | ||
|
|
3ae31f6341 | ||
|
|
014c6a3b73 | ||
|
|
65f9cd35c9 | ||
|
|
324bc26e1e | ||
|
|
234ff55376 | ||
|
|
e13f79134e | ||
|
|
f78ca003d2 | ||
|
|
d7460a9e0a | ||
|
|
85a98326fe | ||
|
|
5d1584ce83 | ||
|
|
5f02605aca | ||
|
|
b3b1441510 | ||
|
|
84ba790ef9 | ||
|
|
de93e83abe | ||
|
|
1bcb8349de | ||
|
|
dc527c2cf6 | ||
|
|
71daa4ce2f | ||
|
|
a13710e9d2 | ||
|
|
bf2e7ac236 | ||
|
|
72120f262d | ||
|
|
6cde07a75b | ||
|
|
42213789ba | ||
|
|
598655ef33 | ||
|
|
b247b00bd7 | ||
|
|
d39a732d17 | ||
|
|
86e2e39772 | ||
|
|
6405ab2b17 | ||
|
|
cbbde8af9b | ||
|
|
2c4779a98c | ||
|
|
b465faf936 | ||
|
|
a0a801a7c7 | ||
|
|
2863654f8f | ||
|
|
717869d0da | ||
|
|
115465f017 | ||
|
|
738f483aef | ||
|
|
f7e782f081 | ||
|
|
2214c70b2b | ||
|
|
7f4bc6f952 | ||
|
|
bf28abc4ca | ||
|
|
963b9e9e59 | ||
|
|
8a8de30949 | ||
|
|
cee67cbd97 | ||
|
|
d60d834a65 | ||
|
|
c1a10bf926 | ||
|
|
a1dd707315 | ||
|
|
60d778bcb1 | ||
|
|
d6291b03e3 | ||
|
|
24b5b94d63 | ||
|
|
85d4f6f304 | ||
|
|
da2c80b4fc | ||
|
|
18a91d633d | ||
|
|
65ec35f4dd | ||
|
|
515e388521 | ||
|
|
b9037b9a52 | ||
|
|
0bf7bcb204 | ||
|
|
bec4a188a3 | ||
|
|
df6b5ae043 | ||
|
|
e29d0959c4 | ||
|
|
cce73a8c32 | ||
|
|
eee9d37c97 | ||
|
|
4b983afc5c | ||
|
|
3a1dd252e1 | ||
|
|
c2d7bc818e | ||
|
|
732a4a24e6 | ||
|
|
64e3cd23ad | ||
|
|
aca48f024d | ||
|
|
9b0549a762 | ||
|
|
b0681b89ea | ||
|
|
2182cf6a89 | ||
|
|
2e13d48518 | ||
|
|
841e570e57 | ||
|
|
1a08df7824 | ||
|
|
3f0140c716 | ||
|
|
38a9c69bde | ||
|
|
3366a27575 | ||
|
|
a93ba33e92 | ||
|
|
80c5decee8 | ||
|
|
21b84c9f36 | ||
|
|
970966e4c5 | ||
|
|
4336344d40 | ||
|
|
fe9740e3eb | ||
|
|
7581942b0c | ||
|
|
5dfab7e325 | ||
|
|
e93f338a9e | ||
|
|
d1b3afdd01 | ||
|
|
054fde7288 | ||
|
|
f887d05d5e | ||
|
|
09dea0be01 | ||
|
|
40157bcb8c | ||
|
|
e0e80c5a56 | ||
|
|
926a052e99 | ||
|
|
a7d10ae9d6 | ||
|
|
0757ebf520 | ||
|
|
20479cdcab | ||
|
|
1954efd7e0 | ||
|
|
cb34377363 | ||
|
|
2e9437572d | ||
|
|
0e639ef6a8 | ||
|
|
5c7467e4bf | ||
|
|
b9b71b2015 | ||
|
|
50b7f2cf30 | ||
|
|
cf20fe65cc | ||
|
|
a676f26c36 | ||
|
|
8ba7fdfa85 | ||
|
|
f81861df19 | ||
|
|
1d6f654c2f | ||
|
|
67df53dda8 | ||
|
|
11bc32ab8a | ||
|
|
f5610bbdb4 | ||
|
|
24cd1b3c5d | ||
|
|
48c2bda539 | ||
|
|
7888f5ca74 | ||
|
|
1dfdb261e1 | ||
|
|
7b0217be6a | ||
|
|
bab9140ef2 | ||
|
|
ddee6815af | ||
|
|
4e9dbc98e3 | ||
|
|
c313e72f75 | ||
|
|
e0c84e007c | ||
|
|
fe2f15c576 | ||
|
|
87ef57eb29 | ||
|
|
6f75dd3dbe | ||
|
|
1d45e6d839 | ||
|
|
87875c1a7f | ||
|
|
8d6e8f2610 | ||
|
|
fe81fd3549 | ||
|
|
fbfa219615 | ||
|
|
1e1ee6ba8c | ||
|
|
c51bb22822 | ||
|
|
7c32b525a3 | ||
|
|
b242119e00 | ||
|
|
ecf90a3941 | ||
|
|
c97ba58dcb | ||
|
|
e802ceea86 | ||
|
|
280accd9ad | ||
|
|
f59122254d | ||
|
|
137b6fcff7 | ||
|
|
44b9f60478 | ||
|
|
a34c0ff776 | ||
|
|
4dfce57bd1 | ||
|
|
9c45642d79 | ||
|
|
bfd1ff5fa7 | ||
|
|
2322b2d1da | ||
|
|
b8dc4e2dcd | ||
|
|
e4959e0c38 | ||
|
|
dbafd6ba4d | ||
|
|
0f1057a6cf | ||
|
|
88b0479366 | ||
|
|
4ce40dd85c | ||
|
|
bf2813c241 | ||
|
|
cf4045faec | ||
|
|
6349db3122 | ||
|
|
62c7af7b38 | ||
|
|
6ab55fcd22 | ||
|
|
947f049893 | ||
|
|
71cbbbe556 | ||
|
|
bfc5e8054d | ||
|
|
62a290c197 | ||
|
|
b2ee92d606 | ||
|
|
b68100df38 | ||
|
|
41b8bb0acc | ||
|
|
1b52f3ca3e | ||
|
|
8ff6c8bd03 | ||
|
|
ed249366f9 | ||
|
|
ed9cb77bba | ||
|
|
6ad1d917bd | ||
|
|
e5c24a5fdc | ||
|
|
a6dd1d9db0 | ||
|
|
b334da55ed | ||
|
|
c6c2d5a0d9 | ||
|
|
fc89a1bdf6 | ||
|
|
489f71ea47 | ||
|
|
029331b3b7 | ||
|
|
70aba2ec52 | ||
|
|
ed0b8b4758 | ||
|
|
251fb2cd49 | ||
|
|
0dc5b8282a | ||
|
|
d5eb165ea4 | ||
|
|
9d4b39c1f7 | ||
|
|
c3dda027bf | ||
|
|
0307e9752d |
2
.github/workflows/test-integration-pg.yml
vendored
2
.github/workflows/test-integration-pg.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
name: Test on PHP ${{ matrix.php-versions }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
TEST_DATABASE_HOST: '127.0.0.1'
|
||||
TEST_DATABASE_PLATFORM: 'Postgresql'
|
||||
|
||||
2
.github/workflows/test-integration.yml
vendored
2
.github/workflows/test-integration.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
name: Test on PHP ${{ matrix.php-versions }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
TEST_DATABASE_HOST: '127.0.0.1'
|
||||
TEST_DATABASE_PORT: '8888'
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
name: Test on PHP ${{ matrix.php-versions }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.2', '8.3', '8.4']
|
||||
|
||||
38
.idea/jsonSchemas.xml
generated
38
.idea/jsonSchemas.xml
generated
@@ -341,6 +341,25 @@
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="metadata/app/clientIcons">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="generatedName" value="New Schema" />
|
||||
<option name="name" value="metadata/app/clientIcons" />
|
||||
<option name="relativePathToSchema" value="schema/metadata/app/clientIcons.json" />
|
||||
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||
<option name="patterns">
|
||||
<list>
|
||||
<Item>
|
||||
<option name="pattern" value="true" />
|
||||
<option name="path" value="*/Resources/metadata/app/clientIcons.json" />
|
||||
<option name="mappingKind" value="Pattern" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="metadata/app/clientRecord">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
@@ -1386,6 +1405,25 @@
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="metadata/logicDefs">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="generatedName" value="New Schema" />
|
||||
<option name="name" value="metadata/logicDefs" />
|
||||
<option name="relativePathToSchema" value="schema/metadata/logicDefs.json" />
|
||||
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||
<option name="patterns">
|
||||
<list>
|
||||
<Item>
|
||||
<option name="pattern" value="true" />
|
||||
<option name="path" value="*/metadata/logicDefs/*.json" />
|
||||
<option name="mappingKind" value="Pattern" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="metadata/notificationDefs">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -82,6 +82,12 @@
|
||||
],
|
||||
"url": "./schema/metadata/integrations.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/metadata/logicDefs/*.json"
|
||||
],
|
||||
"url": "./schema/metadata/logicDefs.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/metadata/notificationDefs/*.json"
|
||||
@@ -184,6 +190,12 @@
|
||||
],
|
||||
"url": "./schema/metadata/app/client.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/Resources/metadata/app/clientIcons.json"
|
||||
],
|
||||
"url": "./schema/metadata/app/clientIcons.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"*/Resources/metadata/app/clientRecord.json"
|
||||
|
||||
@@ -232,6 +232,16 @@ class Binding implements BindingProcessor
|
||||
'Espo\\Tools\\Stream\\Service',
|
||||
'streamService'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Espo\\Core\\Utils\\Config\\SystemConfig',
|
||||
'systemConfig'
|
||||
);
|
||||
|
||||
$binder->bindService(
|
||||
'Espo\\Core\\Utils\\Config\\ApplicationConfig',
|
||||
'applicationConfig'
|
||||
);
|
||||
}
|
||||
|
||||
private function bindCore(Binder $binder): void
|
||||
|
||||
@@ -46,14 +46,10 @@ class AccessChecker implements AccessEntityCREDSChecker
|
||||
{
|
||||
use DefaultAccessCheckerDependency;
|
||||
|
||||
private DefaultAccessChecker $defaultAccessChecker;
|
||||
private AclManager $aclManager;
|
||||
|
||||
public function __construct(DefaultAccessChecker $defaultAccessChecker, AclManager $aclManager)
|
||||
{
|
||||
$this->defaultAccessChecker = $defaultAccessChecker;
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
public function __construct(
|
||||
private DefaultAccessChecker $defaultAccessChecker,
|
||||
private AclManager $aclManager,
|
||||
) {}
|
||||
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
@@ -70,6 +66,10 @@ class AccessChecker implements AccessEntityCREDSChecker
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$user->isAdmin() && !$entity->isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entity->isPortal()) {
|
||||
if ($this->aclManager->getPermissionLevel($user, Permission::PORTAL) === Table::LEVEL_YES) {
|
||||
return true;
|
||||
|
||||
@@ -68,9 +68,9 @@ class AccessChecker implements AccessEntityCREDSChecker
|
||||
|
||||
assert($entity instanceof CoreEntity);
|
||||
|
||||
$userIdList = $entity->getLinkMultipleIdLIst('users');
|
||||
$userIdList = $entity->getLinkMultipleIdList('users');
|
||||
|
||||
if (is_array($userIdList) && in_array($user->getId(), $userIdList)) {
|
||||
if (in_array($user->getId(), $userIdList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ class AppLog implements Cleanup
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
if (!$this->config->get('cleanupAppLog')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = DeleteBuilder::create()
|
||||
->from(AppLogRecord::ENTITY_TYPE)
|
||||
->where(['createdAt<' => $this->getBefore()->toString()])
|
||||
|
||||
@@ -31,42 +31,40 @@ namespace Espo\Classes\Cleanup;
|
||||
|
||||
use Espo\Core\Cleanup\Cleanup;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Entities\WebhookEventQueueItem;
|
||||
use Espo\Entities\WebhookQueueItem;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use DateTime;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class WebhookQueue implements Cleanup
|
||||
{
|
||||
private string $cleanupWebhookQueuePeriod = '10 days';
|
||||
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
public function __construct(private Config $config, private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$period = '-' . $this->config->get('cleanupWebhookQueuePeriod', $this->cleanupWebhookQueuePeriod);
|
||||
|
||||
$datetime = new DateTime();
|
||||
|
||||
$datetime->modify($period);
|
||||
$from = $datetime->format('Y-m-d H:i:s');
|
||||
$from = $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
|
||||
$query1 = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from('WebhookQueueItem')
|
||||
->from(WebhookQueueItem::ENTITY_TYPE)
|
||||
->where([
|
||||
'DATE:(createdAt)<' => $from,
|
||||
'OR' => [
|
||||
'status!=' => 'Pending',
|
||||
'deleted' => true,
|
||||
'status!=' => WebhookQueueItem::STATUS_PENDING,
|
||||
Attribute::DELETED => true,
|
||||
],
|
||||
])
|
||||
->build();
|
||||
@@ -76,12 +74,12 @@ class WebhookQueue implements Cleanup
|
||||
$query2 = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from('WebhookEventQueueItem')
|
||||
->from(WebhookEventQueueItem::ENTITY_TYPE)
|
||||
->where([
|
||||
'DATE:(createdAt)<' => $from,
|
||||
'OR' => [
|
||||
'isProcessed' => true,
|
||||
'deleted' => true,
|
||||
Attribute::DELETED => true,
|
||||
],
|
||||
])
|
||||
->build();
|
||||
|
||||
@@ -85,7 +85,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
$column => '{value}',
|
||||
],
|
||||
],
|
||||
@@ -97,7 +97,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
$column => '{value}',
|
||||
],
|
||||
],
|
||||
@@ -109,7 +109,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
$column => '{value}',
|
||||
],
|
||||
],
|
||||
@@ -121,7 +121,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
$column => '{value}',
|
||||
],
|
||||
],
|
||||
@@ -133,7 +133,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
"$column*" => '{value}',
|
||||
],
|
||||
],
|
||||
@@ -145,7 +145,7 @@ class RelationshipRole implements FieldConverter
|
||||
'from' => $midTable,
|
||||
'select' => [$nearKey],
|
||||
'whereClause' => [
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
"$column*" => '{value}',
|
||||
],
|
||||
],
|
||||
|
||||
@@ -52,11 +52,6 @@ class AddressDataLoader implements Loader
|
||||
/** @var EmailRepository $repository */
|
||||
$repository = $this->entityManager->getRepository(Email::ENTITY_TYPE);
|
||||
|
||||
$repository->loadFromField($entity);
|
||||
$repository->loadToField($entity);
|
||||
$repository->loadCcField($entity);
|
||||
$repository->loadBccField($entity);
|
||||
$repository->loadReplyToField($entity);
|
||||
$repository->loadNameHash($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\FieldProcessing\Email;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Repositories\Email as EmailRepository;
|
||||
|
||||
/**
|
||||
* @implements Loader<Email>
|
||||
*/
|
||||
class AddressLoader implements Loader
|
||||
{
|
||||
public function __construct(private EntityManager $entityManager)
|
||||
{}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
/** @var EmailRepository $repository */
|
||||
$repository = $this->entityManager->getRepository(Email::ENTITY_TYPE);
|
||||
|
||||
$repository->loadFromField($entity);
|
||||
$repository->loadToField($entity);
|
||||
$repository->loadCcField($entity);
|
||||
$repository->loadBccField($entity);
|
||||
$repository->loadReplyToField($entity);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
/**
|
||||
* @implements Loader<Email>
|
||||
@@ -57,7 +58,7 @@ class UserColumnsLoader implements Loader
|
||||
Email::USERS_COLUMN_IN_ARCHIVE,
|
||||
])
|
||||
->where([
|
||||
'deleted' => false,
|
||||
Attribute::DELETED => false,
|
||||
'userId' => $this->user->getId(),
|
||||
'emailId' => $entity->getId(),
|
||||
])
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Espo\Classes\FieldProcessing\InboundEmail;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Mail\ConfigDataProvider;
|
||||
use Espo\Entities\InboundEmail;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
@@ -41,12 +41,12 @@ use Espo\ORM\Entity;
|
||||
class IsSystemLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$isSystem = $entity->getEmailAddress() === $this->config->get('outboundEmailFromAddress');
|
||||
$isSystem = $entity->getEmailAddress() === $this->configDataProvider->getSystemOutboundAddress();
|
||||
|
||||
$entity->set('isSystem', $isSystem);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -26,34 +27,46 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('crm:views/calendar/record/edit-view', ['views/record/base'], function (Dep) {
|
||||
namespace Espo\Classes\FieldProcessing\OAuthAccount;
|
||||
|
||||
return Dep.extend({
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\OAuthAccount;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\OAuth\ConfigDataProvider;
|
||||
|
||||
template: 'crm:calendar/record/edit-view',
|
||||
/**
|
||||
* @implements Loader<OAuthAccount>
|
||||
*/
|
||||
class DataLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
if (!$entity->get('providerId')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createField('mode', 'views/fields/enum', {
|
||||
options: this.getMetadata().get(['clientDefs', 'Calendar', 'sharedViewModeList']) || [],
|
||||
translation: 'DashletOptions.options.mode'
|
||||
}, null, null, {
|
||||
labelText: this.translate('mode', 'fields', 'DashletOptions')
|
||||
});
|
||||
$provider = $entity->getProvider();
|
||||
|
||||
this.createField('name', 'views/fields/varchar', {
|
||||
required: true
|
||||
}, null, null, {
|
||||
labelText: this.translate('name', 'fields')
|
||||
});
|
||||
$scope = null;
|
||||
|
||||
this.createField('teams', 'crm:views/calendar/fields/teams', {
|
||||
required: true
|
||||
}, null, null, {
|
||||
labelText: this.translate('teams', 'fields'),
|
||||
foreignScope: 'Team'
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
if ($provider->getScopes()) {
|
||||
$scope = implode($provider->getScopeSeparator() ?? ' ', $provider->getScopes());
|
||||
}
|
||||
|
||||
$data = [
|
||||
'endpoint' => $provider->getAuthorizationEndpoint(),
|
||||
'clientId' => $provider->getClientId(),
|
||||
'redirectUri' => $this->configDataProvider->getRedirectUri(),
|
||||
'scope' => $scope,
|
||||
'prompt' => $provider->getAuthorizationPrompt(),
|
||||
'params' => $provider->getAuthorizationParams(),
|
||||
];
|
||||
|
||||
$entity->set('data', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\FieldProcessing\OAuthProvider;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\OAuthProvider;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\OAuth\ConfigDataProvider;
|
||||
|
||||
/**
|
||||
* @implements Loader<OAuthProvider>
|
||||
*/
|
||||
class AuthorizationRedirectUriLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$entity->set('authorizationRedirectUri', $this->configDataProvider->getRedirectUri());
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace Espo\Classes\FieldValidators;
|
||||
use Espo\Core\Field\Currency;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class CurrencyType extends FloatType
|
||||
@@ -83,7 +84,7 @@ class CurrencyType extends FloatType
|
||||
}
|
||||
|
||||
/** @var int $precision */
|
||||
$precision = $entity->getAttributeParam($field, 'precision') ?? self::DEFAULT_PRECISION;
|
||||
$precision = $entity->getAttributeParam($field, AttributeParam::PRECISION) ?? self::DEFAULT_PRECISION;
|
||||
|
||||
$value = $entity->get($field);
|
||||
|
||||
@@ -95,6 +96,8 @@ class CurrencyType extends FloatType
|
||||
|
||||
$pad = str_pad('', $precision, '9');
|
||||
|
||||
assert(is_numeric($pad));
|
||||
|
||||
$limit = Currency::create($pad, 'USD');
|
||||
|
||||
if ($currency->compare($limit) === 1) {
|
||||
|
||||
@@ -41,7 +41,6 @@ use Espo\Entities\ArrayValue;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\AuthLogRecord;
|
||||
use Espo\Entities\AuthToken;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\Job;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\Notification;
|
||||
@@ -98,7 +97,6 @@ class Cleanup implements JobDataLess
|
||||
$this->cleanupJobs();
|
||||
$this->cleanupScheduledJobLog();
|
||||
$this->cleanupAttachments();
|
||||
$this->cleanupEmails();
|
||||
$this->cleanupNotifications();
|
||||
$this->cleanupActionHistory();
|
||||
$this->cleanupAuthToken();
|
||||
@@ -149,7 +147,7 @@ class Cleanup implements JobDataLess
|
||||
->where([
|
||||
'modifiedAt<' => $this->getCleanupJobFromDate(),
|
||||
'status=' => JobStatus::PENDING,
|
||||
'deleted' => true,
|
||||
Attribute::DELETED => true,
|
||||
])
|
||||
->build();
|
||||
|
||||
@@ -412,7 +410,7 @@ class Cleanup implements JobDataLess
|
||||
->from($scope)
|
||||
->withDeleted()
|
||||
->where([
|
||||
'deleted' => true,
|
||||
Attribute::DELETED => true,
|
||||
'modifiedAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
|
||||
'modifiedAt>' => $datetimeFrom->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
|
||||
])
|
||||
@@ -465,7 +463,7 @@ class Cleanup implements JobDataLess
|
||||
->delete()
|
||||
->from(Attachment::ENTITY_TYPE)
|
||||
->where([
|
||||
'deleted' => true,
|
||||
Attribute::DELETED => true,
|
||||
'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
|
||||
])
|
||||
->build();
|
||||
@@ -473,68 +471,6 @@ class Cleanup implements JobDataLess
|
||||
$this->entityManager->getQueryExecutor()->execute($delete);
|
||||
}
|
||||
|
||||
private function cleanupEmails(): void
|
||||
{
|
||||
$dateBefore = date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT, time() - 3600 * 24 * 20);
|
||||
|
||||
$query = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->select()
|
||||
->from(Email::ENTITY_TYPE)
|
||||
->withDeleted()
|
||||
->build();
|
||||
|
||||
/** @var iterable<Email> $emails */
|
||||
$emails = $this->entityManager
|
||||
->getRDBRepository(Email::ENTITY_TYPE)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->select([Attribute::ID])
|
||||
->where([
|
||||
'createdAt<' => $dateBefore,
|
||||
'deleted' => true,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$id = $email->getId();
|
||||
|
||||
$attachments = $this->entityManager
|
||||
->getRDBRepository(Attachment::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentId' => $id,
|
||||
'parentType' => Email::ENTITY_TYPE,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$this->entityManager->removeEntity($attachment);
|
||||
}
|
||||
|
||||
$delete = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Email::ENTITY_TYPE)
|
||||
->where([
|
||||
'deleted' => true,
|
||||
'id' => $id,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($delete);
|
||||
|
||||
$delete = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Email::RELATIONSHIP_EMAIL_USER)
|
||||
->where([
|
||||
'emailId' => $id,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($delete);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupNotifications(): void
|
||||
{
|
||||
@@ -584,17 +520,16 @@ class Cleanup implements JobDataLess
|
||||
{
|
||||
$scope = $entity->getEntityType();
|
||||
|
||||
if (!$entity->get('deleted')) {
|
||||
if (!$entity->get(Attribute::DELETED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repository = $this->entityManager->getRepository($scope);
|
||||
|
||||
if (!$repository instanceof RDBRepository) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
if (
|
||||
!$repository instanceof RDBRepository ||
|
||||
!$entity instanceof CoreEntity
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -647,82 +582,14 @@ class Cleanup implements JobDataLess
|
||||
}
|
||||
}
|
||||
|
||||
$query = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->select()
|
||||
->from(Note::ENTITY_TYPE)
|
||||
->withDeleted()
|
||||
->build();
|
||||
|
||||
$noteList = $this->entityManager
|
||||
->getRDBRepository(Note::ENTITY_TYPE)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedType' => $scope,
|
||||
'relatedId' => $entity->getId(),
|
||||
],
|
||||
[
|
||||
'parentType' => $scope,
|
||||
'parentId' => $entity->getId(),
|
||||
]
|
||||
]
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($noteList as $note) {
|
||||
$this->entityManager->removeEntity($note);
|
||||
|
||||
$note->set('deleted', true);
|
||||
|
||||
$this->cleanupDeletedEntity($note);
|
||||
}
|
||||
$this->cleanupEntityNotes($entity);
|
||||
$this->cleanupEntityAttachments($entity);
|
||||
|
||||
if ($scope === Note::ENTITY_TYPE) {
|
||||
$attachmentList = $this->entityManager
|
||||
->getRDBRepository(Attachment::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentId' => $entity->getId(),
|
||||
'parentType' => Note::ENTITY_TYPE,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($attachmentList as $attachment) {
|
||||
$this->entityManager->removeEntity($attachment);
|
||||
$this->entityManager
|
||||
->getRDBRepository(Attachment::ENTITY_TYPE)
|
||||
->deleteFromDb($attachment->getId());
|
||||
}
|
||||
|
||||
// @todo If ever reactions are supported not only for notes, then move out of the if-block.
|
||||
|
||||
$deleteReactionsQuery = DeleteBuilder::create()
|
||||
->from(UserReaction::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentId' => $entity->getId(),
|
||||
'parentType' => Note::ENTITY_TYPE,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($deleteReactionsQuery);
|
||||
$this->cleanupNoteReactions($entity);
|
||||
}
|
||||
|
||||
$arrayValueList = $this->entityManager
|
||||
->getRDBRepository(ArrayValue::ENTITY_TYPE)
|
||||
->sth()
|
||||
->where([
|
||||
'entityType' => $entity->getEntityType(),
|
||||
'entityId' => $entity->getId(),
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($arrayValueList as $arrayValue) {
|
||||
$this->entityManager
|
||||
->getRDBRepository(ArrayValue::ENTITY_TYPE)
|
||||
->deleteFromDb($arrayValue->getId());
|
||||
}
|
||||
$this->cleanupEntityArrayValues($entity);
|
||||
}
|
||||
|
||||
private function cleanupDeletedRecords(): void
|
||||
@@ -759,13 +626,13 @@ class Cleanup implements JobDataLess
|
||||
|
||||
$service = $this->recordServiceContainer->get($scope);
|
||||
|
||||
$whereClause = ['deleted' => true];
|
||||
$whereClause = [Attribute::DELETED => true];
|
||||
|
||||
if (
|
||||
!$this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($scope)
|
||||
->hasAttribute('deleted')
|
||||
->hasAttribute(Attribute::DELETED)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -816,4 +683,96 @@ class Cleanup implements JobDataLess
|
||||
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
private function cleanupEntityAttachments(CoreEntity $entity): void
|
||||
{
|
||||
// @todo Add file, image types support.
|
||||
|
||||
$attachments = $this->entityManager
|
||||
->getRDBRepository(Attachment::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentId' => $entity->getId(),
|
||||
'parentType' => $entity->getEntityType(),
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$this->entityManager->removeEntity($attachment);
|
||||
|
||||
$this->entityManager
|
||||
->getRDBRepository(Attachment::ENTITY_TYPE)
|
||||
->deleteFromDb($attachment->getId());
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupEntityNotes(CoreEntity $entity): void
|
||||
{
|
||||
$scope = $entity->getEntityType();
|
||||
|
||||
$query = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->select()
|
||||
->from(Note::ENTITY_TYPE)
|
||||
->withDeleted()
|
||||
->build();
|
||||
|
||||
$noteList = $this->entityManager
|
||||
->getRDBRepository(Note::ENTITY_TYPE)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedType' => $scope,
|
||||
'relatedId' => $entity->getId(),
|
||||
],
|
||||
[
|
||||
'parentType' => $scope,
|
||||
'parentId' => $entity->getId(),
|
||||
]
|
||||
]
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($noteList as $note) {
|
||||
$this->entityManager->removeEntity($note);
|
||||
|
||||
$note->set(Attribute::DELETED, true);
|
||||
|
||||
$this->cleanupDeletedEntity($note);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupNoteReactions(CoreEntity $entity): void
|
||||
{
|
||||
// @todo If ever reactions are supported not only for notes, then move out of the if-block.
|
||||
|
||||
$deleteReactionsQuery = DeleteBuilder::create()
|
||||
->from(UserReaction::ENTITY_TYPE)
|
||||
->where([
|
||||
'parentId' => $entity->getId(),
|
||||
'parentType' => Note::ENTITY_TYPE,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($deleteReactionsQuery);
|
||||
}
|
||||
|
||||
private function cleanupEntityArrayValues(CoreEntity $entity): void
|
||||
{
|
||||
$arrayValues = $this->entityManager
|
||||
->getRDBRepository(ArrayValue::ENTITY_TYPE)
|
||||
->sth()
|
||||
->where([
|
||||
'entityType' => $entity->getEntityType(),
|
||||
'entityId' => $entity->getId(),
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($arrayValues as $arrayValue) {
|
||||
$this->entityManager
|
||||
->getRDBRepository(ArrayValue::ENTITY_TYPE)
|
||||
->deleteFromDb($arrayValue->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\OAuthProvider;
|
||||
|
||||
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 GeneralFilter implements Filter
|
||||
{
|
||||
private const ATTR_CLIENT_SECRET = 'clientSecret';
|
||||
|
||||
public function __construct(private Crypt $crypt) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function filter(Data $data): void
|
||||
{
|
||||
$this->processClientSecret($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function processClientSecret(Data $data): void
|
||||
{
|
||||
$value = $data->get(self::ATTR_CLIENT_SECRET);
|
||||
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$data->set(self::ATTR_CLIENT_SECRET, $this->crypt->encrypt($value));
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Mail\EmailSender;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
@@ -42,7 +42,7 @@ class BeforeCreate implements SaveHook
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if ($entity->getStatus() === Email::STATUS_SENDING) {
|
||||
$messageId = Sender::generateMessageId($entity);
|
||||
$messageId = EmailSender::generateMessageId($entity);
|
||||
|
||||
$entity->setMessageId('<' . $messageId . '>');
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Email\Util;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class BeforeSave implements SaveHook
|
||||
{
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if (
|
||||
@@ -49,5 +49,22 @@ class BeforeSave implements SaveHook
|
||||
) {
|
||||
throw new BadRequest("Cannot set send-at if status is not Draft.");
|
||||
}
|
||||
|
||||
$this->processBodyPlain($entity);
|
||||
}
|
||||
|
||||
private function processBodyPlain(Email $entity): void
|
||||
{
|
||||
if (!$entity->isHtml() || !$entity->isAttributeChanged('body')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $entity->getBody();
|
||||
|
||||
if ($body) {
|
||||
$body = Util::stripHtml($body) ?: null;
|
||||
}
|
||||
|
||||
$entity->setBodyPlain($body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Classes\RecordHooks\Email;
|
||||
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Mail\EmailSender;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
@@ -94,7 +94,7 @@ class BeforeUpdate implements SaveHook
|
||||
}
|
||||
|
||||
if ($entity->getStatus() == Email::STATUS_SENDING) {
|
||||
$messageId = Sender::generateMessageId($entity);
|
||||
$messageId = EmailSender::generateMessageId($entity);
|
||||
|
||||
$entity->setMessageId('<' . $messageId . '>');
|
||||
}
|
||||
|
||||
@@ -201,11 +201,16 @@ class InFolder implements ItemConverter
|
||||
return WhereClause::fromRaw([
|
||||
'groupFolderId' => $groupFolderId,
|
||||
'groupStatusFolder' => null,
|
||||
'createdById!=' => $this->user->getId(),
|
||||
'fromEmailAddressId!=' => $this->getEmailAddressIdList(),
|
||||
'status' => [
|
||||
Email::STATUS_ARCHIVED,
|
||||
Email::STATUS_SENT,
|
||||
],
|
||||
'OR' => [
|
||||
'status' => Email::STATUS_ARCHIVED,
|
||||
'createdById!=' => $this->user->getId(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Classes\Select\EmailAddress\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
class Orphan implements Filter
|
||||
@@ -42,24 +43,24 @@ class Orphan implements Filter
|
||||
'EntityEmailAddress',
|
||||
'entityEmailAddress',
|
||||
[
|
||||
'emailAddressId:' => 'id',
|
||||
'deleted' => false,
|
||||
'emailAddressId:' => Attribute::ID,
|
||||
Attribute::DELETED => false,
|
||||
]
|
||||
)
|
||||
->leftJoin(
|
||||
'EmailEmailAddress',
|
||||
'emailEmailAddress',
|
||||
[
|
||||
'emailAddressId:' => 'id',
|
||||
'deleted' => false,
|
||||
'emailAddressId:' => Attribute::ID,
|
||||
Attribute::DELETED => false,
|
||||
]
|
||||
)
|
||||
->leftJoin(
|
||||
'Email',
|
||||
'email',
|
||||
[
|
||||
'fromEmailAddressId:' => 'id',
|
||||
'deleted' => false,
|
||||
'fromEmailAddressId:' => Attribute::ID,
|
||||
Attribute::DELETED => false,
|
||||
]
|
||||
)
|
||||
->where([
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Classes\Select\PhoneNumber\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
class Orphan implements Filter
|
||||
@@ -44,8 +45,8 @@ class Orphan implements Filter
|
||||
'EntityPhoneNumber',
|
||||
'entityPhoneNumber',
|
||||
[
|
||||
'phoneNumberId:' => 'id',
|
||||
'deleted' => false,
|
||||
'phoneNumberId:' => Attribute::ID,
|
||||
Attribute::DELETED => false,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -32,30 +32,26 @@ namespace Espo\Classes\TemplateHelpers;
|
||||
use Espo\Core\Htmlizer\Helper;
|
||||
use Espo\Core\Htmlizer\Helper\Data;
|
||||
use Espo\Core\Htmlizer\Helper\Result;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Log;
|
||||
use const CURLOPT_FOLLOWLOCATION;
|
||||
use const CURLOPT_HEADER;
|
||||
use const CURLOPT_HTTPHEADER;
|
||||
use const CURLOPT_RETURNTRANSFER;
|
||||
use const CURLOPT_TIMEOUT;
|
||||
use const CURLOPT_URL;
|
||||
use const CURLOPT_USERAGENT;
|
||||
|
||||
class GoogleMaps implements Helper
|
||||
{
|
||||
private const DEFAULT_SIZE = '400x400';
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $config;
|
||||
|
||||
private $log;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
Config $config,
|
||||
Log $log
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->config = $config;
|
||||
$this->log = $log;
|
||||
}
|
||||
private Metadata $metadata,
|
||||
private Config $config,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
public function render(Data $data): Result
|
||||
{
|
||||
@@ -194,6 +190,7 @@ class GoogleMaps implements Helper
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $url
|
||||
* @return string|bool
|
||||
*/
|
||||
private function getImage(string $url)
|
||||
@@ -207,13 +204,13 @@ class GoogleMaps implements Helper
|
||||
|
||||
$c = curl_init();
|
||||
|
||||
curl_setopt($c, \CURLOPT_URL, $url);
|
||||
curl_setopt($c, \CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($c, \CURLOPT_HEADER, 0);
|
||||
curl_setopt($c, \CURLOPT_USERAGENT, $agent);
|
||||
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($c, CURLOPT_HEADER, false);
|
||||
curl_setopt($c, CURLOPT_USERAGENT, $agent);
|
||||
curl_setopt($c, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
$raw = curl_exec($c);
|
||||
|
||||
|
||||
@@ -30,31 +30,33 @@
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
|
||||
use Espo\Core\Exceptions\NotFoundSilent;
|
||||
use Espo\Tools\Formula\Service;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Field\LinkParent;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Formula
|
||||
{
|
||||
private Service $service;
|
||||
|
||||
public function __construct(Service $service, User $user)
|
||||
{
|
||||
$this->service = $service;
|
||||
|
||||
/**
|
||||
* @throws ForbiddenSilent
|
||||
*/
|
||||
public function __construct(
|
||||
private Service $service,
|
||||
User $user,
|
||||
) {
|
||||
if (!$user->isAdmin()) {
|
||||
throw new ForbiddenSilent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function postActionCheckSyntax(Request $request): stdClass
|
||||
{
|
||||
$expression = $request->getParsedBody()->expression ?? null;
|
||||
@@ -66,6 +68,10 @@ class Formula
|
||||
return $this->service->checkSyntax($expression)->toStdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws NotFoundSilent
|
||||
*/
|
||||
public function postActionRun(Request $request): stdClass
|
||||
{
|
||||
$expression = $request->getParsedBody()->expression ?? null;
|
||||
|
||||
@@ -29,31 +29,34 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Services\Integration as Service;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Api\Request;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Integration
|
||||
{
|
||||
private $service;
|
||||
|
||||
private $user;
|
||||
|
||||
public function __construct(Service $service, User $user)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->user = $user;
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function __construct(
|
||||
private Service $service,
|
||||
private User $user,
|
||||
) {
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getActionRead(Request $request): stdClass
|
||||
{
|
||||
/** @var string $id */
|
||||
@@ -64,6 +67,10 @@ class Integration
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function putActionUpdate(Request $request): stdClass
|
||||
{
|
||||
/** @var string $id */
|
||||
|
||||
@@ -35,6 +35,9 @@ use Espo\Tools\ActionHistory\Service as Service;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class LastViewed
|
||||
{
|
||||
public function __construct(private SearchParamsFetcher $searchParamsFetcher, private Service $service)
|
||||
@@ -49,9 +52,6 @@ class LastViewed
|
||||
|
||||
$result = $this->service->getLastViewed($maxSize, $offset);
|
||||
|
||||
return (object) [
|
||||
'total' => $result->getTotal(),
|
||||
'list' => $result->getValueMapList(),
|
||||
];
|
||||
return $result->toApiOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,7 @@ class Notification extends RecordBase
|
||||
|
||||
$recordCollection = $this->getNotificationService()->get($userId, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $recordCollection->getTotal(),
|
||||
'list' => $recordCollection->getValueMapList(),
|
||||
];
|
||||
return $recordCollection->toApiOutput();
|
||||
}
|
||||
|
||||
public function getActionNotReadCount(): int
|
||||
|
||||
47
application/Espo/Controllers/OAuthAccount.php
Normal file
47
application/Espo/Controllers/OAuthAccount.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\Controllers\RecordBase;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OAuthAccount extends RecordBase
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
47
application/Espo/Controllers/OAuthProvider.php
Normal file
47
application/Espo/Controllers/OAuthProvider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\Controllers\Record;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OAuthProvider extends Record
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -79,12 +79,12 @@ class Stream
|
||||
|
||||
$reactionsCheckDate = DateTime::createNow();
|
||||
|
||||
return (object) [
|
||||
'total' => $collection->getTotal(),
|
||||
'list' => $collection->getValueMapList(),
|
||||
'reactionsCheckDate' => $reactionsCheckDate->toString(),
|
||||
'updatedReactions' => $this->getReactionUpdates($request, $id),
|
||||
];
|
||||
$output = $collection->toApiOutput();
|
||||
|
||||
$output->reactionsCheckDate = $reactionsCheckDate->toString();
|
||||
$output->updatedReactions = $this->getReactionUpdates($request, $id);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
@@ -94,11 +94,11 @@ class Stream
|
||||
$collection = $this->service->find($scope, $id, $searchParams);
|
||||
$pinnedCollection = $this->service->getPinned($scope, $id);
|
||||
|
||||
return (object) [
|
||||
'total' => $collection->getTotal(),
|
||||
'list' => $collection->getValueMapList(),
|
||||
'pinnedList' => $pinnedCollection->getValueMapList(),
|
||||
];
|
||||
$output = $collection->toApiOutput();
|
||||
|
||||
$output->pinnedList = $pinnedCollection->getValueMapList();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,10 +126,7 @@ class Stream
|
||||
$this->userRecordService->find($id, $searchParams) :
|
||||
$this->service->find($scope, $id ?? '', $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $result->getTotal(),
|
||||
'list' => $result->getValueMapList(),
|
||||
];
|
||||
return $result->toApiOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,10 +147,7 @@ class Stream
|
||||
|
||||
$result = $this->service->findUpdates($scope, $id, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $result->getTotal(),
|
||||
'list' => $result->getValueMapList(),
|
||||
];
|
||||
return $result->toApiOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,11 +31,10 @@ namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Controllers\RecordBase;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
use stdClass;
|
||||
|
||||
class Webhook extends RecordBase
|
||||
class Webhook extends Record
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
|
||||
61
application/Espo/Controllers/WebhookEventQueueItem.php
Normal file
61
application/Espo/Controllers/WebhookEventQueueItem.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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 WebhookEventQueueItem 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();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,11 @@ namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* Bindings:
|
||||
* - `$entityType` – as of v9.1.0.
|
||||
* - `Espo\Core\AclManager`
|
||||
*/
|
||||
interface AccessChecker
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -58,7 +58,7 @@ class AccessCheckerFactory
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
@@ -77,19 +77,27 @@ class AccessCheckerFactory
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope])) {
|
||||
throw new NotImplemented("Access checker is not implemented for '{$scope}'.");
|
||||
throw new NotImplemented("Access checker is not implemented for '$scope'.");
|
||||
}
|
||||
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(AclManager $aclManager): BindingContainer
|
||||
/**
|
||||
* @param class-string<AccessChecker> $className
|
||||
*/
|
||||
private function createBindingContainer(string $className, AclManager $aclManager, string $scope): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder->bindInstance(AclManager::class, $aclManager);
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $scope);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
@@ -91,10 +91,10 @@ class GlobalRestriction
|
||||
private Metadata $metadata,
|
||||
private DataCache $dataCache,
|
||||
private FieldUtil $fieldUtil,
|
||||
Config $config
|
||||
SystemConfig $systemConfig,
|
||||
) {
|
||||
|
||||
$useCache = $config->get('useCache');
|
||||
$useCache = $systemConfig->useCache();
|
||||
|
||||
if ($useCache && $this->dataCache->has($this->cacheKey)) {
|
||||
/** @var stdClass $cachedData */
|
||||
|
||||
@@ -57,14 +57,14 @@ class Map
|
||||
public function __construct(
|
||||
Table $table,
|
||||
private DataBuilder $dataBuilder,
|
||||
private Config $config,
|
||||
private DataCache $dataCache,
|
||||
CacheKeyProvider $cacheKeyProvider
|
||||
CacheKeyProvider $cacheKeyProvider,
|
||||
Config\SystemConfig $systemConfig,
|
||||
) {
|
||||
|
||||
$this->cacheKey = $cacheKeyProvider->get();
|
||||
|
||||
if ($this->config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
|
||||
if ($systemConfig->useCache() && $this->dataCache->has($this->cacheKey)) {
|
||||
/** @var stdClass $cachedData */
|
||||
$cachedData = $this->dataCache->get($this->cacheKey);
|
||||
|
||||
@@ -72,7 +72,7 @@ class Map
|
||||
} else {
|
||||
$this->data = $this->dataBuilder->build($table);
|
||||
|
||||
if ($this->config->get('useCache')) {
|
||||
if ($systemConfig->useCache()) {
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,9 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
/**
|
||||
* Bindings:
|
||||
* - `$entityType` – as of v9.1.0.
|
||||
* - `Espo\Core\AclManager`
|
||||
*/
|
||||
interface OwnershipChecker {}
|
||||
|
||||
@@ -58,7 +58,7 @@ class OwnershipCheckerFactory
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
@@ -83,7 +83,10 @@ class OwnershipCheckerFactory
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(AclManager $aclManager): BindingContainer
|
||||
/**
|
||||
* @param class-string<OwnershipChecker> $className
|
||||
*/
|
||||
private function createBindingContainer(string $className, AclManager $aclManager, string $scope): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
@@ -91,6 +94,10 @@ class OwnershipCheckerFactory
|
||||
|
||||
$binder->bindInstance(AclManager::class, $aclManager);
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $scope);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,14 @@
|
||||
|
||||
namespace Espo\Core\Acl\Table;
|
||||
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Acl\FieldData;
|
||||
use Espo\Core\Acl\ScopeData;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -96,7 +95,7 @@ class DefaultTable implements Table
|
||||
private RoleListProvider $roleListProvider,
|
||||
CacheKeyProvider $cacheKeyProvider,
|
||||
protected User $user,
|
||||
Config $config,
|
||||
SystemConfig $systemConfig,
|
||||
protected Metadata $metadata,
|
||||
DataCache $dataCache,
|
||||
) {
|
||||
@@ -116,7 +115,7 @@ class DefaultTable implements Table
|
||||
|
||||
$this->cacheKey = $cacheKeyProvider->get();
|
||||
|
||||
if ($config->get('useCache') && $dataCache->has($this->cacheKey)) {
|
||||
if ($systemConfig->useCache() && $dataCache->has($this->cacheKey)) {
|
||||
/** @var stdClass $cachedData */
|
||||
$cachedData = $dataCache->get($this->cacheKey);
|
||||
|
||||
@@ -124,7 +123,7 @@ class DefaultTable implements Table
|
||||
} else {
|
||||
$this->load();
|
||||
|
||||
if ($config->get('useCache')) {
|
||||
if ($systemConfig->useCache()) {
|
||||
$dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
@@ -219,7 +218,7 @@ class DefaultTable implements Table
|
||||
$this->applyAdminMandatory($aclTable, $fieldTable);
|
||||
}
|
||||
|
||||
foreach ($aclTable as $scope => $data) {
|
||||
foreach (get_object_vars($aclTable) as $scope => $data) {
|
||||
if (is_string($data) && isset($aclTable->$data)) {
|
||||
$aclTable->$scope = $aclTable->$data;
|
||||
}
|
||||
|
||||
@@ -308,11 +308,12 @@ class AclManager
|
||||
|
||||
$checker = $this->getAccessChecker($scope);
|
||||
|
||||
/** @var non-falsy-string $methodName */
|
||||
$methodName = 'checkEntity' . ucfirst($action);
|
||||
|
||||
$interface = $this->entityActionInterfaceMap[$action] ?? null;
|
||||
|
||||
if ($interface && $checker instanceof $interface) {
|
||||
if ($interface && $checker instanceof $interface && method_exists($checker, $methodName)) {
|
||||
return $checker->$methodName($user, $entity, $data);
|
||||
}
|
||||
|
||||
@@ -322,7 +323,7 @@ class AclManager
|
||||
/**
|
||||
* Check 'read' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity): bool
|
||||
{
|
||||
|
||||
@@ -47,6 +47,9 @@ use Espo\Entities\User;
|
||||
use Espo\Tools\Currency\Conversion\EntityConverterFactory;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ConvertCurrency implements Action
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Espo\Core\Action;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
namespace Espo\Core\Api;
|
||||
|
||||
use Espo\Core\Api\Route\RouteParamsFetcher;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\Route as RouteUtil;
|
||||
use Espo\Core\Utils\Log;
|
||||
|
||||
@@ -54,7 +54,7 @@ class Starter
|
||||
private RouteParamsFetcher $routeParamsFetcher,
|
||||
private MiddlewareProvider $middlewareProvider,
|
||||
private Log $log,
|
||||
private Config $config,
|
||||
private SystemConfig $systemConfig,
|
||||
?string $routeCacheFile = null
|
||||
) {
|
||||
$this->routeCacheFile = $routeCacheFile ?? $this->routeCacheFile;
|
||||
@@ -64,7 +64,7 @@ class Starter
|
||||
{
|
||||
$slim = SlimAppFactory::create();
|
||||
|
||||
if ($this->config->get('useCache')) {
|
||||
if ($this->systemConfig->useCache()) {
|
||||
$slim->getRouteCollector()->setCacheFile($this->routeCacheFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ class Util
|
||||
|
||||
public function obtainIpFromRequest(Request $request): ?string
|
||||
{
|
||||
// Do not add support of any more parameters here.
|
||||
|
||||
$param = $this->config->get('ipAddressServerParam') ?? 'REMOTE_ADDR';
|
||||
|
||||
return $request->getServerParam($param);
|
||||
|
||||
@@ -35,7 +35,7 @@ use SensitiveParameter;
|
||||
/**
|
||||
* An auth token data. Used for auth token creation.
|
||||
*
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
|
||||
@@ -159,17 +159,6 @@ 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')) {
|
||||
$randomValue = openssl_random_pseudo_bytes($length);
|
||||
|
||||
return bin2hex($randomValue);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Could not generate token.");
|
||||
return bin2hex(random_bytes($length));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ class Authentication
|
||||
|
||||
if (!$user->isAdmin() && $this->configDataProvider->isMaintenanceMode()) {
|
||||
throw ServiceUnavailable::createWithBody(
|
||||
"Application is in maintenance mod1e.",
|
||||
"Application is in maintenance mode.",
|
||||
Body::create()
|
||||
->withMessage($this->language->translateLabel('maintenanceModeError', 'messages'))
|
||||
);
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Authentication;
|
||||
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class AuthenticationData
|
||||
{
|
||||
@@ -87,7 +89,7 @@ class AuthenticationData
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPassword(?string $password): self
|
||||
public function withPassword(#[SensitiveParameter] ?string $password): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->password = $password;
|
||||
|
||||
@@ -34,7 +34,7 @@ use UnexpectedValueException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Rsa implements Key
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ use RuntimeException;
|
||||
/**
|
||||
* JWT token.
|
||||
*
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Token
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ use JsonException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Header
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ use JsonException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Payload
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Core\Authentication\Login;
|
||||
|
||||
use Espo\Core\Authentication\AuthToken\AuthToken;
|
||||
use SensitiveParameter;
|
||||
|
||||
class DataBuilder
|
||||
{
|
||||
@@ -44,7 +45,7 @@ class DataBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPassword(?string $password): self
|
||||
public function setPassword(#[SensitiveParameter] ?string $password): self
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
namespace Espo\Core\Authentication\Login;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class MetadataParams
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ class Hmac implements Login
|
||||
{
|
||||
$authString = base64_decode($request->getHeader('X-Hmac-Authorization') ?? '');
|
||||
|
||||
list($apiKey, $hash) = explode(':', $authString, 2);
|
||||
[$apiKey, $hash] = explode(':', $authString, 2);
|
||||
|
||||
if (!$apiKey) {
|
||||
return Result::fail(FailReason::WRONG_CREDENTIALS);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
namespace Espo\Core\Authentication\Logout;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
namespace Espo\Core\Authentication\Logout;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Result
|
||||
{
|
||||
|
||||
@@ -104,6 +104,11 @@ class ConfigDataProvider
|
||||
return $this->object->get('oidcTokenEndpoint');
|
||||
}
|
||||
|
||||
public function getUserInfoEndpoint(): ?string
|
||||
{
|
||||
return $this->object->get('oidcUserInfoEndpoint');
|
||||
}
|
||||
|
||||
public function getJwksEndpoint(): ?string
|
||||
{
|
||||
return $this->object->get('oidcJwksEndpoint');
|
||||
|
||||
@@ -33,7 +33,7 @@ use Espo\Core\Authentication\Jwt\Exceptions\UnsupportedKey;
|
||||
use Espo\Core\Authentication\Jwt\Key;
|
||||
use Espo\Core\Authentication\Jwt\KeyFactory;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\Log;
|
||||
@@ -48,10 +48,10 @@ class KeysProvider
|
||||
|
||||
public function __construct(
|
||||
private DataCache $dataCache,
|
||||
private Config $config,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private KeyFactory $factory,
|
||||
private Log $log
|
||||
private Log $log,
|
||||
private SystemConfig $systemConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -147,7 +147,7 @@ class KeysProvider
|
||||
*/
|
||||
private function getRawFromCache(): ?array
|
||||
{
|
||||
if (!$this->config->get('useCache')) {
|
||||
if (!$this->systemConfig->useCache()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ class KeysProvider
|
||||
*/
|
||||
private function storeRawToCache(array $raw): void
|
||||
{
|
||||
if (!$this->config->get('useCache')) {
|
||||
if (!$this->systemConfig->useCache()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ use Espo\Core\Authentication\Logins\Espo;
|
||||
use Espo\Core\Authentication\Jwt\Exceptions\Invalid;
|
||||
use Espo\Core\Authentication\Jwt\Exceptions\SignatureNotVerified;
|
||||
use Espo\Core\Authentication\Jwt\Validator;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
|
||||
use Espo\Core\Authentication\Result;
|
||||
use Espo\Core\Authentication\Result\FailReason;
|
||||
use Espo\Core\Utils\Json;
|
||||
@@ -45,6 +46,7 @@ use Espo\Core\Utils\Log;
|
||||
use JsonException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
use stdClass;
|
||||
|
||||
class Login implements LoginInterface
|
||||
@@ -62,7 +64,8 @@ class Login implements LoginInterface
|
||||
private Validator $validator,
|
||||
private TokenValidator $tokenValidator,
|
||||
private UserProvider $userProvider,
|
||||
private ApplicationState $applicationState
|
||||
private ApplicationState $applicationState,
|
||||
private UserInfoDataProvider $userInfoDataProvider,
|
||||
) {}
|
||||
|
||||
public function login(Data $data, Request $request): Result
|
||||
@@ -99,7 +102,8 @@ class Login implements LoginInterface
|
||||
throw new RuntimeException("No client secret.");
|
||||
}
|
||||
|
||||
[$rawToken, $failResult] = $this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
|
||||
[$rawToken, $failResult, $accessToken] =
|
||||
$this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
|
||||
|
||||
if ($failResult) {
|
||||
return $failResult;
|
||||
@@ -144,7 +148,9 @@ class Login implements LoginInterface
|
||||
return Result::fail(FailReason::DENIED);
|
||||
}
|
||||
|
||||
$user = $this->userProvider->get($tokenPayload);
|
||||
$userInfo = $this->getUserInfo($tokenPayload, $accessToken);
|
||||
|
||||
$user = $this->userProvider->get($userInfo);
|
||||
|
||||
if (!$user) {
|
||||
return Result::fail(FailReason::USER_NOT_FOUND);
|
||||
@@ -198,7 +204,7 @@ class Login implements LoginInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{?string, ?Result}
|
||||
* @return array{?string, ?Result, ?string}
|
||||
*/
|
||||
private function requestToken(
|
||||
string $endpoint,
|
||||
@@ -250,7 +256,7 @@ class Login implements LoginInterface
|
||||
|
||||
$this->log->warning(self::composeLogMessage('Token request error.', $status, $response));
|
||||
|
||||
return [null, Result::fail(FailReason::DENIED)];
|
||||
return [null, Result::fail(FailReason::DENIED), null];
|
||||
}
|
||||
|
||||
$parsedResponse = null;
|
||||
@@ -266,6 +272,7 @@ class Login implements LoginInterface
|
||||
}
|
||||
|
||||
$token = $parsedResponse->id_token ?? null;
|
||||
$accessToken = $parsedResponse->access_token ?? null;
|
||||
|
||||
if (!$token || !is_string($token)) {
|
||||
$this->log->error(self::composeLogMessage('Bad token response.', $status, $response));
|
||||
@@ -273,7 +280,7 @@ class Login implements LoginInterface
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return [$token, null];
|
||||
return [$token, null, $accessToken];
|
||||
}
|
||||
|
||||
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
|
||||
@@ -295,4 +302,21 @@ class Login implements LoginInterface
|
||||
$this->tokenValidator->validateFields($token);
|
||||
$this->tokenValidator->validateSignature($token);
|
||||
}
|
||||
|
||||
private function getUserInfo(Token\Payload $payload, #[SensitiveParameter] ?string $accessToken): UserInfo
|
||||
{
|
||||
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
|
||||
|
||||
if (!$endpoint) {
|
||||
return new UserInfo($payload, []);
|
||||
}
|
||||
|
||||
if (!$accessToken) {
|
||||
throw new RuntimeException("OIDC: No access token received.");
|
||||
}
|
||||
|
||||
$data = $this->userInfoDataProvider->get($accessToken);
|
||||
|
||||
return new UserInfo($payload, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\Core\Authentication\Oidc;
|
||||
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\Log;
|
||||
use JsonException;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class UserInfoDataProvider
|
||||
{
|
||||
private const REQUEST_TIMEOUT = 10;
|
||||
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function get(#[SensitiveParameter] string $accessToken): array
|
||||
{
|
||||
return $this->load($accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function load(#[SensitiveParameter] string $accessToken): array
|
||||
{
|
||||
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
|
||||
|
||||
if (!$endpoint) {
|
||||
throw new RuntimeException("No userinfo endpoint.");
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => self::REQUEST_TIMEOUT,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Accept: application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
/** @var string|false $response */
|
||||
$response = curl_exec($curl);
|
||||
$error = curl_error($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($response === false) {
|
||||
$response = '';
|
||||
}
|
||||
|
||||
if ($error || is_int($status) && ($status >= 400 && $status < 500)) {
|
||||
$this->log->error(self::composeLogMessage('UserInfo response error.', $status, $response));
|
||||
|
||||
throw new RuntimeException("OIDC: Userinfo request error.");
|
||||
}
|
||||
|
||||
$parsedResponse = null;
|
||||
|
||||
try {
|
||||
$parsedResponse = Json::decode($response, true);
|
||||
} catch (JsonException) {}
|
||||
|
||||
if (!is_array($parsedResponse)) {
|
||||
throw new RuntimeException("OIDC: Bad userinfo response.");
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
|
||||
{
|
||||
if ($status === null) {
|
||||
return "OIDC: $text";
|
||||
}
|
||||
|
||||
return "OIDC: $text; Status: $status; Response: $response";
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Authentication\Oidc;
|
||||
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface UserProvider
|
||||
{
|
||||
public function get(Payload $payload): ?User;
|
||||
public function get(UserInfo $userInfo): ?User;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
namespace Espo\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\ApplicationState;
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DefaultUserProvider implements UserProvider
|
||||
@@ -44,30 +44,30 @@ class DefaultUserProvider implements UserProvider
|
||||
private Sync $sync,
|
||||
private UserRepository $userRepository,
|
||||
private ApplicationState $applicationState,
|
||||
private Log $log
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
public function get(Payload $payload): ?User
|
||||
public function get(UserInfo $userInfo): ?User
|
||||
{
|
||||
$user = $this->findUser($payload);
|
||||
$user = $this->findUser($userInfo);
|
||||
|
||||
if ($user === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$this->syncUser($user, $payload);
|
||||
$this->syncUser($user, $userInfo);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
return $this->tryToCreateUser($payload);
|
||||
return $this->tryToCreateUser($userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User|false|null
|
||||
*/
|
||||
private function findUser(Payload $payload): User|bool|null
|
||||
private function findUser(UserInfo $userInfo): User|bool|null
|
||||
{
|
||||
$usernameClaim = $this->configDataProvider->getUsernameClaim();
|
||||
|
||||
@@ -75,10 +75,10 @@ class DefaultUserProvider implements UserProvider
|
||||
throw new RuntimeException("No username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("No username claim `$usernameClaim` in token.");
|
||||
throw new RuntimeException("No username claim `$usernameClaim` in token and userinfo.");
|
||||
}
|
||||
|
||||
$username = $this->sync->normalizeUsername($username);
|
||||
@@ -136,7 +136,7 @@ class DefaultUserProvider implements UserProvider
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function tryToCreateUser(Payload $payload): ?User
|
||||
private function tryToCreateUser(UserInfo $userInfo): ?User
|
||||
{
|
||||
if (!$this->configDataProvider->createUser()) {
|
||||
return null;
|
||||
@@ -148,16 +148,16 @@ class DefaultUserProvider implements UserProvider
|
||||
throw new RuntimeException("Could not create a user. No OIDC username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("Could not create a user. No username claim returned in token.");
|
||||
throw new RuntimeException("Could not create a user. No username claim in token and userinfo.");
|
||||
}
|
||||
|
||||
return $this->sync->createUser($payload);
|
||||
return $this->sync->createUser($userInfo);
|
||||
}
|
||||
|
||||
private function syncUser(User $user, Payload $payload): void
|
||||
private function syncUser(User $user, UserInfo $userInfo): void
|
||||
{
|
||||
if (
|
||||
!$this->configDataProvider->sync() &&
|
||||
@@ -166,6 +166,6 @@ class DefaultUserProvider implements UserProvider
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sync->syncUser($user, $payload);
|
||||
$this->sync->syncUser($user, $userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Espo\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
|
||||
use Espo\Core\ApplicationState;
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
|
||||
use Espo\Core\Field\LinkMultiple;
|
||||
use Espo\Core\Name\Field;
|
||||
@@ -50,30 +49,31 @@ class Sync
|
||||
private UserRepository $userRepository,
|
||||
private PasswordHash $passwordHash,
|
||||
private AclCacheClearer $aclCacheClearer,
|
||||
private ApplicationState $applicationState
|
||||
private ApplicationState $applicationState,
|
||||
) {}
|
||||
|
||||
public function createUser(Payload $payload): User
|
||||
public function createUser(UserInfo $userInfo): User
|
||||
{
|
||||
$username = $this->getUsernameFromToken($payload);
|
||||
$username = $this->getUsernameFromToken($userInfo);
|
||||
|
||||
$this->usernameValidator->validate($username);
|
||||
|
||||
$user = $this->userRepository->getNew();
|
||||
|
||||
$user->set([
|
||||
'type' => User::TYPE_REGULAR,
|
||||
'userName' => $username,
|
||||
$user->setType(User::TYPE_REGULAR);
|
||||
$user->setUserName($username);
|
||||
|
||||
$user->setMultiple([
|
||||
'password' => $this->passwordHash->hash(Util::generatePassword(10, 4, 2, true)),
|
||||
]);
|
||||
|
||||
$user->set($this->getUserDataFromToken($payload));
|
||||
$user->set($this->getUserTeamsDataFromToken($payload));
|
||||
$user->set($this->getUserDataFromToken($userInfo));
|
||||
$user->set($this->getUserTeamsDataFromToken($userInfo));
|
||||
|
||||
if ($this->applicationState->isPortal()) {
|
||||
$portalId = $this->applicationState->getPortalId();
|
||||
|
||||
$user->set('type', User::TYPE_PORTAL);
|
||||
$user->setType(User::TYPE_PORTAL);
|
||||
$user->setPortals(LinkMultiple::create()->withAddedId($portalId));
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class Sync
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function syncUser(User $user, Payload $payload): void
|
||||
public function syncUser(User $user, UserInfo $payload): void
|
||||
{
|
||||
$username = $this->getUsernameFromToken($payload);
|
||||
|
||||
@@ -116,19 +116,19 @@ class Sync
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getUserDataFromToken(Payload $payload): array
|
||||
private function getUserDataFromToken(UserInfo $userInfo): array
|
||||
{
|
||||
return [
|
||||
'emailAddress' => $payload->get('email'),
|
||||
'phoneNumber' => $payload->get('phone_number'),
|
||||
'emailAddress' => $userInfo->get('email'),
|
||||
'phoneNumber' => $userInfo->get('phone_number'),
|
||||
'emailAddressData' => null,
|
||||
'phoneNumberData' => null,
|
||||
'firstName' => $payload->get('given_name'),
|
||||
'lastName' => $payload->get('family_name'),
|
||||
'middle_name' => $payload->get('middle_name'),
|
||||
'firstName' => $userInfo->get('given_name'),
|
||||
'lastName' => $userInfo->get('family_name'),
|
||||
'middle_name' => $userInfo->get('middle_name'),
|
||||
'gender' =>
|
||||
in_array($payload->get('gender'), ['male', 'female']) ?
|
||||
ucfirst($payload->get('gender') ?? '') :
|
||||
in_array($userInfo->get('gender'), ['male', 'female']) ?
|
||||
ucfirst($userInfo->get('gender') ?? '') :
|
||||
null,
|
||||
];
|
||||
}
|
||||
@@ -136,14 +136,14 @@ class Sync
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getUserTeamsDataFromToken(Payload $payload): array
|
||||
private function getUserTeamsDataFromToken(UserInfo $userInfo): array
|
||||
{
|
||||
return [
|
||||
'teamsIds' => $this->getTeamIdList($payload),
|
||||
'teamsIds' => $this->getTeamIdList($userInfo),
|
||||
];
|
||||
}
|
||||
|
||||
private function getUsernameFromToken(Payload $payload): string
|
||||
private function getUsernameFromToken(UserInfo $userInfo): string
|
||||
{
|
||||
$usernameClaim = $this->configDataProvider->getUsernameClaim();
|
||||
|
||||
@@ -151,7 +151,7 @@ class Sync
|
||||
throw new RuntimeException("No OIDC username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("No username claim returned in token.");
|
||||
@@ -167,7 +167,7 @@ class Sync
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getTeamIdList(Payload $payload): array
|
||||
private function getTeamIdList(UserInfo $userInfo): array
|
||||
{
|
||||
$idList = $this->configDataProvider->getTeamIds() ?? [];
|
||||
$columns = $this->configDataProvider->getTeamColumns() ?? (object) [];
|
||||
@@ -176,7 +176,7 @@ class Sync
|
||||
return [];
|
||||
}
|
||||
|
||||
$groupList = $this->getGroups($payload);
|
||||
$groupList = $this->getGroups($userInfo);
|
||||
|
||||
$resultIdList = [];
|
||||
|
||||
@@ -194,7 +194,7 @@ class Sync
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getGroups(Payload $payload): array
|
||||
private function getGroups(UserInfo $userInfo): array
|
||||
{
|
||||
$groupClaim = $this->configDataProvider->getGroupClaim();
|
||||
|
||||
@@ -202,7 +202,7 @@ class Sync
|
||||
return [];
|
||||
}
|
||||
|
||||
$value = $payload->get($groupClaim);
|
||||
$value = $userInfo->get($groupClaim);
|
||||
|
||||
if (!$value) {
|
||||
return [];
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 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\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
|
||||
class UserInfo
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function __construct(
|
||||
private Payload $payload,
|
||||
private array $data,
|
||||
) {}
|
||||
|
||||
public function get(string $name): mixed
|
||||
{
|
||||
return $this->payload->get($name) ?? $this->data[$name] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ use stdClass;
|
||||
/**
|
||||
* An authentication result.
|
||||
*
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Result
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Espo\Core\Authentication\Result;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ class ContextualBinder
|
||||
*
|
||||
* @template T of object
|
||||
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
|
||||
* @param class-string<T> $serviceName A service name.
|
||||
* @param string $serviceName A service name.
|
||||
*/
|
||||
public function bindService(string|NamedClassKey $key, string $serviceName): self
|
||||
{
|
||||
|
||||
@@ -73,6 +73,7 @@ class EspoBindingLoader implements BindingLoader
|
||||
|
||||
private function loadCustom(Binder $binder): void
|
||||
{
|
||||
/** @var class-string<BindingProcessor>|string $className */
|
||||
$className = 'Espo\\Custom\\Binding';
|
||||
|
||||
if (!class_exists($className)) {
|
||||
|
||||
@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
|
||||
/**
|
||||
* Command parameters.
|
||||
*
|
||||
* @immutable
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
|
||||
@@ -248,11 +248,10 @@ class Extension implements Command
|
||||
private function printList(IO $io): void
|
||||
{
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepository(ExtensionEntity::ENTITY_TYPE)
|
||||
->getRDBRepositoryByClass(ExtensionEntity::class)
|
||||
->find();
|
||||
|
||||
/** @noinspection PhpParamsInspection */
|
||||
$count = is_countable($collection) ? count($collection) : iterator_count($collection);
|
||||
$count = count($collection);
|
||||
|
||||
/** @noinspection PhpIfWithCommonPartsInspection */
|
||||
if ($count === 0) {
|
||||
@@ -268,9 +267,9 @@ class Extension implements Command
|
||||
$io->writeLine("");
|
||||
|
||||
foreach ($collection as $extension) {
|
||||
$isInstalled = $extension->get('isInstalled');
|
||||
$isInstalled = $extension->isInstalled();
|
||||
|
||||
$io->writeLine(' Name: ' . $extension->get(Field::NAME));
|
||||
$io->writeLine(' Name: ' . $extension->getName());
|
||||
$io->writeLine(' ID: ' . $extension->getId());
|
||||
$io->writeLine(' Version: ' . $extension->getVersion());
|
||||
$io->writeLine(' Installed: ' . ($isInstalled ? 'yes' : 'no'));
|
||||
|
||||
@@ -151,6 +151,8 @@ class Upgrade implements Command
|
||||
if (!$packageFile) {
|
||||
fwrite(STDOUT, "Error: Unable to download upgrade package.\n");
|
||||
|
||||
$io->setExitStatus(1);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -177,6 +179,8 @@ class Upgrade implements Command
|
||||
|
||||
fwrite(STDOUT, $errorMessage . "\n");
|
||||
|
||||
$io->setExitStatus(1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -491,13 +495,19 @@ class Upgrade implements Command
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return realpath($localFilePath);
|
||||
$path = realpath($localFilePath);
|
||||
|
||||
assert($path !== false);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function isShellEnabled(): bool
|
||||
{
|
||||
if (!function_exists('exec') || !is_callable('shell_exec')) {
|
||||
if (
|
||||
!function_exists('exec') ||
|
||||
!is_callable('shell_exec') /** @phpstan-ignore-line */
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -512,7 +522,7 @@ class Upgrade implements Command
|
||||
|
||||
private function getCurrentVersion(): ?string
|
||||
{
|
||||
$configData = include "data/config.php";
|
||||
$configData = include "data/config.php"; /** @phpstan-ignore-line */
|
||||
|
||||
if (!$configData) {
|
||||
return null;
|
||||
|
||||
@@ -36,16 +36,12 @@ use Espo\Core\Utils\Config;
|
||||
|
||||
class Version implements Command
|
||||
{
|
||||
public function __construct(private Config $config)
|
||||
public function __construct(private Config\SystemConfig $config)
|
||||
{}
|
||||
|
||||
public function run(Params $params, IO $io): void
|
||||
{
|
||||
$version = $this->config->get('version');
|
||||
|
||||
if (is_null($version)) {
|
||||
return;
|
||||
}
|
||||
$version = $this->config->getVersion();
|
||||
|
||||
$io->writeLine($version);
|
||||
}
|
||||
|
||||
@@ -187,13 +187,19 @@ class Container implements ContainerInterface
|
||||
}
|
||||
|
||||
if ($id === self::ID_CONTAINER) {
|
||||
$this->classCache[$id] = new ReflectionClass(Container::class);
|
||||
/** @var ReflectionClass<object> $object */
|
||||
$object = new ReflectionClass(Container::class);
|
||||
|
||||
$this->classCache[$id] = $object;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($id === self::ID_INJECTABLE_FACTORY) {
|
||||
$this->classCache[$id] = new ReflectionClass(InjectableFactory::class);
|
||||
/** @var ReflectionClass<object> $object */
|
||||
$object = new ReflectionClass(InjectableFactory::class);
|
||||
|
||||
$this->classCache[$id] = $object;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,12 +168,14 @@ class ContainerBuilder
|
||||
)
|
||||
);
|
||||
|
||||
/** @var FileManager $fileManager */
|
||||
$fileManager = $this->services['fileManager'] ?? (
|
||||
new $this->fileManagerClassName(
|
||||
$config->get('defaultPermissions')
|
||||
)
|
||||
);
|
||||
|
||||
/** @var DataCache $dataCache */
|
||||
$dataCache = $this->services['dataCache'] ?? (
|
||||
new $this->dataCacheClassName($fileManager)
|
||||
);
|
||||
@@ -185,10 +187,13 @@ class ContainerBuilder
|
||||
new $this->moduleClassName($fileManager, $dataCache, $useCache)
|
||||
);
|
||||
|
||||
$systemConfig = new Config\SystemConfig($config);
|
||||
|
||||
$this->services['config'] = $config;
|
||||
$this->services['fileManager'] = $fileManager;
|
||||
$this->services['dataCache'] = $dataCache;
|
||||
$this->services['module'] = $module;
|
||||
$this->services['systemConfig'] = $systemConfig;
|
||||
|
||||
$bindingLoader = $this->bindingLoader ?? (
|
||||
new EspoBindingLoader($module)
|
||||
|
||||
@@ -39,8 +39,8 @@ class ContainerConfiguration implements Configuration
|
||||
{
|
||||
/**
|
||||
* Log must be loaded before anything.
|
||||
* @phpstan-ignore-next-line
|
||||
* @noinspection PhpPropertyOnlyWrittenInspection
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private Log $log;
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ abstract class Base
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var User;
|
||||
* @var User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var Acl;
|
||||
* @var Acl
|
||||
*/
|
||||
protected $acl;
|
||||
|
||||
@@ -161,12 +161,11 @@ abstract class Base
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void;
|
||||
* @return void
|
||||
* @deprecated
|
||||
*/
|
||||
protected function checkControllerAccess()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Espo\Core\Controllers;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\NotFoundSilent;
|
||||
@@ -65,12 +64,9 @@ class Record extends RecordBase
|
||||
|
||||
$searchParams = $this->fetchSearchParamsFromRequest($request);
|
||||
|
||||
$recordCollection = $this->getRecordService()->findLinked($id, $link, $searchParams);
|
||||
$result = $this->getRecordService()->findLinked($id, $link, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $recordCollection->getTotal(),
|
||||
'list' => $recordCollection->getValueMapList(),
|
||||
];
|
||||
return $result->toApiOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ use Espo\Core\Record\ServiceContainer as RecordServiceContainer;
|
||||
use Espo\Core\Record\SearchParamsFetcher;
|
||||
use Espo\Core\Record\CreateParamsFetcher;
|
||||
use Espo\Core\Record\ReadParamsFetcher;
|
||||
use Espo\Core\Record\UpdateContext;
|
||||
use Espo\Core\Record\UpdateParamsFetcher;
|
||||
use Espo\Core\Record\DeleteParamsFetcher;
|
||||
use Espo\Core\Record\FindParamsFetcher;
|
||||
@@ -222,8 +223,16 @@ class RecordBase extends Base implements
|
||||
|
||||
$params = $this->updateParamsFetcher->fetch($request);
|
||||
|
||||
$context = new UpdateContext();
|
||||
|
||||
$params = $params->withContext($context);
|
||||
|
||||
$entity = $this->getRecordService()->update($id, $data, $params);
|
||||
|
||||
if ($context->linkUpdated) {
|
||||
$response->setHeader('X-Record-Link-Updated', 'true');
|
||||
}
|
||||
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
@@ -246,10 +255,7 @@ class RecordBase extends Base implements
|
||||
|
||||
$recordCollection = $this->getRecordService()->find($searchParams, $findParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $recordCollection->getTotal(),
|
||||
'list' => $recordCollection->getValueMapList(),
|
||||
];
|
||||
return $recordCollection->toApiOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,15 +63,12 @@ class RecordTree extends Record
|
||||
return (object) $this->actionListTree($request->getRouteParams(), $request->getParsedBody(), $request);
|
||||
}
|
||||
|
||||
$where = $request->getQueryParams()['where'] ?? null;
|
||||
$selectParams = $this->fetchSearchParamsFromRequest($request);
|
||||
|
||||
$parentId = $request->getQueryParam('parentId');
|
||||
$maxDepth = $request->getQueryParam('maxDepth');
|
||||
$onlyNotEmpty = (bool) $request->getQueryParam('onlyNotEmpty');
|
||||
|
||||
if ($where !== null && !is_array($where)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if ($maxDepth !== null) {
|
||||
$maxDepth = (int) $maxDepth;
|
||||
}
|
||||
@@ -79,7 +76,7 @@ class RecordTree extends Record
|
||||
$collection = $this->getRecordTreeService()->getTree(
|
||||
$parentId,
|
||||
[
|
||||
'where' => $where,
|
||||
'where' => $selectParams->getWhere(),
|
||||
'onlyNotEmpty' => $onlyNotEmpty,
|
||||
],
|
||||
$maxDepth
|
||||
|
||||
@@ -35,6 +35,11 @@ class CalculatorUtil
|
||||
{
|
||||
private const SCALE = 14;
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg1
|
||||
* @param numeric-string $arg2
|
||||
* @return numeric-string
|
||||
*/
|
||||
public static function add(string $arg1, string $arg2): string
|
||||
{
|
||||
if (!function_exists('bcadd')) {
|
||||
@@ -50,6 +55,11 @@ class CalculatorUtil
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg1
|
||||
* @param numeric-string $arg2
|
||||
* @return numeric-string
|
||||
*/
|
||||
public static function subtract(string $arg1, string $arg2): string
|
||||
{
|
||||
if (!function_exists('bcsub')) {
|
||||
@@ -65,6 +75,11 @@ class CalculatorUtil
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg1
|
||||
* @param numeric-string $arg2
|
||||
* @return numeric-string
|
||||
*/
|
||||
public static function multiply(string $arg1, string $arg2): string
|
||||
{
|
||||
if (!function_exists('bcmul')) {
|
||||
@@ -80,6 +95,11 @@ class CalculatorUtil
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg1
|
||||
* @param numeric-string $arg2
|
||||
* @return numeric-string
|
||||
*/
|
||||
public static function divide(string $arg1, string $arg2): string
|
||||
{
|
||||
if (!function_exists('bcdiv')) {
|
||||
@@ -88,20 +108,23 @@ class CalculatorUtil
|
||||
);
|
||||
}
|
||||
|
||||
/** @var ?string $result */
|
||||
$result = bcdiv(
|
||||
$arg1,
|
||||
$arg2,
|
||||
self::SCALE
|
||||
);
|
||||
|
||||
if ($result === null) {
|
||||
if ($result === null) { /** @phpstan-ignore-line */
|
||||
throw new DivisionByZeroError();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg
|
||||
* @return numeric-string
|
||||
*/
|
||||
public static function round(string $arg, int $precision = 0): string
|
||||
{
|
||||
if (!function_exists('bcadd')) {
|
||||
@@ -114,6 +137,8 @@ class CalculatorUtil
|
||||
$addition = '-' . $addition;
|
||||
}
|
||||
|
||||
assert(is_numeric($addition));
|
||||
|
||||
return bcadd(
|
||||
$arg,
|
||||
$addition,
|
||||
@@ -121,6 +146,10 @@ class CalculatorUtil
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $arg1
|
||||
* @param numeric-string $arg2
|
||||
*/
|
||||
public static function compare(string $arg1, string $arg2): int
|
||||
{
|
||||
if (!function_exists('bccomp')) {
|
||||
|
||||
@@ -100,6 +100,10 @@ class Converter
|
||||
return $this->convert($value, $targetCurrencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $amount
|
||||
* @return numeric-string
|
||||
*/
|
||||
private function convertAmount(string $amount, float $rate, float $targetRate): string
|
||||
{
|
||||
return CalculatorUtil::divide(
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Utils\Crypt;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait CryptSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\DataManager;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait DataManagerSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Mail\EmailSender as EmailSender;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait EmailSenderSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Htmlizer\HtmlizerFactory as HtmlizerFactory;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait HtmlizerFactorySetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Utils\Language;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait LanguageSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Entities\Preferences;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait PreferencesSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Select\SelectManagerFactory;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait SelectManagerFactorySetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Utils\TemplateFileManager;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait TemplateFileManagerSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\WebSocket\Submission;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait WebSocketSubmissionSetter
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -31,11 +31,12 @@ namespace Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Webhook\Manager;
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait WebhookManagerSetter
|
||||
{
|
||||
/**
|
||||
* @var Manager
|
||||
*/
|
||||
/** @var Manager */
|
||||
protected $webhookManager;
|
||||
|
||||
public function setWebhookManager(Manager $webhookManager): void
|
||||
|
||||
@@ -71,6 +71,10 @@ class Person extends Entity
|
||||
{
|
||||
$this->setInContainer('lastName', $value);
|
||||
|
||||
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->helper->formatPersonName($this, 'name');
|
||||
|
||||
$this->setInContainer(Field::NAME, $name);
|
||||
@@ -84,6 +88,10 @@ class Person extends Entity
|
||||
{
|
||||
$this->setInContainer('firstName', $value);
|
||||
|
||||
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->helper->formatPersonName($this, 'name');
|
||||
|
||||
$this->setInContainer(Field::NAME, $name);
|
||||
@@ -97,6 +105,10 @@ class Person extends Entity
|
||||
{
|
||||
$this->setInContainer('middleName', $value);
|
||||
|
||||
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->helper->formatPersonName($this, 'name');
|
||||
|
||||
$this->setInContainer(Field::NAME, $name);
|
||||
@@ -150,27 +162,27 @@ class Person extends Entity
|
||||
return $this->get('middleName');
|
||||
}
|
||||
|
||||
public function setFirstName(?string $firstName): self
|
||||
public function setFirstName(?string $firstName): static
|
||||
{
|
||||
return $this->set('firstName', $firstName);
|
||||
}
|
||||
|
||||
public function setLastName(?string $lastName): self
|
||||
public function setLastName(?string $lastName): static
|
||||
{
|
||||
return $this->set('lastName', $lastName);
|
||||
}
|
||||
|
||||
public function setMiddleName(?string $middleName): self
|
||||
public function setMiddleName(?string $middleName): static
|
||||
{
|
||||
return $this->set('middleName', $middleName);
|
||||
}
|
||||
|
||||
public function setEmailAddressGroup(EmailAddressGroup $group): self
|
||||
public function setEmailAddressGroup(EmailAddressGroup $group): static
|
||||
{
|
||||
return $this->setValueObject('emailAddress', $group);
|
||||
}
|
||||
|
||||
public function setPhoneNumberGroup(PhoneNumberGroup $group): self
|
||||
public function setPhoneNumberGroup(PhoneNumberGroup $group): static
|
||||
{
|
||||
return $this->setValueObject('phoneNumber', $group);
|
||||
}
|
||||
@@ -181,7 +193,7 @@ class Person extends Entity
|
||||
return $this->getValueObject('address');
|
||||
}
|
||||
|
||||
public function setAddress(Address $address): self
|
||||
public function setAddress(Address $address): static
|
||||
{
|
||||
return $this->setValueObject('address', $address);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoint\Traits;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @todo Remove in v10.0.
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait NotStrictAuth
|
||||
{
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoints;
|
||||
|
||||
/**
|
||||
* @deprecated Use `Espo\Core\EntryPoint\Traits\NoAuth` instead.
|
||||
* @todo Remove in v10.0.
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait NoAuth
|
||||
{
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoints;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @todo Remove in v10.0.
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait NotStrictAuth
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user