Compare commits

...

304 Commits
7.0.1 ... 7.0.9

Author SHA1 Message Date
Yuri Kuznetsov
b22464c02f v 2022-01-21 10:58:42 +02:00
Yuri Kuznetsov
5dcfbf00d2 fix email address 2022-01-11 13:03:48 +02:00
Yuri Kuznetsov
f4753f773f modal backdrop fix 2022-01-11 12:22:51 +02:00
Yuri Kuznetsov
a6593f574e fix 2022-01-06 14:26:26 +02:00
Yuri Kuznetsov
bff4e859cd fix notice 2022-01-06 14:07:57 +02:00
Yuri Kuznetsov
d725cce8a7 fix email to case attachments copy 2022-01-05 17:14:21 +02:00
Yuri Kuznetsov
a730f47ceb sms status update 2022-01-05 11:19:33 +02:00
Eymen Elkum
150e41a26d fix folder name (#2180)
* fix folder name

* remove trim folder name on inbound
2021-12-30 15:39:28 +02:00
Taras Machyshyn
c500b30484 Notification fix 2021-12-29 12:15:39 +02:00
Yuri Kuznetsov
5266e8eeba email address fix 2021-12-28 18:31:05 +02:00
Yuri Kuznetsov
e161b0da19 fix list expanded dd 2021-12-24 11:29:23 +02:00
Yuri Kuznetsov
31a2ef63ec cs fix 2021-12-24 11:19:33 +02:00
Yuri Kuznetsov
9f932462a8 fix image 2021-12-22 16:23:17 +02:00
Yuri Kuznetsov
a615eed985 Merge branch 'master' into hotfix/7.0.9 2021-12-20 15:38:14 +02:00
Yuri Kuznetsov
df56679c4e rename themes 2021-12-20 15:37:30 +02:00
Yuri Kuznetsov
23b2e87127 fix 2021-12-19 09:42:59 +02:00
Yuri Kuznetsov
9391c68ad0 fix plural link name 2021-12-19 09:10:52 +02:00
Taras Machyshyn
8a15278af2 Installation fix 2021-12-14 16:12:22 +02:00
Yuri Kuznetsov
8f2c12cba7 remove css 2021-12-13 15:49:04 +02:00
Yuri Kuznetsov
842c028fb5 css changes 2021-12-13 15:37:16 +02:00
Yuri Kuznetsov
27aae16634 cleanup 2021-12-13 15:21:39 +02:00
Yuri Kuznetsov
0206da6d4f css fix 2021-12-13 13:09:42 +02:00
Yuri Kuznetsov
687f308b40 merge 2021-12-13 12:54:11 +02:00
Eymen Elkum
20eced4373 personal & group email account: ignore internal fields from being duplicated (#2178)
Co-authored-by: Eymen Elkum <eymen@eblasoft.com.tr>
2021-12-13 12:45:03 +02:00
Yuri Kuznetsov
7d0c793e47 kanban improvements 2021-12-13 12:36:06 +02:00
Yuri Kuznetsov
ead21ea2ee kanban improvements 2021-12-13 12:11:43 +02:00
Yuri Kuznetsov
45ef1a4de5 kanban changes 2021-12-12 13:09:35 +02:00
Yuri Kuznetsov
28b866830e kanban changes 2021-12-10 17:08:51 +02:00
Yuri Kuznetsov
4d68e35f3b fix 2021-12-09 16:50:47 +02:00
Yuri Kuznetsov
12a2069fcf kanban scroll 2021-12-09 16:34:01 +02:00
Yuri Kuznetsov
b87d2459cc kanban create 2021-12-09 14:06:08 +02:00
Yuri Kuznetsov
7c1c25577c save event 2021-12-09 12:38:58 +02:00
Yuri Kuznetsov
9d6e0ba54b cs fix 2021-12-09 12:38:58 +02:00
Yuri Kuznetsov
bd3c6867b5 cs fix 2021-12-09 12:38:58 +02:00
Taras Machyshyn
28c4b8ea05 Fix typo 2021-12-09 12:18:26 +02:00
Yuri Kuznetsov
7a90e30243 fix 2021-12-09 11:37:32 +02:00
Yuri Kuznetsov
f482c41e6c custom global bool filterse 2021-12-08 16:35:58 +02:00
Yuri Kuznetsov
06ffafba10 send invitations from modal 2021-12-08 15:50:38 +02:00
Yuri Kuznetsov
2a07124d52 cs fix 2021-12-08 15:35:28 +02:00
Yuri Kuznetsov
65247b1c69 cs fix 2021-12-08 11:59:03 +02:00
Yuri Kuznetsov
10e4b7e5b6 cs fix 2021-12-08 11:52:46 +02:00
Yuri Kuznetsov
ff3e6c34ba cs fix 2021-12-08 11:50:50 +02:00
Yuri Kuznetsov
19700cfa00 cs fix 2021-12-08 11:50:32 +02:00
Yuri Kuznetsov
d0de9e5bb9 Merge branch 'hotfix/7.0.9' 2021-12-06 10:54:11 +02:00
Yuri Kuznetsov
9861653378 types 2021-12-02 18:14:46 +02:00
Yuri Kuznetsov
0a4525b75e fix 2021-12-02 16:30:22 +02:00
Yuri Kuznetsov
1c45cadcf8 fix htmlizer order related 2021-12-02 15:33:50 +02:00
Yuri Kuznetsov
83ced0d58f orm defs: no type exception 2021-12-02 12:54:17 +02:00
Yuri Kuznetsov
6ee224ffbc fix 2021-12-01 17:23:20 +02:00
Yuri Kuznetsov
a060f0732b attachment container fix 2021-12-01 15:15:06 +02:00
Yuri Kuznetsov
4e2a824b1d fix 2021-12-01 11:24:26 +02:00
Yuri Kuznetsov
85c4848614 cleanup 2021-12-01 11:16:40 +02:00
Yuri Kuznetsov
a5ca838b7a fix typo 2021-12-01 11:12:34 +02:00
Yuri Kuznetsov
1127f4b28c languageIsGlobal param 2021-11-30 16:04:20 +02:00
Yuri Kuznetsov
e68ff3c391 fix smarty for php8.1 2021-11-29 13:46:03 +02:00
Yuri Kuznetsov
c257da0e70 accept bool 1 value, fix for php 8.1 2021-11-29 13:43:07 +02:00
Yuri Kuznetsov
7669ac90ae edit fix 2021-11-26 16:14:12 +02:00
Yuri Kuznetsov
5e7ddcad1c try check usage 2021-11-26 10:48:51 +02:00
Yuri Kuznetsov
1cf773216d cs fix 2021-11-26 10:44:35 +02:00
Yuri Kuznetsov
da3283f2ba fix link one saver 2021-11-25 17:12:04 +02:00
Yuri Kuznetsov
18d6a6767b notification message support markdown 2021-11-25 13:19:58 +02:00
Yuri Kuznetsov
f8fd222e25 multiple bool filters fix 2021-11-24 12:21:55 +02:00
Yuri Kuznetsov
fd7819f76a cs fix 2021-11-24 09:09:29 +02:00
Yuri Kuznetsov
48cfedb777 fix parent 2021-11-24 09:07:51 +02:00
Yuri Kuznetsov
9220ad8baa email to case account 2021-11-23 15:16:52 +02:00
Yuri Kuznetsov
6207c8f547 attachment take into account field level 2021-11-22 11:22:56 +02:00
Yuri Kuznetsov
b9e1660f92 comment 2021-11-22 10:58:05 +02:00
Yuri Kuznetsov
8863e7da9b Merge branch 'hotfix/7.0.9' 2021-11-19 12:41:24 +02:00
Yuri Kuznetsov
3b1462ac1a complex expression field view 2021-11-19 12:40:37 +02:00
Yuri Kuznetsov
937503ab8b formula hide entity functions if no target entity type 2021-11-18 12:53:51 +02:00
Yuri Kuznetsov
b3edfba0da fix 2021-11-17 12:19:47 +02:00
Yuri Kuznetsov
01a1d26a52 cs fix 2021-11-17 12:18:27 +02:00
Yuri Kuznetsov
4662fc47e9 xlsx export: fix link one 2021-11-16 12:15:11 +02:00
Yuri Kuznetsov
5b543d778d fix 2021-11-16 12:11:21 +02:00
Yuri Kuznetsov
798f56650d person name support fix 2021-11-13 10:34:30 +02:00
Yuri Kuznetsov
864c96fe56 fixes 2021-11-13 10:29:47 +02:00
Yuri Kuznetsov
4d5eecd680 Merge branch 'hotfix/7.0.9' 2021-11-12 12:44:19 +02:00
Taras Machyshyn
db2216adb7 Fixed PDO parameters for Dbal connection 2021-11-11 15:31:11 +02:00
Yuri Kuznetsov
ae056f4a22 type 2021-11-11 12:49:20 +02:00
Yuri Kuznetsov
5178c2fbd0 orm pdo refactoring 2021-11-11 12:47:43 +02:00
Yuri Kuznetsov
4bedaef9a8 fixes 2021-11-11 12:17:21 +02:00
Yuri Kuznetsov
42d4de77ae cs fix 2021-11-11 12:17:05 +02:00
Yuri Kuznetsov
3e0a8f0af1 fix 2021-11-11 11:40:11 +02:00
Yuri Kuznetsov
33e29e20a2 type fixes 2021-11-10 16:53:15 +02:00
Yuri Kuznetsov
d2a1e8f771 fixes 2021-11-10 16:40:33 +02:00
Yuri Kuznetsov
3a68ebcbf4 type fixes 2021-11-10 16:04:31 +02:00
Yuri Kuznetsov
f496f386ee type fixes 2021-11-10 15:44:09 +02:00
Yuri Kuznetsov
9e641e65ed type fixes 2021-11-10 15:34:24 +02:00
Yuri Kuznetsov
d34bc6d7d2 type fixes 2021-11-10 15:10:58 +02:00
Yuri Kuznetsov
7581169f87 update jquery-ui 2021-11-10 12:58:11 +02:00
Yuri Kuznetsov
7c53701df5 npm update packages 2021-11-10 12:31:57 +02:00
Yuri Kuznetsov
d273523c46 fix link multiple remove btn 2021-11-10 12:20:39 +02:00
Yuri Kuznetsov
df6745ff79 cs fix 2021-11-10 12:19:37 +02:00
Yuri Kuznetsov
9327b418bf fix 2021-11-10 11:38:04 +02:00
Yuri Kuznetsov
5430fed1d1 fix 2021-11-10 11:28:29 +02:00
Yuri Kuznetsov
10285cf3d3 phpstan level 5 2021-11-09 17:47:18 +02:00
Yuri Kuznetsov
c8bed9af32 fixes 2021-11-09 17:46:54 +02:00
Yuri Kuznetsov
7499703d77 fix tests 2021-11-09 17:20:24 +02:00
Yuri Kuznetsov
4e1f421dc1 fixes and cleanup 2021-11-09 17:13:25 +02:00
Yuri Kuznetsov
4ab2a0749c fixes 2021-11-09 16:43:27 +02:00
Yuri Kuznetsov
530ac9da18 type fixes 2021-11-09 16:38:00 +02:00
Yuri Kuznetsov
4069d38978 type fixes 2021-11-09 16:14:07 +02:00
Yuri Kuznetsov
4070b38190 update phpstan 2021-11-09 15:58:36 +02:00
Yuri Kuznetsov
718f3ddc9f type fixes 2021-11-09 15:15:40 +02:00
Yuri Kuznetsov
e4be18ac51 type fixes 2021-11-09 15:08:44 +02:00
Yuri Kuznetsov
fa63270530 type fixes 2021-11-09 13:48:04 +02:00
Yuri Kuznetsov
54286984e9 type fixes 2021-11-09 13:07:37 +02:00
Yuri Kuznetsov
523e4d2900 type fixes 2021-11-09 12:48:57 +02:00
Yuri Kuznetsov
79af0d2cc7 type fixes 2021-11-09 12:26:42 +02:00
Yuri Kuznetsov
4152ee10ea type fixes 2021-11-09 12:01:22 +02:00
Yuri Kuznetsov
c41b92d15a module fix order and test 2021-11-09 11:54:48 +02:00
Yuri Kuznetsov
5af9679751 fix user service 2021-11-09 11:24:07 +02:00
Yuri Kuznetsov
c51fcf0cb6 fix 2021-11-08 17:24:54 +02:00
Yuri Kuznetsov
41d4735502 type fixes 2021-11-08 17:14:08 +02:00
Yuri Kuznetsov
7c3964c6f5 type fixes 2021-11-08 17:09:29 +02:00
Yuri Kuznetsov
df744ba263 export fix 2021-11-08 16:40:23 +02:00
Yuri Kuznetsov
380b3dde0a return type const 2021-11-08 16:31:54 +02:00
Yuri Kuznetsov
4c696c8b21 type fixes 2021-11-08 16:05:53 +02:00
Yuri Kuznetsov
95f9206880 type fixes 2021-11-08 15:48:16 +02:00
Yuri Kuznetsov
e00557885f type fixes 2021-11-08 15:28:12 +02:00
Yuri Kuznetsov
9aaab7d726 phpstan dynamic return type 2021-11-08 15:23:34 +02:00
Yuri Kuznetsov
ab9f78d33f type fixes 2021-11-08 15:22:49 +02:00
Yuri Kuznetsov
ac57f6a39a fix types 2021-11-08 15:16:39 +02:00
Yuri Kuznetsov
49077225c6 type fixes 2021-11-08 13:47:48 +02:00
Yuri Kuznetsov
f3a13739c0 type fixes 2021-11-08 13:31:15 +02:00
Yuri Kuznetsov
ae4d37498a csv export fix 2021-11-08 12:35:03 +02:00
Yuri Kuznetsov
25a607952c fix before action methods 2021-11-08 11:03:54 +02:00
Yuri Kuznetsov
51cfbb0779 fixes 2021-11-05 13:40:32 +02:00
Yuri Kuznetsov
15c97e5d12 fixes 2021-11-05 13:26:05 +02:00
Yuri Kuznetsov
4847bc019a phpstan level 4 2021-11-05 12:54:35 +02:00
Yuri Kuznetsov
094f6e2f50 fixes 2021-11-05 12:53:54 +02:00
Yuri Kuznetsov
6cc19a52f1 fixes 2021-11-05 12:33:36 +02:00
Yuri Kuznetsov
f0237005c0 fixes 2021-11-05 12:14:56 +02:00
Yuri Kuznetsov
3aa31a830c type fixes 2021-11-05 12:12:39 +02:00
Yuri Kuznetsov
c61513cf84 fixes 2021-11-05 12:02:06 +02:00
Yuri Kuznetsov
d10b6dd9b3 fixes 2021-11-05 11:49:04 +02:00
Yuri Kuznetsov
b466f91f8a fixes 2021-11-05 11:32:03 +02:00
Yuri Kuznetsov
7081440121 type fixes 2021-11-05 11:22:59 +02:00
Yuri Kuznetsov
5042f58e30 fixes 2021-11-05 11:04:49 +02:00
Yuri Kuznetsov
beb51081d3 type fixes 2021-11-05 10:55:41 +02:00
Yuri Kuznetsov
1997da4343 fixes 2021-11-04 17:22:56 +02:00
Yuri Kuznetsov
0b3f3e67dd fixes 2021-11-04 17:14:41 +02:00
Yuri Kuznetsov
e9d7b92a6e phpstan level 3 2021-11-04 16:20:29 +02:00
Yuri Kuznetsov
334d5609b0 types 2021-11-04 16:19:43 +02:00
Yuri Kuznetsov
6e31c591bd types 2021-11-04 16:16:22 +02:00
Yuri Kuznetsov
d125d8f067 types 2021-11-04 15:54:48 +02:00
Yuri Kuznetsov
49b55236d0 types 2021-11-04 15:09:36 +02:00
Yuri Kuznetsov
093001bf95 fixes 2021-11-04 13:50:10 +02:00
Yuri Kuznetsov
3445f87e0c fix 2021-11-04 13:34:43 +02:00
Yuri Kuznetsov
8d3ba52501 injectable factory type hint 2021-11-04 13:33:16 +02:00
Yuri Kuznetsov
82a7590a2a type fixes 2021-11-04 13:19:53 +02:00
Yuri Kuznetsov
773df7aedd type fixes 2021-11-04 13:00:40 +02:00
Yuri Kuznetsov
424ca82b15 type fixes 2021-11-04 12:50:56 +02:00
Yuri Kuznetsov
e5df6ad7bf type fixes 2021-11-04 12:48:31 +02:00
Yuri Kuznetsov
d81f8f7bed type fixes 2021-11-04 12:35:39 +02:00
Yuri Kuznetsov
7203b652fa type fixes 2021-11-04 12:17:37 +02:00
Yuri Kuznetsov
5d5268c2c7 type fixes 2021-11-04 11:55:57 +02:00
Yuri Kuznetsov
b568e96407 type fixes 2021-11-04 11:36:52 +02:00
Yuri Kuznetsov
1dda881422 type fixes 2021-11-04 11:14:25 +02:00
Yuri Kuznetsov
17d2791d40 type fixes 2021-11-04 10:59:48 +02:00
Yuri Kuznetsov
9469b66c61 fix 2021-11-04 08:19:52 +02:00
Yuri Kuznetsov
bbc16b942e type fixes 2021-11-03 21:16:03 +02:00
Yuri Kuznetsov
2cb235f49c type fixes 2021-11-03 21:07:10 +02:00
Yuri Kuznetsov
e2a7a439d2 type fixes 2021-11-03 18:38:20 +02:00
Yuri Kuznetsov
2c19ebf570 fix types 2021-11-03 17:57:50 +02:00
Yuri Kuznetsov
19d799ac5e type fixes 2021-11-03 17:38:27 +02:00
Yuri Kuznetsov
e89a0955ea fix types 2021-11-03 17:30:42 +02:00
Yuri Kuznetsov
fc6dfd1994 fix orm types 2021-11-03 17:01:17 +02:00
Yuri Kuznetsov
3fa33b6706 type fixes 2021-11-03 16:47:27 +02:00
Yuri Kuznetsov
939daa616b type fixes 2021-11-03 16:43:44 +02:00
Yuri Kuznetsov
fd22f50de2 type fixes 2021-11-03 16:34:18 +02:00
Yuri Kuznetsov
48ca9259af type fixes 2021-11-03 16:02:45 +02:00
Yuri Kuznetsov
c49d3d4c30 type fixes 2021-11-03 15:52:54 +02:00
Yuri Kuznetsov
246b0c2a37 type fixes 2021-11-03 15:39:45 +02:00
Yuri Kuznetsov
33f446401b di setters fixes 2021-11-03 15:29:54 +02:00
Yuri Kuznetsov
9c677463f2 type fixes 2021-11-03 15:27:08 +02:00
Yuri Kuznetsov
786e267e93 type fixes 2021-11-03 15:17:28 +02:00
Yuri Kuznetsov
c782743a24 fix test 2021-11-03 13:19:29 +02:00
Yuri Kuznetsov
b2c4c76fba type fixes 2021-11-03 13:14:22 +02:00
Yuri Kuznetsov
5a6660b60d type fixes 2021-11-03 12:56:57 +02:00
Yuri Kuznetsov
f5d324eb48 type fixes 2021-11-03 11:39:41 +02:00
Yuri Kuznetsov
901e0967fc type fixes 2021-11-03 11:17:47 +02:00
Yuri Kuznetsov
1cfacc3813 fix regression websocket 2021-11-03 11:11:03 +02:00
Yuri Kuznetsov
8f1c7d5d54 fix attachment allowed types 2021-11-03 10:59:30 +02:00
Yuri Kuznetsov
3427c1b0bd type fixes 2021-11-02 21:15:15 +02:00
Yuri Kuznetsov
4a12d44971 type fixes 2021-11-02 19:56:48 +02:00
Yuri Kuznetsov
d833d43a8b fixes 2021-11-02 19:40:15 +02:00
Yuri Kuznetsov
64a3b851b6 type fixes 2021-11-02 18:24:37 +02:00
Yuri Kuznetsov
2a0ceceb2f type fixes 2021-11-02 18:16:42 +02:00
Yuri Kuznetsov
7250e0e5c4 type fixes 2021-11-02 17:08:01 +02:00
Yuri Kuznetsov
40161fdc3c type fixes 2021-11-02 16:37:39 +02:00
Yuri Kuznetsov
cb815d7dea type fixes 2021-11-02 16:25:25 +02:00
Yuri Kuznetsov
fffe4aacc1 fixes 2021-11-02 16:20:55 +02:00
Yuri Kuznetsov
985c12c918 type fixes 2021-11-02 16:15:31 +02:00
Yuri Kuznetsov
87bbf61c66 type fixes 2021-11-02 15:58:40 +02:00
Yuri Kuznetsov
8f204de057 fix injectable factory types 2021-11-02 15:38:59 +02:00
Yuri Kuznetsov
419b08070a fix 2021-11-02 15:30:57 +02:00
Yuri Kuznetsov
ba1d4d7b72 fixes 2021-11-02 13:45:16 +02:00
Yuri Kuznetsov
a48b5df28e type fixes 2021-11-02 13:34:32 +02:00
Yuri Kuznetsov
07bbc42df5 fix exceptions 2021-11-02 13:20:29 +02:00
Yuri Kuznetsov
14e77571c0 type fixes 2021-11-02 12:40:50 +02:00
Yuri Kuznetsov
71a35878c1 type fixes 2021-11-02 12:24:56 +02:00
Yuri Kuznetsov
7dc74f0767 phpstan config 2021-11-02 12:24:42 +02:00
Yuri Kuznetsov
784d12f3ca fix container 2021-11-02 11:32:30 +02:00
Yuri Kuznetsov
cdf9700ff0 fix loader interface 2021-11-02 11:10:43 +02:00
Yuri Kuznetsov
05f926b33b fix bool any filter 2021-11-02 11:07:55 +02:00
Yuri Kuznetsov
b05f2acc34 type fix 2021-11-01 17:34:15 +02:00
Yuri Kuznetsov
67b52d1f42 fixes 2021-11-01 17:19:42 +02:00
Yuri Kuznetsov
6391c3ca08 fixes 2021-11-01 17:17:44 +02:00
Yuri Kuznetsov
fc59a1efa1 type fixes 2021-11-01 17:00:07 +02:00
Yuri Kuznetsov
e215e39e5d type fixes 2021-11-01 16:46:41 +02:00
Yuri Kuznetsov
33e1f48717 fix import 2021-11-01 11:49:28 +02:00
Yuri Kuznetsov
94f61e5aff fix 2021-11-01 11:43:59 +02:00
Yuri Kuznetsov
41b25a43ba tcpdf fix 2021-11-01 11:27:20 +02:00
Yuri Kuznetsov
09e617ee78 fixes 2021-10-29 21:56:31 +03:00
Yuri Kuznetsov
f5f8b2f66a fix 2021-10-29 21:39:38 +03:00
Yuri Kuznetsov
45b9b41557 type fixes 2021-10-29 17:32:31 +03:00
Yuri Kuznetsov
e0a1b983e3 Merge branch 'hotfix/7.0.9' of github.com:espocrm/espocrm into hotfix/7.0.9 2021-10-29 17:12:48 +03:00
Yuri Kuznetsov
4d2b339112 fixes 2021-10-29 17:12:40 +03:00
Yuri Kuznetsov
b1a66e4a20 fix exceptions 2021-10-29 17:11:05 +03:00
Yuri Kuznetsov
1e44f82b38 fixes 2021-10-29 17:07:32 +03:00
Eymen Elkum
487f428c7f add missing returns (#2132) 2021-10-29 17:03:03 +03:00
Yuri Kuznetsov
f5fa51854d fixes 2021-10-29 16:12:24 +03:00
Yuri Kuznetsov
3f15295293 fixes 2021-10-29 15:53:31 +03:00
Yuri Kuznetsov
de72c2b055 fixes 2021-10-29 15:33:25 +03:00
Yuri Kuznetsov
8f93507fec fixes 2021-10-29 15:14:37 +03:00
Yuri Kuznetsov
2afaf8b446 fix 2021-10-29 14:05:59 +03:00
Yuri Kuznetsov
33e0a1b228 phpstan 2021-10-29 13:56:55 +03:00
Yuri Kuznetsov
4b7dd1fd22 fixes 2021-10-29 13:55:58 +03:00
Yuri Kuznetsov
6f7ef14482 fixes 2021-10-29 13:48:03 +03:00
Yuri Kuznetsov
097a115595 fixes 2021-10-29 13:42:43 +03:00
Yuri Kuznetsov
cd78458d19 fix select 2021-10-29 13:24:21 +03:00
Yuri Kuznetsov
b69d706cc4 fix where 2021-10-29 13:16:47 +03:00
Yuri Kuznetsov
8286d7b621 fixes 2021-10-29 12:46:57 +03:00
Yuri Kuznetsov
d899c55f4f pagination fixes 2021-10-28 16:54:55 +03:00
Yuri Kuznetsov
0a954e171d pagination fixes 2021-10-28 13:59:45 +03:00
Yuri Kuznetsov
89d2c38257 refactoring 2021-10-28 13:50:32 +03:00
Yuri Kuznetsov
d7829e7801 v 2021-10-26 12:07:42 +03:00
Yuri Kuznetsov
3d4f6055e4 merge fixes 2021-10-26 12:07:15 +03:00
Yuri Kuznetsov
383ae3fe3e entity inteface add methods 2021-10-26 11:45:10 +03:00
Yuri Kuznetsov
47cbd18c88 ldap user save fix 2021-10-26 11:43:18 +03:00
Yuri Kuznetsov
d5779878c7 fix link multiple loading on list 2021-10-25 15:30:16 +03:00
Shefqet Avdullau
c65cabffd5 Fixed typo in Tasks default filter Completed (#2126)
Fixes #2125
2021-10-25 15:04:42 +03:00
Yuri Kuznetsov
fe90a62239 autocomplete new password 2021-10-25 13:24:21 +03:00
Yuri Kuznetsov
13ae33266f dummy file 2021-10-25 13:14:19 +03:00
Yuri Kuznetsov
399b689bcd fix personal email account assigned user 2021-10-25 12:01:38 +03:00
Yuri Kuznetsov
1219e7be12 fix category tree order 2021-10-25 11:02:23 +03:00
Yuri Kuznetsov
10f67ab360 mass update opp stage 2021-10-21 13:21:01 +03:00
Yuri Kuznetsov
ce1f755612 action data cloning 2021-10-21 13:20:36 +03:00
Yuri Kuznetsov
fa943d1166 mass action data cloning 2021-10-21 13:18:51 +03:00
Yuri Kuznetsov
dc09f91e35 refactoring 2021-10-21 12:56:47 +03:00
Yuri Kuznetsov
34b8fa2728 acceptanceStatus customization fields disabled 2021-10-20 16:04:33 +03:00
Yuri Kuznetsov
02f7bebf67 outbound email address autocomplete 2021-10-19 16:01:19 +03:00
Yuri Kuznetsov
4ffc384764 formatting fix 2021-10-19 15:27:19 +03:00
Yuri Kuznetsov
e1ef2b34ea orphan email address filter fix 2021-10-19 15:14:32 +03:00
Yuri Kuznetsov
d5a83a8819 fix 2021-10-19 12:43:30 +03:00
Yuri Kuznetsov
661f499f67 orm custom functions 2021-10-19 12:40:07 +03:00
Yuri Kuznetsov
a073beeea7 lastAccess export disable 2021-10-18 17:35:07 +03:00
Yuri Kuznetsov
07f6bf7a7a signature with insert email fix 2021-10-18 13:56:42 +03:00
Yuri Kuznetsov
0cfbb269f2 fix ldap 2021-10-18 13:15:23 +03:00
Yuri Kuznetsov
f3791e5fb4 fix ldap create user 2021-10-18 13:03:06 +03:00
Yuri Kuznetsov
37d8b0d3bd cleanup 2021-10-15 15:47:17 +03:00
Arkadiy
4ae383033a fix missing class imports, exceptions and undefined properties (#2112)
* fix exception

* fix missing imports

* fix access to an undefined property
2021-10-15 15:46:45 +03:00
Yuri Kuznetsov
ea71675701 fix typo 2021-10-15 10:56:48 +03:00
Arkadiy
1178d01baa fix for SelectingBuilderTrait (#2110)
* fix undefined type

* fix exception
2021-10-14 15:13:55 +03:00
Yuri Kuznetsov
ace53f9494 v 2021-10-14 13:01:32 +03:00
Yuri Kuznetsov
3c17ff2da9 layout set fix 2021-10-14 10:35:08 +03:00
Yuri Kuznetsov
163991a36b fix 2021-10-14 10:07:09 +03:00
Yuri Kuznetsov
291721c1d0 fix checkboxes 2021-10-13 16:30:36 +03:00
Yuri Kuznetsov
d96301d544 7.0.6 2021-10-13 15:56:19 +03:00
Yuri Kuznetsov
54d9674055 fix emails folderId 2021-10-13 10:35:47 +03:00
Yuri Kuznetsov
4a5a07649a cs fix 2021-10-13 10:32:39 +03:00
Yuri Kuznetsov
cfdb65fad8 fix missing checkboxes 2021-10-13 10:12:44 +03:00
Yuri Kuznetsov
16544a4a70 fix email address parsing 2021-10-12 15:52:59 +03:00
Yuri Kuznetsov
02939d0840 v 2021-10-12 13:06:02 +03:00
Yuri Kuznetsov
bf331c7020 cs fix 2021-10-12 12:05:58 +03:00
Yuri Kuznetsov
e80b55572b fix lead filter 2021-10-12 11:32:10 +03:00
Yuri Kuznetsov
51c29f0a6e fix link & link multiple search empty 2021-10-12 11:30:20 +03:00
Yuri Kuznetsov
056191db82 fix pdf filename 2021-10-11 13:01:59 +03:00
Yuri Kuznetsov
9ace150efa v 2021-10-11 11:31:48 +03:00
Yuri Kuznetsov
79ae89d172 fix duration 2021-10-10 11:38:15 +03:00
Yuri Kuznetsov
bb703e2060 fix export not existing fields 2021-10-10 11:22:58 +03:00
Yuri Kuznetsov
972d9019e1 fix log 2021-10-10 11:19:38 +03:00
Yuri Kuznetsov
c99566410a note acl refactoring and period change 2021-10-08 15:35:05 +03:00
Yuri Kuznetsov
9c56a14fb0 file manager allow create file w/ wrong ownership 2021-10-08 11:45:56 +03:00
Yuri Kuznetsov
b95e58317b fix msg 2021-10-08 11:27:09 +03:00
Yuri Kuznetsov
6fd9d6e15c upgrade fix 2021-10-07 17:42:31 +03:00
Yuri Kuznetsov
518b9fa2ba unlink file in checkCreateFile 2021-10-07 17:17:22 +03:00
Yuri Kuznetsov
c113b7bd88 throw exception in auth 2021-10-07 15:00:11 +03:00
Yuri Kuznetsov
fb0a302f62 v 2021-10-06 18:27:50 +03:00
Yuri Kuznetsov
43127379cd fix settings 2021-10-06 18:27:07 +03:00
Yuri Kuznetsov
c5bc7136c7 fix activities 2021-10-06 13:34:35 +03:00
Yuri Kuznetsov
18ba671417 tryCheck usage 2021-10-06 12:00:33 +03:00
Yuri Kuznetsov
6141bfd1fb printTrace in default config 2021-10-06 11:28:41 +03:00
Yuri Kuznetsov
4e4b51c4f1 metadata tryCheck usage 2021-10-06 11:06:10 +03:00
Yuri Kuznetsov
83ac300aa3 fix logo upload 2021-10-05 20:31:24 +03:00
Yuri Kuznetsov
f641a3c9c6 fix 2021-10-05 18:15:55 +03:00
Yuri Kuznetsov
18a4782bf1 v 2021-10-05 17:17:41 +03:00
Yuri Kuznetsov
f1fddb7325 fix 2021-10-05 15:07:12 +03:00
Yuri Kuznetsov
9c864af409 fix 2021-10-05 13:34:08 +03:00
Yuri Kuznetsov
7cd332228a fix getEntity user 2021-10-05 12:58:04 +03:00
Yuri Kuznetsov
54c534736f printTrace logger param 2021-10-05 12:27:49 +03:00
Yuri Kuznetsov
6f07862572 fix test 2021-10-05 12:27:20 +03:00
Yuri Kuznetsov
54863ca6a4 fix group email account keep unread 2021-10-05 11:35:33 +03:00
677 changed files with 10824 additions and 4651 deletions

View File

@@ -110,6 +110,7 @@ module.exports = grunt => {
'build/tmp/client/custom/*',
'!build/tmp/client/custom/modules',
'build/tmp/client/custom/modules/*',
'!build/tmp/client/custom/modules/dummy.txt',
]
}
},

View File

@@ -32,6 +32,7 @@ namespace Espo\Classes\Acl\Attachment;
use Espo\Entities\{
User,
Note,
Attachment,
};
use Espo\ORM\Entity;
@@ -45,6 +46,9 @@ use Espo\Core\{
Acl\Traits\DefaultAccessCheckerDependency,
};
/**
* @implements AccessEntityCREDChecker<Attachment>
*/
class AccessChecker implements AccessEntityCREDChecker
{
use DefaultAccessCheckerDependency;
@@ -65,7 +69,10 @@ class AccessChecker implements AccessEntityCREDChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
/** @var Attachment $entity */
if ($entity->get('parentType') === 'Settings') {
// Allow the logo.
return true;
}
@@ -93,6 +100,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($parent->getEntityType() === 'Note') {
/** @var Note $parent */
$result = $this->checkEntityReadNoteParent($user, $parent);
if ($result !== null) {
@@ -100,6 +108,16 @@ class AccessChecker implements AccessEntityCREDChecker
}
}
else if ($this->aclManager->checkEntity($user, $parent)) {
if (
$entity->getTargetField() &&
in_array(
$entity->getTargetField(),
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
)
) {
return false;
}
return true;
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Classes\Acl\Email;
use Espo\Entities\User;
use Espo\Entities\Email;
use Espo\ORM\Entity;
@@ -52,6 +53,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
/** @var Email $entity */
if ($this->defaultAccessChecker->checkEntityRead($user, $entity, $data)) {
return true;
}
@@ -79,6 +82,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
{
/** @var Email $entity */
if ($user->isAdmin()) {
return true;
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Classes\Acl\Email;
use Espo\Entities\User;
use Espo\Entities\Email;
use Espo\ORM\Entity;
@@ -50,6 +51,8 @@ class OwnershipChecker implements OwnershipOwnChecker, OwnershipTeamChecker
public function checkOwn(User $user, Entity $entity): bool
{
/** @var Email $entity */
if ($user->getId() === $entity->get('assignedUserId')) {
return true;
}

View File

@@ -42,6 +42,9 @@ use Espo\Core\{
AclManager,
};
/**
* @implements AccessEntityCREDSChecker<User>
*/
class AccessChecker implements AccessEntityCREDSChecker
{
use DefaultAccessCheckerDependency;
@@ -62,6 +65,8 @@ class AccessChecker implements AccessEntityCREDSChecker
return false;
}
/** @var User $entity */
if ($entity->isSuperAdmin() && !$user->isSuperAdmin()) {
return false;
}
@@ -71,6 +76,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if ($entity->isPortal()) {
if ($this->aclManager->getPermissionLevel($user, 'portal') === Table::LEVEL_YES) {
return true;
@@ -88,6 +95,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if ($entity->isSystem()) {
return false;
}
@@ -107,6 +116,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if (!$user->isAdmin()) {
return false;
}
@@ -124,6 +135,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
return $this->aclManager->checkUserPermission($user, $entity, 'user');
}
}

View File

@@ -30,8 +30,8 @@
namespace Espo\Classes\Acl\User;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\{
Acl\OwnershipOwnChecker,
@@ -47,6 +47,8 @@ class OwnershipChecker implements OwnershipOwnChecker, OwnershipTeamChecker
public function checkTeam(User $user, Entity $entity): bool
{
assert($entity instanceof CoreEntity);
$intersect = array_intersect(
$user->getLinkMultipleIdList('teams'),
$entity->getLinkMultipleIdList('teams')

View File

@@ -32,6 +32,7 @@ namespace Espo\Classes\AclPortal\Attachment;
use Espo\Entities\{
User,
Note,
Attachment,
};
use Espo\ORM\Entity;
@@ -45,6 +46,9 @@ use Espo\Core\{
Portal\Acl\Traits\DefaultAccessCheckerDependency,
};
/**
* @implements AccessEntityCREDChecker<Attachment>
*/
class AccessChecker implements AccessEntityCREDChecker
{
use DefaultAccessCheckerDependency;
@@ -67,7 +71,10 @@ class AccessChecker implements AccessEntityCREDChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
/** @var Attachment $entity */
if ($entity->get('parentType') === 'Settings') {
// Allow the logo.
return true;
}
@@ -94,6 +101,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($parent->getEntityType() === 'Note') {
/** @var Note $parent */
$result = $this->checkEntityReadNoteParent($user, $parent);
if ($result !== null) {
@@ -101,6 +109,16 @@ class AccessChecker implements AccessEntityCREDChecker
}
}
else if ($this->aclManager->checkEntity($user, $parent)) {
if (
$entity->getTargetField() &&
in_array(
$entity->getTargetField(),
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
)
) {
return false;
}
return true;
}

View File

@@ -33,8 +33,9 @@ use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\{
Portal\AclManager,
Acl\Table,
Acl\ScopeData,
Acl\AccessEntityCREDSChecker,
@@ -48,14 +49,10 @@ class AccessChecker implements AccessEntityCREDSChecker
private $defaultAccessChecker;
private $aclManager;
public function __construct(
DefaultAccessChecker $defaultAccessChecker,
AclManager $aclManager
DefaultAccessChecker $defaultAccessChecker
) {
$this->defaultAccessChecker = $defaultAccessChecker;
$this->aclManager = $aclManager;
}
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
@@ -72,6 +69,8 @@ class AccessChecker implements AccessEntityCREDSChecker
return false;
}
assert($entity instanceof CoreEntity);
$userIdList = $entity->getLinkMultipleIdLIst('users');
if (is_array($userIdList) && in_array($user->getId(), $userIdList)) {

View File

@@ -69,7 +69,7 @@ class Binding
$keyList = $data->getContextKeyList($context);
foreach ($keyList as $key) {
$result .= $this->printItem($key, $data->getContext($context, $key), true);
$result .= $this->printItem($key, $data->getContext($context, $key));
}
}

View File

@@ -40,8 +40,19 @@ use Espo\Core\{
*/
class TemplateEntityTypeList
{
/**
* @var Acl
*/
protected $acl;
/**
* @var SelectBuilderFactory
*/
protected $selectBuilderFactory;
/**
* @var EntityManager
*/
protected $entityManager;
public function __construct(Acl $acl, SelectBuilderFactory $selectBuilderFactory, EntityManager $entityManager)
@@ -51,7 +62,7 @@ class TemplateEntityTypeList
$this->entityManager = $entityManager;
}
public function get() : array
public function get(): array
{
if (!$this->acl->checkScope('Template')) {
return [];
@@ -69,7 +80,7 @@ class TemplateEntityTypeList
->build();
$templateCollection = $this->entityManager
->getRepository('Template')
->getRDBRepository('Template')
->clone($query)
->find();

View File

@@ -43,6 +43,10 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Entities\Notification;
use Espo\Entities\Email as EmailEntity;
use Espo\Repositories\Email as EmailRepository;
use Espo\Repositories\EmailAddress as EmailAddressRepository;
use DateTime;
use Exception;
@@ -79,6 +83,8 @@ class Email implements AssignmentNotificator
public function process(Entity $entity, Params $params): void
{
/** @var EmailEntity $entity */
if (!in_array($entity->get('status'), ['Archived', 'Sent', 'Being Imported'])) {
return;
}
@@ -107,10 +113,6 @@ class Email implements AssignmentNotificator
return;
}
if (!$dt) {
return;
}
if ($dt->diff(new DateTime())->days > self::DAYS_THRESHOLD) {
return;
}
@@ -138,12 +140,17 @@ class Email implements AssignmentNotificator
'emailName' => $entity->get('name'),
];
/** @var EmailRepository $emailRepository */
$emailRepository = $this->entityManager->getRepository('Email');
/** @var EmailAddressRepository $emailAddressRepository */
$emailAddressRepository = $this->entityManager->getRepository('EmailAddress');
if (!$entity->has('from')) {
$this->entityManager->getRepository('Email')->loadFromField($entity);
$emailRepository->loadFromField($entity);
}
if (!$entity->has('to')) {
$this->entityManager->getRepository('Email')->loadToField($entity);
$emailRepository->loadToField($entity);
}
$person = null;
@@ -151,9 +158,7 @@ class Email implements AssignmentNotificator
$from = $entity->get('from');
if ($from) {
$person = $this->entityManager
->getRepository('EmailAddress')
->getEntityByAddress($from, null, ['User', 'Contact', 'Lead']);
$person = $emailAddressRepository->getEntityByAddress($from, null, ['User', 'Contact', 'Lead']);
if ($person) {
$data['personEntityType'] = $person->getEntityType();
@@ -165,7 +170,7 @@ class Email implements AssignmentNotificator
$userIdFrom = null;
if ($person && $person->getEntityType() === 'User') {
$userIdFrom = $person->id;
$userIdFrom = $person->getId();
}
if (empty($data['personEntityId'])) {
@@ -214,7 +219,7 @@ class Email implements AssignmentNotificator
if ($folderId) {
if (
$this->entityManager
->getRepository('EmailFolder')
->getRDBRepository('EmailFolder')
->where([
'id' => $folderId,
'skipNotifications' => true,
@@ -226,6 +231,7 @@ class Email implements AssignmentNotificator
}
}
/** @var User|null $user */
$user = $this->entityManager->getEntity('User', $userId);
if (!$user) {
@@ -261,7 +267,7 @@ class Email implements AssignmentNotificator
}
$existing = $this->entityManager
->getRepository(Notification::ENTITY_TYPE)
->getRDBRepository(Notification::ENTITY_TYPE)
->where([
'type' => Notification::TYPE_EMAIL_RECEIVED,
'userId' => $userId,

View File

@@ -30,6 +30,8 @@
namespace Espo\Classes\DuplicateWhereBuilders;
use Espo\Core\Duplicate\WhereBuilder;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\ORM\{
Query\Part\Condition as Cond,
@@ -42,6 +44,8 @@ class Company implements WhereBuilder
{
public function build(Entity $entity): ?WhereItem
{
assert($entity instanceof CoreEntity);
$orBuilder = OrGroup::createBuilder();
$toCheck = false;
@@ -84,10 +88,10 @@ class Company implements WhereBuilder
return $orBuilder->build();
}
private function getEmailAddressList(Entity $entity): array
private function getEmailAddressList(CoreEntity $entity): array
{
if ($entity->get('emailAddressData')) {
/* @var $eaGroup EmailAddressGroup */
/** @var EmailAddressGroup $eaGroup */
$eaGroup = $entity->getValueObject('emailAddress');
return $eaGroup->getAddressList();

View File

@@ -29,6 +29,8 @@
namespace Espo\Classes\DuplicateWhereBuilders;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\{
Duplicate\WhereBuilder,
Field\EmailAddressGroup,
@@ -45,6 +47,8 @@ class Person implements WhereBuilder
{
public function build(Entity $entity): ?WhereItem
{
assert($entity instanceof CoreEntity);
$orBuilder = OrGroup::createBuilder();
$toCheck = false;
@@ -93,10 +97,10 @@ class Person implements WhereBuilder
return $orBuilder->build();
}
private function getEmailAddressList(Entity $entity): array
private function getEmailAddressList(CoreEntity $entity): array
{
if ($entity->get('emailAddressData')) {
/* @var $eaGroup EmailAddressGroup */
/** @var EmailAddressGroup $eaGroup */
$eaGroup = $entity->getValueObject('emailAddress');
return $eaGroup->getAddressList();

View File

@@ -39,6 +39,9 @@ use Espo\Core\{
use Espo\Repositories\Email as EmailRepository;
/**
* @implements Loader<\Espo\Entities\Email>
*/
class AddressDataLoader implements Loader
{
private $entityManager;
@@ -50,7 +53,7 @@ class AddressDataLoader implements Loader
public function process(Entity $entity, Params $params): void
{
/* @var $repository EmailRepository */
/** @var EmailRepository $repository */
$repository = $this->entityManager->getRepository('Email');
$repository->loadFromField($entity);

View File

@@ -114,7 +114,7 @@ class IcsDataLoader implements Loader
return;
}
/* @var $emailAddressRepository EmailAddressRepository */
/** @var EmailAddressRepository $emailAddressRepository */
$emailAddressRepository = $this->entityManager->getRepository(EmailAddress::ENTITY_TYPE);
$attendeeEmailAddressList = $espoEvent->getAttendeeEmailAddressList();

View File

@@ -31,6 +31,8 @@ namespace Espo\Classes\FieldProcessing\Email;
use Espo\ORM\Entity;
use Espo\Repositories\EmailAddress as EmailAddressRepository;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
@@ -56,6 +58,8 @@ class StringDataLoader implements Loader
public function process(Entity $entity, Params $params): void
{
/** @var Email $entity */
$userEmailAdddressIdList = [];
$emailAddressCollection = $this->entityManager
@@ -84,9 +88,7 @@ class StringDataLoader implements Loader
$list = [];
foreach ($idList as $emailAddressId) {
$person = $this->entityManager
->getRepository('EmailAddress')
->getEntityByAddressId($emailAddressId, null, true);
$person = $this->getEmailAddressRepository()->getEntityByAddressId($emailAddressId, null, true);
$list[] = $person ? $person->get('name') : $names->$emailAddressId;
}
@@ -103,9 +105,7 @@ class StringDataLoader implements Loader
}
if (!array_key_exists($fromEmailAddressId, $this->fromEmailAddressNameCache)) {
$person = $this->entityManager
->getRepository('EmailAddress')
->getEntityByAddressId($fromEmailAddressId, null, true);
$person = $this->getEmailAddressRepository()->getEntityByAddressId($fromEmailAddressId, null, true);
$fromName = $person ? $person->get('name') : null;
@@ -119,4 +119,10 @@ class StringDataLoader implements Loader
$entity->set('personStringData', $fromName);
}
private function getEmailAddressRepository(): EmailAddressRepository
{
/** @var EmailAddressRepository */
return $this->entityManager->getRepository('EmailAddress');
}
}

View File

@@ -54,7 +54,7 @@ class UserColumnsLoader implements Loader
public function process(Entity $entity, Params $params): void
{
$emailUser = $this->entityManager
->getRepository('EmailUser')
->getRDBRepository('EmailUser')
->select(['isRead', 'isImportant', 'inTrash'])
->where([
'deleted' => false,

View File

@@ -39,6 +39,9 @@ use Espo\Core\{
use Espo\Repositories\Import as ImportRepository;
/**
* @implements Loader<\Espo\Entities\Import>
*/
class CountsLoader implements Loader
{
private $entityManager;
@@ -50,7 +53,7 @@ class CountsLoader implements Loader
public function process(Entity $entity, Params $params): void
{
/* @var $repository ImportRepository */
/** @var ImportRepository $repository */
$repository = $this->entityManager->getRepository('Import');
$importedCount = $repository->countResultRecords($entity, 'imported');

View File

@@ -42,7 +42,7 @@ class AttachmentsLoader implements Loader
{
public function process(Entity $entity, Params $params): void
{
/* @var $entity Note */
/** @var Note $entity */
$entity->loadAttachments();
}
}

View File

@@ -31,6 +31,8 @@ namespace Espo\Classes\FieldProcessing\Portal;
use Espo\ORM\Entity;
use Espo\Repositories\Portal as PortalRepository;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
@@ -48,8 +50,12 @@ class UrlLoader implements Loader
public function process(Entity $entity, Params $params): void
{
$this->entityManager
->getRepository('Portal')
->loadUrlField($entity);
$this->getPortalRepository()->loadUrlField($entity);
}
private function getPortalRepository(): PortalRepository
{
/** @var PortalRepository */
return $this->entityManager->getRepository('Portal');
}
}

View File

@@ -30,11 +30,16 @@
namespace Espo\Classes\FieldValidators;
use Espo\ORM\Entity;
use Espo\Core\ORM\Entity as CoreEntity;
class LinkMultipleType
{
public function checkRequired(Entity $entity, string $field): bool
{
if (!$entity instanceof CoreEntity) {
return false;
}
return count($entity->getLinkMultipleIdList($field)) > 0;
}
}

View File

@@ -83,7 +83,7 @@ class AuthTokenControl implements JobDataLess
}
$tokenList = $this->entityManager
->getRepository('AuthToken')
->getRDBRepository('AuthToken')
->where($whereClause)
->limit(0, 500)
->find();

View File

@@ -40,8 +40,14 @@ use DateTimeZone;
class CheckNewVersion implements JobDataLess
{
/**
* @var Config
*/
protected $config;
/**
* @var EntityManager
*/
protected $entityManager;
public function __construct(Config $config, EntityManager $entityManager)

View File

@@ -30,6 +30,8 @@
namespace Espo\Classes\Jobs;
use Espo\Core\Record\ServiceContainer;
use Espo\ORM\Repository\RDBRepository;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\{
Utils\Config,
@@ -183,14 +185,16 @@ class Cleanup implements JobDataLess
private function cleanupScheduledJobLog(): void
{
$scheduledJobList = $this->entityManager->getRepository('ScheduledJob')
$scheduledJobList = $this->entityManager
->getRDBRepository('ScheduledJob')
->select(['id'])
->find();
foreach ($scheduledJobList as $scheduledJob) {
$scheduledJobId = $scheduledJob->get('id');
$ignoreLogRecordList = $this->entityManager->getRepository('ScheduledJobLogRecord')
$ignoreLogRecordList = $this->entityManager
->getRDBRepository('ScheduledJobLogRecord')
->select(['id'])
->where([
'scheduledJobId' => $scheduledJobId,
@@ -204,6 +208,7 @@ class Cleanup implements JobDataLess
}
$ignoreIdList = [];
foreach ($ignoreLogRecordList as $logRecord) {
$ignoreIdList[] = $logRecord->get('id');
}
@@ -300,7 +305,7 @@ class Cleanup implements JobDataLess
$datetime->modify($period);
$collection = $this->entityManager
->getRepository('Attachment')
->getRDBRepository('Attachment')
->where([
'OR' => [
[
@@ -329,7 +334,7 @@ class Cleanup implements JobDataLess
]);
$collection = $this->entityManager
->getRepository('Attachment')
->getRDBRepository('Attachment')
->clone($orphanQueryBuilder->build())
->limit(0, 5000)
->find();
@@ -398,7 +403,8 @@ class Cleanup implements JobDataLess
continue;
}
$query = $this->entityManager->getQueryBuilder()
$query = $this->entityManager
->getQueryBuilder()
->select()
->from($scope)
->withDeleted()
@@ -415,16 +421,16 @@ class Cleanup implements JobDataLess
foreach ($deletedEntityList as $deletedEntity) {
$attachmentToRemoveList = $this->entityManager
->getRepository('Attachment')
->getRDBRepository('Attachment')
->where([
'OR' => [
[
'relatedType' => $scope,
'relatedId' => $deletedEntity->id,
'relatedId' => $deletedEntity->getId(),
],
[
'parentType' => $scope,
'parentId' => $deletedEntity->id,
'parentId' => $deletedEntity->getId(),
]
]
])
@@ -436,7 +442,9 @@ class Cleanup implements JobDataLess
}
}
$delete = $this->entityManager->getQueryBuilder()->delete()
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from('Attachment')
->where([
'deleted' => true,
@@ -451,14 +459,15 @@ class Cleanup implements JobDataLess
{
$dateBefore = date('Y-m-d H:i:s', time() - 3600 * 24 * 20);
$query = $this->entityManager->getQueryBuilder()
$query = $this->entityManager
->getQueryBuilder()
->select()
->from('Email')
->withDeleted()
->build();
$emailList = $this->entityManager
->getRepository('Email')
->getRDBRepository('Email')
->clone($query)
->select(['id'])
->where([
@@ -471,7 +480,7 @@ class Cleanup implements JobDataLess
$id = $email->get('id');
$attachments = $this->entityManager
->getRepository('Attachment')
->getRDBRepository('Attachment')
->where([
'parentId' => $id,
'parentType' => 'Email'
@@ -482,7 +491,9 @@ class Cleanup implements JobDataLess
$this->entityManager->removeEntity($attachment);
}
$delete = $this->entityManager->getQueryBuilder()->delete()
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from('Email')
->where([
'deleted' => true,
@@ -492,7 +503,9 @@ class Cleanup implements JobDataLess
$this->entityManager->getQueryExecutor()->execute($delete);
$delete = $this->entityManager->getQueryBuilder()->delete()
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from('EmailUser')
->where([
'emailId' => $id,
@@ -510,14 +523,17 @@ class Cleanup implements JobDataLess
$datetime = new DateTime();
$datetime->modify($period);
$notificationList = $this->entityManager->getRepository('Notification')
$notificationList = $this->entityManager
->getRDBRepository('Notification')
->where([
'DATE:createdAt<' => $datetime->format('Y-m-d'),
])
->find();
foreach ($notificationList as $notification) {
$this->entityManager->getRepository('Notification')->deleteFromDb($notification->get('id'));
$this->entityManager
->getRDBRepository('Notification')
->deleteFromDb($notification->get('id'));
}
}
@@ -553,7 +569,15 @@ class Cleanup implements JobDataLess
$repository = $this->entityManager->getRepository($scope);
$repository->deleteFromDb($entity->id);
if (!$repository instanceof RDBRepository) {
return;
}
if (!$entity instanceof CoreEntity) {
return;
}
$repository->deleteFromDb($entity->getId());
$query = $this->entityManager->getQueryComposer();
@@ -576,7 +600,7 @@ class Cleanup implements JobDataLess
}
$where = [
$midKey => $entity->id,
$midKey => $entity->getId(),
];
$conditions = $entity->getRelationParam($relation, 'conditions') ?? [];
@@ -585,17 +609,15 @@ class Cleanup implements JobDataLess
$where[$key] = $value;
}
if (empty($where)) {
continue;
}
$relationEntityType = ucfirst($relationName);
if (!$this->entityManager->hasRepository($relationEntityType)) {
continue;
}
$delete = $this->entityManager->getQueryBuilder()->delete()
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from($relationEntityType)
->where($where)
->build();
@@ -607,24 +629,25 @@ class Cleanup implements JobDataLess
}
}
$query = $this->entityManager->getQueryBuilder()
$query = $this->entityManager
->getQueryBuilder()
->select()
->from('Note')
->withDeleted()
->build();
$noteList = $this->entityManager
->getRepository('Note')
->getRDBRepository('Note')
->clone($query)
->where([
'OR' => [
[
'relatedType' => $scope,
'relatedId' => $entity->id,
'relatedId' => $entity->getId(),
],
[
'parentType' => $scope,
'parentId' => $entity->id,
'parentId' => $entity->getId(),
]
]
])
@@ -640,29 +663,33 @@ class Cleanup implements JobDataLess
if ($scope === 'Note') {
$attachmentList = $this->entityManager
->getRepository('Attachment')
->getRDBRepository('Attachment')
->where([
'parentId' => $entity->id,
'parentId' => $entity->getId(),
'parentType' => 'Note',
])
->find();
foreach ($attachmentList as $attachment) {
$this->entityManager->removeEntity($attachment);
$this->entityManager->getRepository('Attachment')->deleteFromDb($attachment->id);
$this->entityManager
->getRDBRepository('Attachment')
->deleteFromDb($attachment->getId());
}
}
$arrayValueList = $this->entityManager
->getRepository('ArrayValue')
->getRDBRepository('ArrayValue')
->where([
'entityType' => $entity->getEntityType(),
'entityId' => $entity->id,
'entityId' => $entity->getId(),
])
->find();
foreach ($arrayValueList as $arrayValue) {
$this->entityManager->getRepository('ArrayValue')->deleteFromDb($arrayValue->id);
$this->entityManager
->getRDBRepository('ArrayValue')
->deleteFromDb($arrayValue->getId());
}
}
@@ -693,24 +720,12 @@ class Cleanup implements JobDataLess
$repository = $this->entityManager->getRepository($scope);
if (!$repository) {
if (!$repository instanceof RDBRepository) {
continue;
}
if (!method_exists($repository, 'find')) continue;
if (!method_exists($repository, 'clone')) continue;
if (!method_exists($repository, 'where')) continue;
if (!method_exists($repository, 'select')) continue;
if (!method_exists($repository, 'deleteFromDb')) continue;
$hasCleanupMethod = false;
$service = $this->recordServiceContainer->get($scope);
if (method_exists($service, 'cleanup')) {
$hasCleanupMethod = true;
}
$whereClause = [
'deleted' => 1,
];
@@ -722,7 +737,8 @@ class Cleanup implements JobDataLess
$whereClause['createdAt<'] = $datetime->format('Y-m-d H:i:s');
}
$query = $this->entityManager->getQueryBuilder()
$query = $this->entityManager
->getQueryBuilder()
->select()
->from($scope)
->withDeleted()
@@ -735,9 +751,9 @@ class Cleanup implements JobDataLess
->find();
foreach ($deletedEntityList as $entity) {
if ($hasCleanupMethod) {
if (method_exists($service, 'cleanup')) {
try {
$service->cleanup($entity->id);
$service->cleanup($entity->getId());
}
catch (Throwable $e) {
$this->log->error("Cleanup job: Cleanup scope {$scope}: " . $e->getMessage());

View File

@@ -48,15 +48,15 @@ use Espo\{
class MassDelete implements MassAction
{
protected $massDeleteOriginal;
private $massDeleteOriginal;
protected $queryBuilder;
private $queryBuilder;
protected $entityManager;
private $entityManager;
protected $acl;
private $acl;
protected $user;
private $user;
public function __construct(
MassDeleteOriginal $massDeleteOriginal,
@@ -87,14 +87,14 @@ class MassDelete implements MassAction
$query = $this->queryBuilder->build($params);
$collection = $this->entityManager
->getRepository('User')
->getRDBRepository('User')
->clone($query)
->sth()
->select(['id'])
->find();
foreach ($collection as $entity) {
$this->checkEntity($entity, $data);
$this->checkEntity($entity);
}
return $this->massDeleteOriginal->process($params, $data);
@@ -102,11 +102,11 @@ class MassDelete implements MassAction
protected function checkEntity(Entity $entity): void
{
if ($entity->id === 'system') {
if ($entity->getId() === 'system') {
throw new Forbidden("Can't delete 'system' user.");
}
if ($entity->id === $this->user->id) {
if ($entity->getId() === $this->user->getId()) {
throw new Forbidden("Can't delete own user.");
}
}

View File

@@ -50,19 +50,19 @@ use Espo\{
class MassUpdate implements MassAction
{
protected $massUpdateOriginal;
private $massUpdateOriginal;
protected $queryBuilder;
private $queryBuilder;
protected $entityManager;
private $entityManager;
protected $acl;
private $acl;
protected $user;
private $user;
protected $fileManager;
private $fileManager;
protected $dataManager;
private $dataManager;
public function __construct(
MassUpdateOriginal $massUpdateOriginal,
@@ -108,7 +108,7 @@ class MassUpdate implements MassAction
$query = $this->queryBuilder->build($params);
$collection = $this->entityManager
->getRepository('User')
->getRDBRepository('User')
->clone($query)
->sth()
->select(['id'])
@@ -127,11 +127,11 @@ class MassUpdate implements MassAction
protected function checkEntity(Entity $entity, Data $data): void
{
if ($entity->id === 'system') {
if ($entity->getId() === 'system') {
throw new Forbidden("Can't update 'system' user.");
}
if ($entity->id === $this->user->id) {
if ($entity->getId() === $this->user->getId()) {
if ($data->has('isActive')) {
throw new Forbidden("Can't change 'isActive' field for own user.");
}

View File

@@ -36,6 +36,9 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @implements LinkHook<\Espo\Entities\Team>
*/
class BeforeLinkUserCheck implements LinkHook
{
public function process(Entity $entity, string $link, Entity $foreignEntity): void
@@ -44,6 +47,8 @@ class BeforeLinkUserCheck implements LinkHook
return;
}
assert($foreignEntity instanceof User);
$this->processUserCheck($foreignEntity);
}

View File

@@ -45,7 +45,7 @@ class EmailAddressHelper
public function getEmailAddressIdByValue(string $value): ?string
{
$emailAddress = $this->entityManager
->getRepository('EmailAddress')
->getRDBRepository('EmailAddress')
->where([
'lower' => strtolower($value),
])
@@ -55,6 +55,6 @@ class EmailAddressHelper
return null;
}
return $emailAddress->id;
return $emailAddress->getId();
}
}

View File

@@ -44,12 +44,16 @@ use Espo\{
class FromEquals implements ItemConverter
{
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var EmailAddressHelper
*/
protected $emailAddressHelper;
protected $randomStringGenerator;
public function __construct(
EntityManager $entityManager,
EmailAddressHelper $emailAddressHelper

View File

@@ -45,10 +45,19 @@ use Espo\{
class InFolder implements ItemConverter
{
/**
* @var User
*/
protected $user;
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var JoinHelper
*/
protected $joinHelper;
public function __construct(User $user, EntityManager $entityManager, JoinHelper $joinHelper)
@@ -186,7 +195,7 @@ class InFolder implements ItemConverter
protected function getEmailAddressIdList(): array
{
$emailAddressList = $this->entityManager
->getRepository('User')
->getRDBRepository('User')
->getRelation($this->user, 'emailAddresses')
->select(['id'])
->find();
@@ -194,7 +203,7 @@ class InFolder implements ItemConverter
$emailAddressIdList = [];
foreach ($emailAddressList as $emailAddress) {
$emailAddressIdList[] = $emailAddress->id;
$emailAddressIdList[] = $emailAddress->getId();
}
return $emailAddressIdList;

View File

@@ -36,19 +36,36 @@ class Orphan implements Filter
{
public function apply(SelectBuilder $queryBuilder): void
{
$queryBuilder->where([
'entityEmailAddress.id' => null,
]);
$queryBuilder->leftJoin(
'EntityEmailAddress',
'entityEmailAddress',
[
'emailAddressId:' => 'id',
'deleted' => false,
]
);
$queryBuilder->distinct();
$queryBuilder
->distinct()
->leftJoin(
'EntityEmailAddress',
'entityEmailAddress',
[
'emailAddressId:' => 'id',
'deleted' => false,
]
)
->leftJoin(
'EmailEmailAddress',
'emailEmailAddress',
[
'emailAddressId:' => 'id',
'deleted' => false,
]
)
->leftJoin(
'Email',
'email',
[
'fromEmailAddressId:' => 'id',
'deleted' => false,
]
)
->where([
'entityEmailAddress.id' => null,
'emailEmailAddress.id' => null,
'email.id' => null,
]);
}
}

View File

@@ -31,15 +31,19 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Controllers\RecordBase;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use stdClass;
class ActionHistoryRecord extends RecordBase
{
public function beforeUpdate(): void
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function beforeCreate(): void
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}

View File

@@ -29,6 +29,8 @@
namespace Espo\Controllers;
use Espo\Services\Attachment as Service;
use Espo\Core\{
Exceptions\Forbidden,
Exceptions\BadRequest,
@@ -37,18 +39,20 @@ use Espo\Core\{
Controllers\RecordBase,
};
use StdClass;
use stdClass;
class Attachment extends RecordBase
{
public function beforeList(): void
public function getActionList(Request $request, Response $response): stdClass
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
return parent::getActionList($request, $response);
}
public function postActionGetAttachmentFromImageUrl(Request $request): StdClass
public function postActionGetAttachmentFromImageUrl(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -57,13 +61,15 @@ class Attachment extends RecordBase
}
if (empty($data->field)) {
throw new BadRequest('postActionGetAttachmentFromImageUrl: No field specified');
throw new BadRequest('postActionGetAttachmentFromImageUrl: No field specified.');
}
return $this->getRecordService()->getAttachmentFromImageUrl($data)->getValueMap();
return $this->getAttachmentService()
->getAttachmentFromImageUrl($data)
->getValueMap();
}
public function postActionGetCopiedAttachment(Request $request): StdClass
public function postActionGetCopiedAttachment(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -72,10 +78,12 @@ class Attachment extends RecordBase
}
if (empty($data->field)) {
throw new BadRequest('postActionGetCopiedAttachment copy: No field specified');
throw new BadRequest('postActionGetCopiedAttachment copy: No field specified.');
}
return $this->getRecordService()->getCopiedAttachment($data)->getValueMap();
return $this->getAttachmentService()
->getCopiedAttachment($data)
->getValueMap();
}
public function getActionFile(Request $request, Response $response): void
@@ -86,7 +94,7 @@ class Attachment extends RecordBase
throw new BadRequest();
}
$fileData = $this->getRecordService()->getFileData($id);
$fileData = $this->getAttachmentService()->getFileData($id);
$response
->setHeader('Content-Type', $fileData->type)
@@ -94,4 +102,10 @@ class Attachment extends RecordBase
->setHeader('Content-Length', (string) $fileData->size)
->setBody($fileData->stream);
}
private function getAttachmentService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -30,8 +30,11 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Controllers\Record;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use stdClass;
class AuthLogRecord extends Record
{
@@ -40,22 +43,22 @@ class AuthLogRecord extends Record
return $this->user->isAdmin();
}
public function beforeUpdate(): void
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function beforeCreate(): void
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function beforeCreateLink(): void
public function postActionCreateLink(Request $request): bool
{
throw new Forbidden();
}
public function beforeRemoveLink(): void
public function deleteActionRemoveLink(Request $request): bool
{
throw new Forbidden();
}

View File

@@ -30,8 +30,8 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Controllers\Record;
use Espo\Core\Api\Request;
class AuthToken extends Record
{
@@ -40,12 +40,12 @@ class AuthToken extends Record
return $this->user->isAdmin();
}
public function beforeCreateLink(): void
public function postActionCreateLink(Request $request): bool
{
throw new Forbidden();
}
public function beforeRemoveLink(): void
public function deleteActionRemoveLink(Request $request): bool
{
throw new Forbidden();
}

View File

@@ -89,6 +89,7 @@ class DashboardTemplate extends Record
private function getDashboardTemplateService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -52,8 +52,10 @@ class Email extends Record
}
$id = $data->id;
$parentType = $data->parentType ?? null;
$field = $data->field ?? null;
return $this->getEmailService()->getCopiedAttachments($id);
return $this->getEmailService()->getCopiedAttachments($id, $parentType, null, $field);
}
/**
@@ -317,11 +319,13 @@ class Email extends Record
private function getEmailService(): Service
{
/** @var Service */
return $this->getRecordService();
}
private function getEmailTemplateService(): EmailTemplateService
{
/** @var EmailTemplateService */
return $this->getServiceFactory()->create('EmailTemplate');
}
}

View File

@@ -32,13 +32,20 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\Error;
use Espo\Services\EmailAccount as Service;
use Espo\Core\Di\CryptAware;
use Espo\Core\Di\CryptSetter;
use Espo\Core\{
Controllers\Record,
Api\Request,
};
class EmailAccount extends Record
class EmailAccount extends Record implements CryptAware
{
use CryptSetter;
protected function checkAccess(): bool
{
return $this->acl->check('EmailAccountScope');
@@ -59,7 +66,7 @@ class EmailAccount extends Record
'userId' => $data->userId ?? null,
];
return $this->getRecordService()->getFolders($params);
return $this->getEmailAccountService()->getFolders($params);
}
public function postActionTestConnection(Request $request): bool
@@ -69,24 +76,28 @@ class EmailAccount extends Record
if (is_null($data->password)) {
$emailAccount = $this->entityManager->getEntity('EmailAccount', $data->id);
if (!$emailAccount || !$emailAccount->id) {
if (!$emailAccount || !$emailAccount->getId()) {
throw new Error();
}
if (
$emailAccount->get('assignedUserId') != $this->user->id &&
$emailAccount->get('assignedUserId') !== $this->user->getId() &&
!$this->user->isAdmin()
) {
throw new Forbidden();
}
$data->password = $this->getContainer()
->get('crypt')
->decrypt($emailAccount->get('password'));
$data->password = $this->crypt->decrypt($emailAccount->get('password'));
}
$this->getRecordService()->testConnection(get_object_vars($data));
$this->getEmailAccountService()->testConnection(get_object_vars($data));
return true;
}
private function getEmailAccountService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -31,6 +31,8 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Services\EmailAddress as Service;
use Espo\Core\{
Controllers\RecordBase,
Api\Request,
@@ -58,6 +60,12 @@ class EmailAddress extends RecordBase
$onlyActual = $request->getQueryParam('onlyActual') === 'true';
return $this->getRecordService()->searchInAddressBook($q, $maxSize, $onlyActual);
return $this->getEmailAddressService()->searchInAddressBook($q, $maxSize, $onlyActual);
}
private function getEmailAddressService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -31,6 +31,8 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Services\EmailFolder as Service;
use Espo\Core\{
Controllers\RecordBase,
Api\Request,
@@ -46,7 +48,7 @@ class EmailFolder extends RecordBase
throw new BadRequest();
}
$this->getRecordService()->moveUp($data->id);
$this->getEmailFolderService()->moveUp($data->id);
return true;
}
@@ -59,13 +61,19 @@ class EmailFolder extends RecordBase
throw new BadRequest();
}
$this->getRecordService()->moveDown($data->id);
$this->getEmailFolderService()->moveDown($data->id);
return true;
}
public function getActionListAll(): array
{
return $this->getRecordService()->listAll();
return $this->getEmailFolderService()->listAll();
}
private function getEmailFolderService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -29,20 +29,22 @@
namespace Espo\Controllers;
use Espo\Services\EmailTemplate as Service;
use Espo\Core\{
Controllers\Record,
Api\Request,
};
use StdClass;
use stdClass;
class EmailTemplate extends Record
{
public function actionParse(Request $request): StdClass
public function actionParse(Request $request): stdClass
{
$id = $request->getQueryParam('id');
return (object) $this->getRecordService()->parse(
return (object) $this->getEmailTempalteService()->parse(
$id,
[
'emailAddress' => $request->getQueryParam('emailAddress'),
@@ -54,4 +56,10 @@ class EmailTemplate extends Record
true
);
}
private function getEmailTempalteService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -235,19 +235,14 @@ class EntityManager
'labelForeign',
];
$additionalParamList = [];
$params = [];
foreach ($paramList as $item) {
if (array_key_exists($item, $data)) {
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
}
}
foreach ($additionalParamList as $item) {
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
}
if (array_key_exists('linkMultipleField', $data)) {
$params['linkMultipleField'] = $data['linkMultipleField'];
}

View File

@@ -38,7 +38,7 @@ use Espo\Core\{
Api\Response,
};
use StdClass;
use stdClass;
class Extension extends RecordBase
{
@@ -47,7 +47,7 @@ class Extension extends RecordBase
return $this->user->isAdmin();
}
public function postActionUpload(Request $request): StdClass
public function postActionUpload(Request $request): stdClass
{
$body = $request->getBodyContents();
@@ -117,12 +117,12 @@ class Extension extends RecordBase
return true;
}
public function beforeCreate(): void
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function beforeUpdate(): void
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}

View File

@@ -31,6 +31,8 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Services\ExternalAccount as Service;
use Espo\Core\{
Controllers\RecordBase,
Api\Request,
@@ -38,7 +40,7 @@ use Espo\Core\{
Record\ReadParams,
};
use StdClass;
use stdClass;
class ExternalAccount extends RecordBase
{
@@ -49,20 +51,22 @@ class ExternalAccount extends RecordBase
return $this->acl->checkScope('ExternalAccount');
}
public function getActionList(Request $request, Response $response): StdClass
public function getActionList(Request $request, Response $response): stdClass
{
$integrations = $this->entityManager->getRepository('Integration')->find();
$integrations = $this->entityManager
->getRDBRepository('Integration')
->find();
$list = [];
foreach ($integrations as $entity) {
if (
$entity->get('enabled') &&
$this->metadata->get('integrations.' . $entity->id .'.allowUserAccounts')
$this->metadata->get('integrations.' . $entity->getId() .'.allowUserAccounts')
) {
$userAccountAclScope = $this->metadata
->get(['integrations', $entity->id, 'userAccountAclScope']);
->get(['integrations', $entity->getId(), 'userAccountAclScope']);
if ($userAccountAclScope) {
if (!$this->acl->checkScope($userAccountAclScope)) {
@@ -81,7 +85,7 @@ class ExternalAccount extends RecordBase
];
}
public function getActionGetOAuth2Info(Request $request): ?StdClass
public function getActionGetOAuth2Info(Request $request): ?stdClass
{
$id = $request->getQueryParam('id');
@@ -97,14 +101,14 @@ class ExternalAccount extends RecordBase
return (object) [
'clientId' => $entity->get('clientId'),
'redirectUri' => $this->config->get('siteUrl') . '?entryPoint=oauthCallback',
'isConnected' => $this->getRecordService()->ping($integration, $userId)
'isConnected' => $this->getExternalAccount()->ping($integration, $userId)
];
}
return null;
}
public function getActionRead(Request $request, Response $response): StdClass
public function getActionRead(Request $request, Response $response): stdClass
{
$id = $request->getRouteParam('id');
@@ -113,7 +117,7 @@ class ExternalAccount extends RecordBase
->getValueMap();
}
public function putActionUpdate(Request $request, Response $response): StdClass
public function putActionUpdate(Request $request, Response $response): stdClass
{
$id = $request->getRouteParam('id');
@@ -151,8 +155,12 @@ class ExternalAccount extends RecordBase
throw new Forbidden();
}
$service = $this->getRecordService();
return $this->getExternalAccount()->authorizationCode($integration, $userId, $code);
}
return $service->authorizationCode($integration, $userId, $code);
private function getExternalAccount(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Tools\Import\Params as ImportParams;
use Espo\Tools\Import\Service as Service;
@@ -143,23 +144,18 @@ class Import extends Record
return true;
}
public function beforePatch(): void
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new BadRequest();
throw new Forbidden();
}
public function beforeUpdate(): void
public function postActionCreateLink(Request $request): bool
{
throw new BadRequest();
throw new Forbidden();
}
public function beforeCreateLink(): void
public function deleteActionRemoveLink(Request $request): bool
{
throw new BadRequest();
}
public function beforeRemoveLink(): void
{
throw new BadRequest();
throw new Forbidden();
}
}

View File

@@ -29,13 +29,22 @@
namespace Espo\Controllers;
use Espo\Services\InboundEmail as Service;
use Espo\Core\Exceptions\Error;
use Espo\Core\Di\CryptAware;
use Espo\Core\Di\CryptSetter;
use Espo\Core\{
Controllers\Record,
Api\Request,
};
class InboundEmail extends Record
class InboundEmail extends Record implements CryptAware
{
use CryptSetter;
protected function checkAccess(): bool
{
return $this->getUser()->isAdmin();
@@ -54,7 +63,7 @@ class InboundEmail extends Record
'id' => $data->id ?? null,
];
return $this->getRecordService()->getFolders($params);
return $this->getInboundEmailService()->getFolders($params);
}
public function postActionTestConnection(Request $request): bool
@@ -64,17 +73,21 @@ class InboundEmail extends Record
if (is_null($data->password)) {
$inboundEmail = $this->entityManager->getEntity('InboundEmail', $data->id);
if (!$inboundEmail || !$inboundEmail->id) {
if (!$inboundEmail || !$inboundEmail->getId()) {
throw new Error();
}
$data->password = $this->getContainer()
->get('crypt')
->decrypt($inboundEmail->get('password'));
$data->password = $this->crypt->decrypt($inboundEmail->get('password'));
}
$this->getRecordService()->testConnection(get_object_vars($data));
$this->getInboundEmailService()->testConnection(get_object_vars($data));
return true;
}
private function getInboundEmailService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -30,9 +30,12 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use stdClass;
class Job extends RecordBase
{
protected function checkAccess(): bool
@@ -40,12 +43,12 @@ class Job extends RecordBase
return $this->user->isAdmin();
}
public function beforeCreate(): void
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function beforeUpdate(): void
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}

View File

@@ -41,7 +41,7 @@ use Espo\Core\{
Api\Response,
};
use StdClass;
use stdClass;
class LeadCapture extends Record
{
@@ -54,10 +54,6 @@ class LeadCapture extends Record
throw new BadRequest('No API key provided.');
}
if (empty($data)) {
throw new BadRequest('No payload provided.');
}
$allowOrigin = $this->config->get('leadCaptureAllowOrigin', '*');
$response->setHeader('Access-Control-Allow-Origin', $allowOrigin);
@@ -88,7 +84,7 @@ class LeadCapture extends Record
return true;
}
public function postActionGenerateNewApiKey(Request $request): StdClass
public function postActionGenerateNewApiKey(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -112,6 +108,7 @@ class LeadCapture extends Record
private function getLeadCaptureService(): Service
{
/** @var Service */
return $this->getRecordService();
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Error;
use Espo\Services\Notification as Service;
use Espo\Core\{
Controllers\RecordBase,
@@ -37,13 +37,13 @@ use Espo\Core\{
Api\Response,
};
use StdClass;
use stdClass;
class Notification extends RecordBase
{
public static $defaultAction = 'list';
public function getActionList(Request $request, Response $response): StdClass
public function getActionList(Request $request, Response $response): stdClass
{
$userId = $this->user->getId();
@@ -52,7 +52,7 @@ class Notification extends RecordBase
$offset = $searchParams->getOffset();
$maxSize = $searchParams->getMaxSize();
$after = $request->get('after');
$after = $request->getQueryParam('after');
$params = [
'offset' => $offset,
@@ -60,9 +60,7 @@ class Notification extends RecordBase
'after' => $after,
];
$recordCollection = $this->recordServiceContainer
->get('Notification')
->getList($userId, $params);
$recordCollection = $this->getNotificationService()->getList($userId, $params);
return (object) [
'total' => $recordCollection->getTotal(),
@@ -74,15 +72,21 @@ class Notification extends RecordBase
{
$userId = $this->user->getId();
return $this->recordServiceContainer->get('Notification')->getNotReadCount($userId);
return $this->getNotificationService()->getNotReadCount($userId);
}
public function postActionMarkAllRead(Request $request): bool
{
$userId = $this->user->getId();
$this->recordServiceContainer->get('Notification')->markAllRead($userId);
$this->getNotificationService()->markAllRead($userId);
return true;
}
private function getNotificationService(): Service
{
/** @var Service */
return $this->recordServiceContainer->get('Notification');
}
}

View File

@@ -83,10 +83,6 @@ class Preferences
$data = $request->getParsedBody();
if (!$userId) {
throw new BadRequest();
}
return $this->service
->update($userId, $data)
->getValueMap();

View File

@@ -66,9 +66,9 @@ class Stream
$offset = $searchParams->getOffset();
$maxSize = $searchParams->getMaxSize();
$after = $request->get('after');
$filter = $request->get('filter');
$skipOwn = $request->get('skipOwn') === 'true';
$after = $request->getQueryParam('after');
$filter = $request->getQueryParam('filter');
$skipOwn = $request->getQueryParam('skipOwn') === 'true';
$result = $this->service->find($scope, $id, [
'offset' => $offset,
@@ -96,9 +96,8 @@ class Stream
$offset = $searchParams->getOffset();
$maxSize = $searchParams->getMaxSize();
$after = $request->get('after');
$where = $request->get('where');
$after = $request->getQueryParam('after');
$where = $request->getQueryParam('where');
$result = $this->service->find($scope, $id, [
'offset' => $offset,

View File

@@ -42,6 +42,8 @@ class TwoFactorEmail
{
private $service;
private $user;
public function __construct(Service $service, User $user)
{
$this->service = $service;

View File

@@ -42,6 +42,8 @@ class TwoFactorSms
{
private $service;
private $user;
public function __construct(Service $service, User $user)
{
$this->service = $service;

View File

@@ -34,6 +34,8 @@ use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Services\User as Service;
use Espo\Core\{
Controllers\Record,
Api\Request,
@@ -41,11 +43,11 @@ use Espo\Core\{
Select\Where\Item as WhereItem,
};
use StdClass;
use stdClass;
class User extends Record
{
public function getActionAcl(Request $request): StdClass
public function getActionAcl(Request $request): stdClass
{
$userId = $request->getQueryParam('id');
@@ -53,7 +55,7 @@ class User extends Record
throw new Error();
}
if (!$this->user->isAdmin() && $this->user->getId() != $userId) {
if (!$this->user->isAdmin() && $this->user->getId() !== $userId) {
throw new Forbidden();
}
@@ -77,18 +79,17 @@ class User extends Record
throw new BadRequest();
}
$this->getService('User')
->changePassword(
$this->user->getId(),
$data->password,
true,
$data->currentPassword
);
$this->getUserService()->changePassword(
$this->user->getId(),
$data->password,
true,
$data->currentPassword
);
return true;
}
public function postActionChangePasswordByRequest(Request $request): StdClass
public function postActionChangePasswordByRequest(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -96,7 +97,7 @@ class User extends Record
throw new BadRequest();
}
return $this->getService('User')->changePasswordByRequest($data->requestId, $data->password);
return $this->getUserService()->changePasswordByRequest($data->requestId, $data->password);
}
public function postActionPasswordChangeRequest(Request $request): bool
@@ -116,12 +117,12 @@ class User extends Record
$url = $data->url;
}
$this->getService('User')->passwordChangeRequest($userName, $emailAddress, $url);
$this->getUserService()->passwordChangeRequest($userName, $emailAddress, $url);
return true;
}
public function postActionGenerateNewApiKey(Request $request): StdClass
public function postActionGenerateNewApiKey(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -133,7 +134,7 @@ class User extends Record
throw new Forbidden();
}
return $this->getRecordService()
return $this->getUserService()
->generateNewApiKeyForEntity($data->id)
->getValueMap();
}
@@ -150,23 +151,27 @@ class User extends Record
throw new Forbidden();
}
$this->getRecordService()->generateNewPasswordForUser($data->id);
$this->getUserService()->generateNewPasswordForUser($data->id);
return true;
}
public function beforeCreateLink(): void
public function postActionCreateLink(Request $request): bool
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
return parent::postActionCreateLink($request);
}
public function beforeRemoveLink(): void
public function deleteActionRemoveLink(Request $request): bool
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
return parent::deleteActionRemoveLink($request);
}
protected function fetchSearchParamsFromRequest(Request $request): SearchParams
@@ -187,4 +192,9 @@ class User extends Record
])
);
}
private function getUserService(): Service
{
return $this->getServiceFactory()->create('User');
}
}

View File

@@ -44,6 +44,8 @@ class UserSecurity
{
private $service;
private $user;
public function __construct(Service $service, User $user)
{
$this->service = $service;

View File

@@ -35,7 +35,7 @@ use Espo\Core\{
Api\Response,
};
use StdClass;
use stdClass;
class Webhook extends RecordBase
{
@@ -48,7 +48,7 @@ class Webhook extends RecordBase
return true;
}
public function postActionCreate(Request $request, Response $response): StdClass
public function postActionCreate(Request $request, Response $response): stdClass
{
$result = parent::postActionCreate($request, $response);

View File

@@ -115,7 +115,7 @@ class Acl
* whether a scope level is set to 'enabled'.
*
* @param string|Entity $subject An entity type or entity.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string|null $action Action to check. Constants are available in the `Table` class.
*
* @throws NotImplemented
*/
@@ -128,7 +128,7 @@ class Acl
* The same as `check` but does not throw NotImplemented exception.
*
* @param string|Entity $subject An entity type or entity.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string|null $action Action to check. Constants are available in the `Table` class.
*/
public function tryCheck($subject, ?string $action = null): bool
{
@@ -150,7 +150,7 @@ class Acl
* Check access to a specific entity.
*
* @param Entity $entity An entity to check.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string $action Action to check. Constants are available in the `Table` class.
*/
public function checkEntity(Entity $entity, string $action = Table::ACTION_READ): bool
{
@@ -216,8 +216,8 @@ class Acl
/**
* Get attributes forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements. *
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements. *
* @return string[]
*/
public function getScopeForbiddenAttributeList(
string $scope,
@@ -232,8 +232,8 @@ class Acl
/**
* Get fields forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
*/
public function getScopeForbiddenFieldList(
string $scope,
@@ -248,8 +248,8 @@ class Acl
/**
* Get links forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
*/
public function getScopeForbiddenLinkList(
string $scope,
@@ -283,8 +283,8 @@ class Acl
/**
* Get a restricted field list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedFieldList(string $scope, $type): array
{
@@ -294,8 +294,8 @@ class Acl
/**
* Get a restricted attribute list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedAttributeList(string $scope, $type): array
{
@@ -305,8 +305,8 @@ class Acl
/**
* Get a restricted link list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedLinkList(string $scope, $type): array
{

View File

@@ -29,6 +29,11 @@
namespace Espo\Core\Acl;
/**
* @template TEntity of \Espo\ORM\Entity
* @extends AccessEntityCreateChecker<TEntity>, AccessEntityCreateChecker<TEntity>, AccessEntityReadChecker<TEntity>,
* AccessEntityEditChecker<TEntity>, AccessEntityDeleteChecker<TEntity>
*/
interface AccessEntityCREDChecker extends
AccessEntityCreateChecker,

View File

@@ -29,6 +29,11 @@
namespace Espo\Core\Acl;
/**
* @template TEntity of \Espo\ORM\Entity
* @extends AccessEntityCreateChecker<TEntity>, AccessEntityCreateChecker<TEntity>, AccessEntityReadChecker<TEntity>,
* AccessEntityEditChecker<TEntity>, AccessEntityDeleteChecker<TEntity>, AccessEntityStreamChecker<TEntity>
*/
interface AccessEntityCREDSChecker extends
AccessEntityCreateChecker,

View File

@@ -30,13 +30,17 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @template TEntity of Entity
*/
interface AccessEntityCreateChecker extends AccessCreateChecker
{
/**
* Check 'create' access for an entity.
*
* @phpstan-param TEntity $entity
*/
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool;
}

View File

@@ -30,13 +30,17 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @template TEntity of Entity
*/
interface AccessEntityDeleteChecker extends AccessDeleteChecker
{
/**
* Check 'delete' access for an entity.
*
* @phpstan-param TEntity $entity
*/
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool;
}

View File

@@ -30,13 +30,17 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @template TEntity of Entity
*/
interface AccessEntityEditChecker extends AccessEditChecker
{
/**
* Check 'edit' access for an entity.
*
* @phpstan-param TEntity $entity
*/
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool;
}

View File

@@ -30,13 +30,17 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @template TEntity of Entity
*/
interface AccessEntityReadChecker extends AccessReadChecker
{
/**
* Check 'read' access for entity.
*
* @phpstan-param TEntity $entity
*/
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool;
}

View File

@@ -30,13 +30,17 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @template TEntity of Entity
*/
interface AccessEntityStreamChecker extends AccessStreamChecker
{
/**
* Check 'stream' access for an entity.
*
* @phpstan-param TEntity $entity
*/
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool;
}

View File

@@ -30,7 +30,6 @@
namespace Espo\Core\Acl\AssignmentChecker;
use Espo\Core\{
Utils\ClassFinder,
Utils\Metadata,
InjectableFactory,
Acl\AssignmentChecker,
@@ -42,18 +41,14 @@ class AssignmentCheckerFactory
{
private $defaultClassName = DefaultAssignmentChecker::class;
private $classFinder;
private $metadata;
private $injectableFactory;
public function __construct(
ClassFinder $classFinder,
Metadata $metadata,
InjectableFactory $injectableFactory
) {
$this->classFinder = $classFinder;
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}

View File

@@ -33,22 +33,16 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\{
Utils\Metadata,
Acl\AssignmentChecker,
};
use Espo\Core\Acl\AssignmentChecker;
class AssignmentCheckerManager
{
private $checkerCache = [];
private $metadata;
private $factory;
public function __construct(Metadata $metadata, AssignmentCheckerFactory $factory)
public function __construct(AssignmentCheckerFactory $factory)
{
$this->metadata = $metadata;
$this->factory = $factory;
}

View File

@@ -29,6 +29,10 @@
namespace Espo\Core\Acl;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Repositories\User as UserRepository;
use Espo\ORM\{
Entity,
EntityManager,
@@ -155,9 +159,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
$teamIdList = $user->get(self::ATTR_TEAMS_IDS);
if (
!$this->entityManager
->getRepository('User')
->checkBelongsToAnyOfTeams($assignedUserId, $teamIdList)
!$this->getUserRepository()->checkBelongsToAnyOfTeams($assignedUserId, $teamIdList)
) {
return false;
}
@@ -166,6 +168,12 @@ class DefaultAssignmentChecker implements AssignmentChecker
return true;
}
private function getUserRepository(): UserRepository
{
/** @var UserRepository */
return $this->entityManager->getRepository('User');
}
protected function isPermittedTeams(User $user, Entity $entity): bool
{
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
@@ -174,6 +182,10 @@ class DefaultAssignmentChecker implements AssignmentChecker
return true;
}
if (!$entity instanceof CoreEntity) {
return true;
}
if (!$entity->hasLinkMultipleField(self::FIELD_TEAMS)) {
return true;
}
@@ -224,7 +236,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
return true;
}
private function isPermittedTeamsEmpty(User $user, Entity $entity): bool
private function isPermittedTeamsEmpty(User $user, CoreEntity $entity): bool
{
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
@@ -250,6 +262,10 @@ class DefaultAssignmentChecker implements AssignmentChecker
protected function isPermittedAssignedUsers(User $user, Entity $entity): bool
{
if (!$entity instanceof CoreEntity) {
return true;
}
if (!$entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
return true;
}
@@ -309,7 +325,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
return true;
}
private function isPermittedAssignedUsersLevelNo(User $user, Entity $entity): bool
private function isPermittedAssignedUsersLevelNo(User $user, CoreEntity $entity): bool
{
$userIdList = $entity->getLinkMultipleIdList(self::FIELD_ASSIGNED_USERS);
@@ -328,7 +344,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
return true;
}
private function isPermittedAssignedUsersLevelTeam(User $user, Entity $entity): bool
private function isPermittedAssignedUsersLevelTeam(User $user, CoreEntity $entity): bool
{
$userIdList = $entity->getLinkMultipleIdList(self::FIELD_ASSIGNED_USERS);
@@ -342,9 +358,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
}
if (
!$this->entityManager
->getRepository('User')
->checkBelongsToAnyOfTeams($userId, $teamIdList)
!$this->getUserRepository()->checkBelongsToAnyOfTeams($userId, $teamIdList)
) {
return false;
}

View File

@@ -30,7 +30,7 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Entities\User;
/**
@@ -67,7 +67,7 @@ class DefaultOwnershipChecker implements OwnershipOwnChecker, OwnershipTeamCheck
}
}
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
if ($entity instanceof CoreEntity && $entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
if ($entity->hasLinkMultipleId(self::FIELD_ASSIGNED_USERS, $user->getId())) {
return true;
}
@@ -78,6 +78,8 @@ class DefaultOwnershipChecker implements OwnershipOwnChecker, OwnershipTeamCheck
public function checkTeam(User $user, Entity $entity): bool
{
assert($entity instanceof CoreEntity);
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
if (

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Acl;
use StdClass;
use stdClass;
use RuntimeException;
/**
@@ -37,6 +37,9 @@ use RuntimeException;
*/
class FieldData
{
/**
* @phpstan-ignore-next-line
*/
private $raw;
private $actionData = [];
@@ -77,7 +80,7 @@ class FieldData
/**
* Create from a raw table value.
*/
public static function fromRaw(StdClass $raw): self
public static function fromRaw(stdClass $raw): self
{
$obj = new self();

View File

@@ -37,7 +37,7 @@ use Espo\Core\{
Utils\Config,
};
use StdClass;
use stdClass;
/**
* Lists of restricted fields can be obtained from here. Restricted fields
@@ -104,7 +104,7 @@ class GlobalRestricton
$isFromCache = true;
if (!$this->data instanceof StdClass) {
if (!$this->data instanceof stdClass) {
$this->log->error("ACL GlobalRestricton: Bad data fetched from cache.");
$this->data = null;
@@ -122,7 +122,7 @@ class GlobalRestricton
protected function storeCacheFile(): void
{
$this->dataCache->store($this->cacheKey, $this->data, true);
$this->dataCache->store($this->cacheKey, $this->data);
}
protected function buildData(): void

View File

@@ -38,7 +38,7 @@ use Espo\Core\{
Utils\ObjectUtil,
};
use StdClass;
use stdClass;
use RuntimeException;
/**
@@ -100,7 +100,7 @@ class Map
/**
* Get raw data (for front-end).
*/
public function getData(): StdClass
public function getData(): stdClass
{
return ObjectUtil::clone($this->data);
}
@@ -108,11 +108,11 @@ class Map
/**
* Get a list of forbidden attributes for a scope and action.
*
* @param $scope A scope.
* @param $action An action.
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
* @param string $scope A scope.
* @param string $action An action.
* @param string $thresholdLevel An attribute will be treated as forbidden if the level is
* equal to or lower than the threshold.
* @return array<int, string>
* @return string[]
*/
public function getScopeForbiddenAttributeList(
string $scope,
@@ -180,11 +180,11 @@ class Map
/**
* Get a list of forbidden fields for a scope and action.
*
* @param $scope A scope.
* @param $action An action.
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
* @param string $scope A scope.
* @param string $action An action.
* @param string $thresholdLevel An attribute will be treated as forbidden if the level is
* equal to or lower than the threshold.
* @return array<int, string>
* @return string[]
*/
public function getScopeForbiddenFieldList(
string $scope,

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Acl;
use StdClass;
use stdClass;
use InvalidArgumentException;
use RuntimeException;
@@ -150,14 +150,16 @@ class ScopeData
/**
* Create from a raw table value.
*
* @param StdClass|bool $raw
* @param stdClass|bool $raw
* @return self
*/
public static function fromRaw($raw): self
{
/** @var mixed $raw */
$obj = new self();
if ($raw instanceof StdClass) {
if ($raw instanceof stdClass) {
$obj->isBoolean = false;
$obj->actionData = get_object_vars($raw);

View File

@@ -31,10 +31,8 @@ namespace Espo\Core\Acl\Table;
use Espo\ORM\EntityManager;
use Espo\Entities\{
User,
Role as RoleEntity,
};
use Espo\Entities\User;
use Espo\Entities\Role as RoleEntity;
class DefaultRoleListProvider implements RoleListProvider
{
@@ -49,14 +47,15 @@ class DefaultRoleListProvider implements RoleListProvider
}
/**
* @return array<int, Role>
* @return Role[]
*/
public function get(): array
{
$roleList = [];
/** @var iterable<RoleEntity> */
$userRoleList = $this->entityManager
->getRepository('User')
->getRDBRepository('User')
->getRelation($this->user, 'roles')
->find();
@@ -64,14 +63,16 @@ class DefaultRoleListProvider implements RoleListProvider
$roleList[] = $role;
}
/** @var iterable<\Espo\Entities\Team> */
$teamList = $this->entityManager
->getRepository('User')
->getRDBRepository('User')
->getRelation($this->user, 'teams')
->find();
foreach ($teamList as $team) {
/** @var iterable<RoleEntity> */
$teamRoleList = $this->entityManager
->getRepository('Team')
->getRDBRepository('Team')
->getRelation($team, 'roles')
->find();

View File

@@ -196,6 +196,9 @@ class DefaultTable implements Table
$aclTableList = [];
$fieldTableList = [];
$aclTable = (object) [];
$fieldTable = (object) [];
if (!$this->user->isAdmin()) {
$roleList = $this->roleListProvider->get();

View File

@@ -33,8 +33,9 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\Core\{
ORM\EntityManager,
Acl,
Acl\GlobalRestricton,
Acl\OwnerUserFieldProvider,
@@ -97,18 +98,39 @@ class AclManager
Table::ACTION_STREAM => AccessStreamChecker::class,
];
/**
* @var AccessCheckerFactory|\Espo\Core\Portal\Acl\AccessChecker\AccessCheckerFactory
*/
protected $accessCheckerFactory;
/**
* @var OwnershipCheckerFactory|\Espo\Core\Portal\Acl\OwnershipChecker\OwnershipCheckerFactory
*/
protected $ownershipCheckerFactory;
protected $tableFactory;
/**
* @var TableFactory
*/
private $tableFactory;
protected $mapFactory;
/**
* @var MapFactory
*/
private $mapFactory;
/**
* @var GlobalRestricton
*/
protected $globalRestricton;
/**
* @var OwnerUserFieldProvider
*/
protected $ownerUserFieldProvider;
/**
* @var EntityManager
*/
protected $entityManager;
public function __construct(
@@ -255,7 +277,7 @@ class AclManager
*
* @param User $user A user to check for.
* @param string|Entity $subject An entity type or entity.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string|null $action Action to check. Constants are available in the `Table` class.
*
* @throws NotImplemented
*/
@@ -265,6 +287,7 @@ class AclManager
return $this->checkScope($user, $subject, $action);
}
/** @var mixed */
$entity = $subject;
if ($entity instanceof Entity) {
@@ -281,7 +304,7 @@ class AclManager
*
* @param User $user A user to check for.
* @param string|Entity $subject An entity type or entity.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string|null $action Action to check. Constants are available in the `Table` class.
*/
public function tryCheck(User $user, $subject, ?string $action = null): bool
{
@@ -298,7 +321,7 @@ class AclManager
*
* @param User $user A user to check for.
* @param Entity $entity An entity to check.
* @param string|null Action to check. Constants are available in the `Table` class.
* @param string $action Action to check. Constants are available in the `Table` class.
*
* @throws NotImplemented
*/
@@ -478,8 +501,8 @@ class AclManager
/**
* Get attributes forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
*/
public function getScopeForbiddenAttributeList(
User $user,
@@ -506,8 +529,8 @@ class AclManager
/**
* Get fields forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
*/
public function getScopeForbiddenFieldList(
User $user,
@@ -534,8 +557,8 @@ class AclManager
/**
* Get links forbidden for a user.
*
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return array<int, string>
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
*/
public function getScopeForbiddenLinkList(
User $user,
@@ -581,11 +604,10 @@ class AclManager
if ($permission === Table::LEVEL_TEAM) {
$teamIdList = $user->getLinkMultipleIdList('teams');
if (
!$this->entityManager
->getRepository('User')
->checkBelongsToAnyOfTeams($userId, $teamIdList)
) {
/** @var \Espo\Repositories\User $userRepository */
$userRepository = $this->entityManager->getRepository('User');
if (!$userRepository->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
return false;
}
}
@@ -618,8 +640,8 @@ class AclManager
/**
* Get a restricted field list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedFieldList(string $scope, $type): array
{
@@ -644,8 +666,8 @@ class AclManager
/**
* Get a restricted attribute list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedAttributeList(string $scope, $type): array
{
@@ -670,8 +692,8 @@ class AclManager
/**
* Get a restricted link list for a specific scope by a restriction type.
*
* @param string|array<int, string> $type
* @return array<int, string>
* @param string|string[] $type
* @return string[]
*/
public function getScopeRestrictedLinkList(string $scope, $type): array
{

View File

@@ -35,6 +35,8 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\AclManager;
use Espo\Core\{
ORM\EntityManager,
Portal\AclManager as PortalAclManager,

View File

@@ -52,17 +52,17 @@ use Espo\{
class ConvertCurrency implements Action
{
protected $acl;
private $acl;
protected $entityManager;
private $entityManager;
protected $fieldUtil;
private $fieldUtil;
protected $metadata;
private $metadata;
protected $configDataProvider;
private $configDataProvider;
protected $currencyConverter;
private $currencyConverter;
public function __construct(
Acl $acl,
@@ -125,7 +125,10 @@ class ConvertCurrency implements Action
}
protected function convertEntity(
Entity $entity, array $fieldList, string $targetCurrency, CurrencyRates $rates
Entity $entity,
array $fieldList,
string $targetCurrency,
CurrencyRates $rates
) {
foreach ($fieldList as $field) {
$amount = $entity->get($field);

View File

@@ -207,6 +207,7 @@ class Merger
{
$list = [];
/** @var iterable<PhoneNumber> */
$collection = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'phoneNumbers')
@@ -226,6 +227,7 @@ class Merger
{
$list = [];
/** @var iterable<EmailAddress> */
$collection = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'emailAddresses')

View File

@@ -31,21 +31,25 @@ namespace Espo\Core\Action;
use Espo\Core\Utils\ObjectUtil;
use StdClass;
use stdClass;
class Data
{
private $data;
private function __construct()
{
$this->data = (object) [];
}
public function getRaw(): StdClass
public function getRaw(): stdClass
{
return ObjectUtil::clone($this->data);
}
/**
* Get an item value.
*
* @return mixed
*/
public function get(string $name)
@@ -53,12 +57,15 @@ class Data
return $this->getRaw()->$name ?? null;
}
/**
* Has an item.
*/
public function has(string $name): bool
{
return property_exists($this->data, $name);
}
public static function fromRaw(StdClass $data): self
public static function fromRaw(stdClass $data): self
{
$obj = new self();
@@ -66,4 +73,23 @@ class Data
return $obj;
}
/**
* Clone with an item value.
*
* @param mixed $value
*/
public function with(string $name, $value): self
{
$obj = clone $this;
$obj->data->$name = $value;
return $obj;
}
public function __clone()
{
$this->data = ObjectUtil::clone($this->data);
}
}

View File

@@ -41,7 +41,7 @@ use Espo\Core\{
use Espo\ORM\Entity;
use StdClass;
use stdClass;
class Service
{
@@ -68,7 +68,7 @@ class Service
* @throws BadRequest
* @throws NotFound
*/
public function process(string $entityType, string $action, string $id, StdClass $data): Entity
public function process(string $entityType, string $action, string $id, stdClass $data): Entity
{
if (!$this->acl->checkScope($entityType)) {
throw new ForbiddenSilent();
@@ -91,10 +91,6 @@ class Service
$entity = $service->read($id, ReadParams::create());
if (!$entity) {
throw new NotFound();
}
return $entity;
}
}

View File

@@ -35,11 +35,13 @@ use Espo\Core\{
Utils\Json,
Api\Request,
Api\Response,
Api\RequestWrapper,
Exceptions\NotFound,
};
use ReflectionClass;
use StdClass;
use ReflectionNamedType;
use stdClass;
/**
* Creates controller instances and processes actions.
@@ -103,7 +105,7 @@ class ActionProcessor
$data = $request->getBodyContents();
if ($data && $request->getContentType() === 'application/json') {
if ($data && $this->getRequestContentType($request) === 'application/json') {
$data = json_decode($data);
}
@@ -137,7 +139,7 @@ class ActionProcessor
is_float($result) ||
is_array($result) ||
is_bool($result) ||
$result instanceof StdClass
$result instanceof stdClass
) {
$responseContents = Json::encode($result);
}
@@ -161,7 +163,11 @@ class ActionProcessor
$type = $params[0]->getType();
if (!$type || $type->isBuiltin()) {
if (
!$type ||
!$type instanceof ReflectionNamedType ||
$type->isBuiltin()
) {
return false;
}
@@ -198,4 +204,13 @@ class ActionProcessor
'name' => $name,
]);
}
private function getRequestContentType(Request $request): ?string
{
if ($request instanceof RequestWrapper) {
return $request->getContentType();
}
return null;
}
}

View File

@@ -218,9 +218,7 @@ class Auth
return;
}
$response->setStatus(500);
$this->log->error("Auth: " . $e->getMessage());
throw $e;
}
protected function handleUnauthorized(Response $response, bool $showDialog): void

View File

@@ -29,12 +29,13 @@
namespace Espo\Core\Api;
use Espo\Core\Exceptions\HasBody;
use Espo\Core\{
Api\Request,
Api\Response,
Exceptions\Conflict,
Exceptions\Error,
Utils\Log,
Utils\Config,
};
use Throwable;
@@ -72,9 +73,12 @@ class ErrorOutput
private $log;
public function __construct(Log $log)
private $config;
public function __construct(Log $log, Config $config)
{
$this->log = $log;
$this->config = $config;
}
public function process(
@@ -105,7 +109,7 @@ class ErrorOutput
bool $toPrintBody = false
): void {
$message = $exception->getMessage() ?? '';
$message = $exception->getMessage();
$statusCode = $exception->getCode();
if ($route) {
@@ -131,13 +135,14 @@ class ErrorOutput
}
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
if ($messageLineFile) {
$logMessageItemList[] = $messageLineFile;
}
$logMessageItemList[] = $messageLineFile;
$logMessage = "($statusCode) " . implode("; ", $logMessageItemList);
if ($this->toPrintTrace()) {
$logMessage .= " :: " . $exception->getTraceAsString();
}
$this->log->log($logLevel, $logMessage);
$toPrintBodyXStatusReason = !in_array(
@@ -155,7 +160,7 @@ class ErrorOutput
$response->setHeader('X-Status-Reason', $this->stripInvalidCharactersFromHeaderValue($message));
}
if ($this->doesExceptionHaveBody($exception)) {
if ($exception instanceof HasBody && $this->exceptionHasBody($exception)) {
$response->writeBody($exception->getBody());
$toPrintBody = false;
@@ -176,20 +181,13 @@ class ErrorOutput
}
}
private function doesExceptionHaveBody(Throwable $exception): bool
private function exceptionHasBody(Throwable $exception): bool
{
if (
!$exception instanceof Error &&
!$exception instanceof Conflict
) {
if (!$exception instanceof HasBody) {
return false;
}
$exceptionBody = null;
if (method_exists($exception, 'getBody')) {
$exceptionBody = $exception->getBody();
}
$exceptionBody = $exception->getBody();
return $exceptionBody !== null;
}
@@ -227,7 +225,7 @@ class ErrorOutput
{
$requestBodyString = $this->clearPasswords($request->getBodyContents());
$message = $exception->getMessage() ?? '';
$message = $exception->getMessage();
$statusCode = $exception->getCode();
$routeParams = $request->getRouteParams();
@@ -240,7 +238,7 @@ class ErrorOutput
$logMessageItemList[] = $message;
}
$logMessageItemList[] .= $request->getMethod() . ' ' . $request->getResourcePath();
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
if ($requestBodyString) {
$logMessageItemList[] = "Input data: " . $requestBodyString;
@@ -256,4 +254,9 @@ class ErrorOutput
$this->log->log('debug', $logMessage);
}
private function toPrintTrace(): bool
{
return (bool) $this->config->get('logger.printTrace');
}
}

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Api;
use Psr\Http\Message\UriInterface;
use StdClass;
use stdClass;
/**
* Representation of an HTTP request.
@@ -46,7 +46,7 @@ interface Request
/**
* Get a query parameter.
*
* @return ?string|array
* @return string|array|null
*/
public function getQueryParam(string $name);
@@ -110,7 +110,7 @@ interface Request
/**
* Get a parsed body. If JSON array is passed, then will be converted to `{"list": ARRAY}`.
*/
public function getParsedBody(): StdClass;
public function getParsedBody(): stdClass;
/**
* Get a cookie param value.

View File

@@ -35,7 +35,7 @@ use Psr\Http\Message\UriInterface;
use Slim\Psr7\Factory\UriFactory;
use StdClass;
use stdClass;
/**
* An empty stub for Request.
@@ -48,7 +48,7 @@ class RequestNull implements ApiRequest
}
/**
* @return ?string|array
* @return string|array|null
*/
public function getQueryParam(string $name)
{
@@ -113,7 +113,7 @@ class RequestNull implements ApiRequest
return null;
}
public function getParsedBody(): StdClass
public function getParsedBody(): stdClass
{
return (object) [];
}

View File

@@ -36,7 +36,7 @@ use Psr\Http\Message\{
use Espo\Core\Api\Request as ApiRequest;
use StdClass;
use stdClass;
/**
* Adapter for PSR-7 request interface.
@@ -103,7 +103,7 @@ class RequestWrapper implements ApiRequest
}
/**
* @return ?string|array
* @return string|array|null
*/
public function getQueryParam(string $name)
{
@@ -175,7 +175,7 @@ class RequestWrapper implements ApiRequest
return $contents;
}
public function getParsedBody(): StdClass
public function getParsedBody(): stdClass
{
if ($this->parsedBody === null) {
$this->initParsedBody();

View File

@@ -67,8 +67,8 @@ class Application
/**
* Run an application runner.
*
* @param $className A runner class name.
* @param $params Runner parameters.
* @param string $className A runner class name.
* @param ?RunnerParams $params Runner parameters.
*/
public function run(string $className, ?RunnerParams $params = null): void
{

View File

@@ -77,7 +77,7 @@ class Daemon implements Runner
$processList = [];
while (true) {
while (true) { /** @phpstan-ignore-line */
$toSkip = false;
$runningCount = 0;

View File

@@ -29,6 +29,7 @@
namespace Espo\Core;
use Espo\Core\Exceptions\Error;
use Espo\Entities\Portal as PortalEntity;
use Espo\Entities\User as UserEntity;

View File

@@ -48,11 +48,12 @@ class EspoManager implements Manager
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(AuthTokenEntity::ENTITY_TYPE);
$this->repository = $entityManager->getRDBRepository(AuthTokenEntity::ENTITY_TYPE);
}
public function get(string $token): ?AuthToken
{
/** @var ?AuthTokenEntity */
$authToken = $this->entityManager
->getRDBRepository(AuthTokenEntity::ENTITY_TYPE)
->select([
@@ -77,6 +78,7 @@ class EspoManager implements Manager
public function create(Data $data): AuthToken
{
/** @var ?AuthTokenEntity */
$authToken = $this->repository->getNew();
$authToken->set([
@@ -101,6 +103,8 @@ class EspoManager implements Manager
public function inactivate(AuthToken $authToken): void
{
assert($authToken instanceof AuthTokenEntity);
$this->validateNotChanged($authToken);
$authToken->set('isActive', false);
@@ -110,6 +114,8 @@ class EspoManager implements Manager
public function renew(AuthToken $authToken): void
{
assert($authToken instanceof AuthTokenEntity);
$this->validateNotChanged($authToken);
if ($authToken->isNew()) {
@@ -132,7 +138,7 @@ class EspoManager implements Manager
}
}
protected function validateNotChanged(AuthToken $authToken): void
protected function validateNotChanged(AuthTokenEntity $authToken): void
{
if (
$authToken->isAttributeChanged('token') ||

View File

@@ -80,12 +80,17 @@ class Authentication
private $configDataProvider;
/**
* @var EntityManagerProxy
*/
private $entityManager;
private $loginFactory;
private $twoFactorLoginFactory;
private $authTokenManager;
private $hookManager;
private $log;
@@ -284,11 +289,17 @@ class Authentication
$this->authTokenManager->renew($authToken);
}
$authTokenId = null;
if (property_exists($authToken, 'id')) {
$authTokenId = $authToken->id ?? null;
}
$user->set('token', $authToken->getToken());
$user->set('authTokenId', $authToken->id ?? null);
$user->set('authTokenId', $authTokenId);
if ($authLogRecord) {
$authLogRecord->set('authTokenId', $authToken->id ?? null);
$authLogRecord->set('authTokenId', $authTokenId);
}
}
@@ -344,6 +355,7 @@ class Authentication
private function processAuthTokenCheck(AuthToken $authToken): bool
{
if ($this->allowAnyAccess && $authToken->getPortalId() && !$this->isPortal()) {
/** @var ?Portal */
$portal = $this->entityManager->getEntity('Portal', $authToken->getPortalId());
if ($portal) {
@@ -408,7 +420,7 @@ class Authentication
if ($this->isPortal()) {
$isPortalRelatedToUser = $this->entityManager
->getRepository('Portal')
->getRDBRepository('Portal')
->isRelated($this->getPortal(), 'users', $user);
if (!$isPortalRelatedToUser) {
@@ -548,6 +560,7 @@ class Authentication
return null;
}
/** @var ?AuthLogRecord */
$authLogRecord = $this->entityManager->getEntity('AuthLogRecord');
$requestUrl =
@@ -650,6 +663,7 @@ class Authentication
private function getUserDataRepository(): UserDataRepository
{
/** @var UserDataRepository */
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}
}

View File

@@ -44,6 +44,7 @@ class UserFinder
public function find(string $username, string $hash): ?User
{
/** @var ?User */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
->where([
@@ -58,6 +59,7 @@ class UserFinder
public function findApiHmac(string $apiKey): ?User
{
/** @var ?User */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
->where([
@@ -72,6 +74,7 @@ class UserFinder
public function findApiApiKey(string $apiKey): ?User
{
/** @var ?User */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
->where([

View File

@@ -31,6 +31,8 @@ namespace Espo\Core\Authentication\Hook;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\ServiceUnavailable;
/**
* Before logging in, before credentials are checked.

View File

@@ -0,0 +1,38 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\LDAP;
class ClientFactory
{
public function create(array $options): Client
{
return new Client($options);
}
}

View File

@@ -29,6 +29,14 @@
namespace Espo\Core\Authentication\Logins;
use Espo\Core\FieldProcessing\Relation\LinkMultipleSaver;
use Espo\Core\FieldProcessing\EmailAddress\Saver as EmailAddressSaver;
use Espo\Core\FieldProcessing\PhoneNumber\Saver as PhoneNumberSaver;
use Espo\Core\FieldProcessing\Saver\Params as SaverParams;
use Espo\Entities\User;
use Espo\Core\{
ORM\EntityManager,
Api\Request,
@@ -40,7 +48,8 @@ use Espo\Core\{
Authentication\Login\Data,
Authentication\Result,
Authentication\LDAP\Utils as LDAPUtils,
Authentication\LDAP\Client as LDAPClient,
Authentication\LDAP\Client as Client,
Authentication\LDAP\ClientFactory as ClientFactory,
Authentication\AuthToken\AuthToken,
Authentication\Result\FailReason,
};
@@ -51,9 +60,7 @@ class LDAP implements Login
{
private $utils;
private $ldapClient;
private $baseLogin;
private $client;
private $isPortal;
@@ -67,6 +74,16 @@ class LDAP implements Login
private $log;
private $baseLogin;
private $clientFactory;
private $linkMultipleSaver;
private $emailAddressSaver;
private $phoneNumberSaver;
public function __construct(
Config $config,
EntityManager $entityManager,
@@ -74,6 +91,10 @@ class LDAP implements Login
Language $defaultLanguage,
Log $log,
Espo $baseLogin,
ClientFactory $clientFactyory,
LinkMultipleSaver $linkMultipleSaver,
EmailAddressSaver $emailAddressSaver,
PhoneNumberSaver $phoneNumberSaver,
bool $isPortal = false
) {
$this->config = $config;
@@ -82,6 +103,10 @@ class LDAP implements Login
$this->language = $defaultLanguage;
$this->log = $log;
$this->baseLogin = $baseLogin;
$this->clientFactory = $clientFactyory;
$this->linkMultipleSaver = $linkMultipleSaver;
$this->emailAddressSaver = $emailAddressSaver;
$this->phoneNumberSaver = $phoneNumberSaver;
$this->isPortal = $isPortal;
@@ -160,6 +185,8 @@ class LDAP implements Login
$this->log->info('LDAP: Administrator [' . $username . '] was logged in by Espo method.');
}
$userDn = null;
if (!isset($adminUser)) {
try {
$userDn = $this->findLdapUserDnByUsername($username);
@@ -227,26 +254,26 @@ class LDAP implements Login
return Result::success($user);
}
private function getLdapClient()
private function getLdapClient(): Client
{
if (!isset($this->ldapClient)) {
if (!isset($this->client)) {
$options = $this->utils->getLdapClientOptions();
try {
$this->ldapClient = new LDAPClient($options);
$this->client = $this->clientFactory->create($options);
}
catch (Exception $e) {
$this->log->error('LDAP error: ' . $e->getMessage());
}
}
return $this->ldapClient;
return $this->client;
}
/**
* Login by authorization token.
*/
private function loginByToken($username, AuthToken $authToken = null)
private function loginByToken($username, AuthToken $authToken = null): ?User
{
if (!isset($authToken)) {
return null;
@@ -256,6 +283,10 @@ class LDAP implements Login
$user = $this->entityManager->getEntity('User', $userId);
if (!$user) {
return null;
}
$tokenUsername = $user->get('userName');
if (strtolower($username) != strtolower($tokenUsername)) {
@@ -268,6 +299,7 @@ class LDAP implements Login
return null;
}
/** @var ?User */
return $this->entityManager
->getRDBRepository('User')
->where([
@@ -333,9 +365,23 @@ class LDAP implements Login
$this->entityManager->saveEntity($user, [
// Prevent `user` service being loaded by hooks.
'skipHooks' => true,
'keepNew' => true,
]);
return $this->entityManager->getEntity('User', $user->id);
$saverParams = SaverParams::create()
->withRawOptions([
'skipLinkMultipleHooks' => true,
]);
$this->linkMultipleSaver->process($user, 'teams', $saverParams);
$this->linkMultipleSaver->process($user, 'portalRoles', $saverParams);
$this->emailAddressSaver->process($user, $saverParams);
$this->phoneNumberSaver->process($user, $saverParams);
$user->setAsNotNew();
$user->updateFetchedValues();
return $this->entityManager->getEntity('User', $user->getId());
}
/**
@@ -358,7 +404,7 @@ class LDAP implements Login
'(' . $options['userNameAttribute'] . '=' . $username . ')' .
$loginFilterString . ')';
$result = $ldapClient->search($searchString, null, LDAPClient::SEARCH_SCOPE_SUB);
$result = $ldapClient->search($searchString, null, Client::SEARCH_SCOPE_SUB);
$this->log->debug('LDAP: user search string: "' . $searchString . '"');

View File

@@ -81,11 +81,6 @@ class Data
return $this->loggedUser;
}
public function getStatus(): string
{
return $this->status;
}
public function getView(): ?string
{
return $this->view;

View File

@@ -45,6 +45,9 @@ use Espo\Core\Api\Request;
class EmailLogin implements Login
{
/**
* @var EntityManager
*/
private $entityManager;
private $util;
@@ -100,6 +103,7 @@ class EmailLogin implements Login
private function getUserDataRepository(): UserDataRepository
{
/** @var UserDataRepository */
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}
}

View File

@@ -78,6 +78,9 @@ class Util
*/
private const CODE_LIMIT_PERIOD = '10 minutes';
/**
* @var EntityManager
*/
private $entityManager;
private $config;
@@ -183,6 +186,7 @@ class Util
private function findCodeEntity(User $user): ?TwoFactorCode
{
/** @var ?TwoFactorCode */
return $this->entityManager
->getRDBRepository(TwoFactorCode::ENTITY_TYPE)
->where([
@@ -323,6 +327,7 @@ class Util
private function getUserDataRepository(): UserDataRepository
{
/** @var UserDataRepository */
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}

View File

@@ -44,6 +44,9 @@ use Espo\Core\Api\Request;
class SmsLogin implements Login
{
/**
* @var EntityManager
*/
private $entityManager;
private $util;
@@ -99,6 +102,7 @@ class SmsLogin implements Login
private function getUserDataRepository(): UserDataRepository
{
/** @var UserDataRepository */
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}
}

View File

@@ -77,6 +77,9 @@ class Util
*/
private const CODE_LIMIT_PERIOD = '20 minutes';
/**
* @var EntityManager
*/
private $entityManager;
private $config;
@@ -178,6 +181,7 @@ class Util
private function findCodeEntity(User $user): ?TwoFactorCode
{
/** @var ?TwoFactorCode */
return $this->entityManager
->getRDBRepository(TwoFactorCode::ENTITY_TYPE)
->where([
@@ -312,6 +316,7 @@ class Util
private function getUserDataRepository(): UserDataRepository
{
/** @var UserDataRepository */
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}

Some files were not shown because too many files have changed in this diff Show More