Compare commits

...

339 Commits
7.4.2 ... 7.5.2

Author SHA1 Message Date
Yuri Kuznetsov
1f04ba5083 7.5.2 2023-06-19 19:15:40 +03:00
Yuri Kuznetsov
1ad265611f fix entity manager 2023-06-19 17:39:53 +03:00
Yuri Kuznetsov
04906c5307 fix attachment access in posts to all users 2023-06-16 10:49:20 +03:00
Yuri Kuznetsov
fefadc58bd 7.5.1 2023-06-13 17:31:13 +03:00
Yuri Kuznetsov
1628d3b566 fix update 2023-06-13 17:22:32 +03:00
Yuri Kuznetsov
b456d86b22 ref 2023-06-13 17:19:38 +03:00
Yuri Kuznetsov
c3a1236ca9 fix email assignment notificator 2023-06-13 17:19:15 +03:00
Yuri Kuznetsov
d76a6fa0ec lang 2023-06-08 20:08:21 +03:00
Kyle Mathers
b008270fe6 Update Global.json (#2763)
Fix grammar in error messages
2023-06-08 08:28:21 +03:00
Yuri Kuznetsov
2deb826266 fix array search custom options not stored 2023-06-05 22:54:37 +03:00
Yuri Kuznetsov
66815e3192 de 2023-06-05 09:10:16 +03:00
Yuri Kuznetsov
9e34de69f0 fix enum position 2023-06-04 15:00:31 +03:00
Yuri Kuznetsov
6346fae044 fix array 2023-06-01 13:32:25 +03:00
Yuri Kuznetsov
5ab64077e8 bg 2023-06-01 09:46:47 +03:00
Yuri Kuznetsov
ae8221e208 v 2023-06-01 09:23:07 +03:00
Yuri Kuznetsov
84062f331f fix parent field loader 2023-05-29 13:13:50 +03:00
Yuri Kuznetsov
0a1cd5cb74 schema fixes 2023-05-29 11:32:32 +03:00
Yuri Kuznetsov
f63b7e04d6 fixes 2023-05-29 11:32:32 +03:00
Yuri Kuznetsov
c287137283 grunt do not clean custom modules 2023-05-29 11:32:32 +03:00
Yuri Kuznetsov
d3d8a5dc25 fix docs 2023-05-29 11:32:32 +03:00
Yuri Kuznetsov
bf5468d4f4 Update README.md 2023-05-28 18:10:41 +03:00
Yuri Kuznetsov
7b93cf028a schema impr 2023-05-28 09:07:43 +03:00
Yuri Kuznetsov
3012008b85 cleanup 2023-05-28 08:54:16 +03:00
Yuri Kuznetsov
145312af2c fix 2023-05-27 15:44:06 +03:00
Yuri Kuznetsov
0414ee6aa0 vscode json schema mappings 2023-05-27 15:36:23 +03:00
Yuri Kuznetsov
bf716b7953 fix schema 2023-05-27 15:34:32 +03:00
Yuri Kuznetsov
d4657ff85d fix 2023-05-27 14:14:04 +03:00
Yuri Kuznetsov
5023a58cc1 schema 2023-05-27 13:59:52 +03:00
Yuri Kuznetsov
9ea75f1abf metadata app orm change 2023-05-27 12:14:21 +03:00
Yuri Kuznetsov
50a91836e8 schema 2023-05-27 11:44:41 +03:00
Yuri Kuznetsov
12d05f7a6b schema 2023-05-26 14:47:25 +03:00
Yuri Kuznetsov
bff4002cad schema 2023-05-26 13:39:29 +03:00
Yuri Kuznetsov
29388a8a8d add complex expressions 2023-05-26 11:44:09 +03:00
Yuri Kuznetsov
7f29a66ad3 Merge branch 'fix' 2023-05-26 10:12:18 +03:00
Yuri Kuznetsov
e7067447b6 7.4.6 2023-05-26 09:44:35 +03:00
Yuri Kuznetsov
b807f32669 ref 2023-05-25 22:40:15 +03:00
Yuri Kuznetsov
db596c886e fix 2023-05-25 17:21:21 +03:00
Yuri Kuznetsov
0e37635373 schema 2023-05-25 17:18:55 +03:00
Yuri Kuznetsov
58ac7de123 fix comment 2023-05-25 13:57:58 +03:00
Yuri Kuznetsov
ab8421dbae ref 2023-05-25 12:28:42 +03:00
Yuri Kuznetsov
915b8f288b fix duration 2023-05-25 10:12:00 +03:00
Andrew Fontana
52b7a59085 Add title to list-tree-item to preview name (#2750)
When a folder name is too long it will be almost impossible to read it, with this addition it will be possible to hover the mouse on the folder name to view it fully.
2023-05-25 09:41:47 +03:00
Yuri Kuznetsov
e75119645b fix portal edit acl frontend check 2023-05-24 17:07:49 +03:00
Yuri Kuznetsov
a39cd8f114 Merge branch 'fix' 2023-05-24 16:35:01 +03:00
Yuri Kuznetsov
4e8f771ef7 fix has children search 2023-05-24 16:34:20 +03:00
Yuri Kuznetsov
e0ce18407d fix has children search 2023-05-24 16:31:07 +03:00
Yuri Kuznetsov
fcdcf78629 orm: fix string with colom 2023-05-24 12:16:15 +03:00
Andrew Fontana
821e346e7d edit-attributes.js fix missing translation label (#2748)
fix missing translation label for cancel button
2023-05-23 16:18:00 +03:00
Yuri Kuznetsov
0276fcf5ce orm fix select item array with 1 element 2023-05-23 14:24:43 +03:00
Yuri Kuznetsov
7c729aa23f fix 2023-05-23 14:02:23 +03:00
Yuri Kuznetsov
14d08204f1 fix 2023-05-23 14:01:32 +03:00
Yuri Kuznetsov
2747fe2716 fix test 2023-05-21 11:31:02 +03:00
Yuri Kuznetsov
ff6957f577 binding builtin check commented 2023-05-21 11:10:01 +03:00
Yuri Kuznetsov
39c0da248b cs 2023-05-21 10:05:48 +03:00
Yuri Kuznetsov
4a29c14051 primary filter name parameter 2023-05-21 10:02:19 +03:00
Yuri Kuznetsov
4f8dcf0447 Update README.md 2023-05-19 13:43:51 +03:00
Yuri Kuznetsov
081998c79f Update README.md 2023-05-19 10:34:24 +03:00
Yuri Kuznetsov
23f0f0bc6d Update README.md 2023-05-19 10:28:06 +03:00
Yuri Kuznetsov
4f7f40a245 Update README.md 2023-05-19 10:16:43 +03:00
Yuri Kuznetsov
624be85604 Update README.md 2023-05-19 10:12:06 +03:00
Yuri Kuznetsov
452eca267d Update README.md 2023-05-19 09:34:43 +03:00
Yuri Kuznetsov
0711781a1e Update README.md 2023-05-19 09:33:18 +03:00
Yuri Kuznetsov
65d3c25a9e Update README.md 2023-05-19 09:20:18 +03:00
Yuri Kuznetsov
3a94e1fb98 Update README.md 2023-05-19 09:19:22 +03:00
Yuri Kuznetsov
c621b84a4c Update README.md 2023-05-19 09:14:27 +03:00
Yuri Kuznetsov
df69375644 Merge branch 'fix' 2023-05-14 13:40:33 +03:00
Yuri Kuznetsov
53ecb36705 fix cleanup 2023-05-14 13:40:17 +03:00
Yuri Kuznetsov
2c5ba1b826 Update bug_report.md 2023-05-14 13:18:05 +03:00
Yuri Kuznetsov
90ddfe3b43 Update config.yml 2023-05-14 13:09:59 +03:00
Yuri Kuznetsov
a58d4be849 style fix 2023-05-13 15:07:00 +03:00
Yuri Kuznetsov
6f7b5e9973 cleanup 2023-05-13 15:01:57 +03:00
Yuri Kuznetsov
a727cee3a6 login 2nd step focus 2023-05-13 15:00:29 +03:00
Yuri Kuznetsov
562da819a6 fix typo 2023-05-13 14:57:24 +03:00
Yuri Kuznetsov
7cc980d525 Update bug_report.md 2023-05-12 22:27:48 +03:00
Yuri Kuznetsov
4af973015f Update bug_report.md 2023-05-12 22:26:11 +03:00
Yuri Kuznetsov
210fac225d Update config.yml 2023-05-12 22:16:05 +03:00
Yuri Kuznetsov
875377e0d2 Update bug_report.md 2023-05-12 22:10:10 +03:00
Yuri Kuznetsov
eacd62f7fb Merge branch 'fix' 2023-05-12 10:02:07 +03:00
Yuri Kuznetsov
4faaa2d082 v 2023-05-11 13:28:01 +03:00
Yuri Kuznetsov
8dda1321b5 event fix patch upgrade 2023-05-11 13:12:57 +03:00
Yuri Kuznetsov
bb59cda131 event upgrade fix 2023-05-11 12:59:41 +03:00
Yuri Kuznetsov
ed12c6f6bd cs 2023-05-10 11:54:26 +03:00
Yuri Kuznetsov
324ded28de cs 2023-05-10 10:52:34 +03:00
David
1262fdbf29 fixed group by in count (#2734)
Co-authored-by: David Moškoř <david.moskor@apertia.cz>
2023-05-10 09:13:26 +03:00
Yuri Kuznetsov
901c0ba24e use forceDisplayTopBar 2023-05-09 14:35:07 +03:00
Yuri Kuznetsov
8213f704ce fix 2023-05-09 14:20:38 +03:00
Yuri Kuznetsov
17af6bf515 Merge branch 'fix' 2023-05-09 13:28:35 +03:00
Yuri Kuznetsov
0d9f1a225f v 2023-05-09 13:14:44 +03:00
Yuri Kuznetsov
8518a44935 document dnd fix 2023-05-09 13:03:17 +03:00
Yuri Kuznetsov
458fb47f43 list button contanier placeholder 2023-05-08 18:20:34 +03:00
Yuri Kuznetsov
f4db21af2c cs 2023-05-08 14:39:22 +03:00
Yuri Kuznetsov
9d426f7feb css fix 2023-05-08 14:37:03 +03:00
Yuri Kuznetsov
52ae61b1a5 portal detail view tabs 2023-05-08 13:52:09 +03:00
Yuri Kuznetsov
c3c76fab71 dashboard field style fix 2023-05-08 12:36:05 +03:00
Yuri Kuznetsov
61db883330 kanban stick top use offset 2023-05-08 10:04:06 +03:00
Yuri Kuznetsov
447c236953 import no duplicare 2023-05-08 09:30:40 +03:00
Yuri Kuznetsov
8bbfb743f0 fix 2023-05-08 09:29:00 +03:00
Yuri Kuznetsov
3e18f488c6 cs 2023-05-07 19:40:51 +03:00
Yuri Kuznetsov
ad6d001e9f ref 2023-05-07 19:22:08 +03:00
Yuri Kuznetsov
faa11d603e cs 2023-05-07 19:18:28 +03:00
Yuri Kuznetsov
ae2eff925b fix 2023-05-07 18:49:12 +03:00
Yuri Kuznetsov
f180a8ff84 Update feature_request.md 2023-05-06 19:08:07 +03:00
Yuri Kuznetsov
e6ad56d6d0 quick create message 2023-05-06 09:46:07 +03:00
Yuri Kuznetsov
37f1948f9f ref 2023-05-06 09:35:59 +03:00
Yuri Kuznetsov
1adea79d23 notify fixes 2023-05-06 09:32:39 +03:00
Yuri Kuznetsov
dd640c2fbe ref 2023-05-06 09:32:22 +03:00
Yuri Kuznetsov
6c49e93cbf add field quick search 2023-05-05 15:07:16 +03:00
Yuri Kuznetsov
247858d295 ref 2023-05-05 15:02:32 +03:00
Yuri Kuznetsov
797679ce7a fix js docs 2023-05-05 14:53:03 +03:00
Yuri Kuznetsov
ae17acce20 disable inline edit disabled for autoincrement 2023-05-05 14:33:59 +03:00
Yuri Kuznetsov
5974098482 created at indexes for template entities 2023-05-05 14:06:37 +03:00
Yuri Kuznetsov
0ae0a7e1f9 fix label 2023-05-04 17:06:07 +03:00
Yuri Kuznetsov
fb1e4acd60 remove default empty string 2023-05-04 16:51:20 +03:00
Yuri Kuznetsov
e6407bf292 cleanup 2023-05-04 16:48:55 +03:00
Yuri Kuznetsov
2ee1728776 foreign url multiple 2023-05-04 11:29:28 +03:00
Yuri Kuznetsov
5cab488a81 url multiple export 2023-05-03 19:10:54 +03:00
Yuri Kuznetsov
c623dfac75 docs 2023-05-03 12:23:05 +03:00
Yuri Kuznetsov
3e94e18b73 disable storing text filters param 2023-05-03 11:40:58 +03:00
Yuri Kuznetsov
1d00350fa1 function generate record id 2023-05-03 11:27:18 +03:00
Yuri Kuznetsov
9056ad493b cs 2023-05-02 18:21:28 +03:00
Yuri Kuznetsov
b5a9007619 cs 2023-05-02 18:08:47 +03:00
Yuri Kuznetsov
9221cbc361 cs 2023-05-02 18:08:05 +03:00
Yuri Kuznetsov
9a6cfd4228 cs 2023-05-02 18:06:33 +03:00
Yuri Kuznetsov
81ac053818 record fetch 2023-05-02 16:19:12 +03:00
Yuri Kuznetsov
693bb804b6 cs 2023-05-02 14:56:38 +03:00
Yuri Kuznetsov
6ec70d7792 duplicates use list view 2023-05-02 14:34:56 +03:00
Yuri Kuznetsov
0c6ddb725b docs 2023-05-02 12:38:57 +03:00
Yuri Kuznetsov
fe7689297c dont create select manager 2023-05-02 12:38:13 +03:00
Yuri Kuznetsov
95809454b8 freeEmailProviderDomains custom 2023-05-02 12:34:29 +03:00
Yuri Kuznetsov
9bc932d54f user is busy function 2023-05-02 12:12:51 +03:00
Yuri Kuznetsov
d57d5d1d4a methods 2023-05-02 11:52:59 +03:00
Yuri Kuznetsov
b929aa07ec cs 2023-05-02 10:58:58 +03:00
Yuri Kuznetsov
0c0e118ea2 duplicates use preapre entity for output 2023-05-02 09:57:58 +03:00
Yuri Kuznetsov
d522eab8f5 entity manager params panels 2023-05-01 15:27:03 +03:00
Yuri Kuznetsov
21d5ea9fe6 upgrade fix 2023-05-01 15:26:36 +03:00
Yuri Kuznetsov
9bb1cb3efc fix metadata save custom 2023-05-01 15:25:22 +03:00
Yuri Kuznetsov
c681c3ba54 event status treatment editable 2023-05-01 14:21:36 +03:00
Yuri Kuznetsov
7960eb87cc change 2023-05-01 11:50:55 +03:00
Yuri Kuznetsov
f565f4cf04 revert 2023-05-01 11:34:15 +03:00
Yuri Kuznetsov
5775ddcf6a convert currency change 2023-05-01 11:24:42 +03:00
Yuri Kuznetsov
a220bce2a4 rename 2023-05-01 10:20:01 +03:00
Yuri Kuznetsov
184573946f mainCurrencyFieldList 2023-05-01 10:12:39 +03:00
Yuri Kuznetsov
e0a00f4d41 currency convesion ref 2023-04-30 20:12:03 +03:00
Yuri Kuznetsov
4fe1a035c6 currency disable action 2023-04-30 19:13:49 +03:00
Yuri Kuznetsov
5148f5e341 use customizationOptionsReferenceDisabled 2023-04-30 15:07:13 +03:00
Yuri Kuznetsov
19fa50b6a7 history status list param 2023-04-30 14:55:46 +03:00
Yuri Kuznetsov
19949f4217 template specific entity params 2023-04-30 12:23:20 +03:00
Yuri Kuznetsov
bef906e421 status field locked 2023-04-30 12:07:22 +03:00
Yuri Kuznetsov
0c68ecef37 api error output status reason only specific exceptions 2023-04-30 10:39:05 +03:00
Yuri Kuznetsov
4c5cd11ffb Merge branch 'fix' 2023-04-30 10:07:34 +03:00
Yuri Kuznetsov
b4a5d28f53 duplicate check only name 2023-04-30 10:07:19 +03:00
Yuri Kuznetsov
06ffc8c6f6 fix title 2023-04-29 22:54:51 +03:00
Yuri Kuznetsov
eb32f23d38 ref 2023-04-29 20:32:28 +03:00
Yuri Kuznetsov
5d61b049b5 api-before-save-script 2023-04-29 19:18:02 +03:00
Yuri Kuznetsov
2b667ffa03 cs 2023-04-29 10:55:29 +03:00
Yuri Kuznetsov
fdb24513b5 cs 2023-04-29 10:49:25 +03:00
Yuri Kuznetsov
a822535537 cs 2023-04-28 16:26:07 +03:00
Yuri Kuznetsov
83b60e49c6 ref 2023-04-28 16:19:41 +03:00
Yuri Kuznetsov
86c8fe9a83 ref 2023-04-28 16:08:06 +03:00
Yuri Kuznetsov
d460805c8b ref 2023-04-28 15:57:25 +03:00
Yuri Kuznetsov
a9d73a8501 ref 2023-04-28 15:44:47 +03:00
Yuri Kuznetsov
a3cf76be92 ref 2023-04-28 15:26:06 +03:00
Yuri Kuznetsov
23c9893362 ref 2023-04-28 15:21:19 +03:00
Yuri Kuznetsov
7efbe56763 clnup 2023-04-28 15:20:01 +03:00
Yuri Kuznetsov
9759ab523c ref 2023-04-28 14:28:51 +03:00
Yuri Kuznetsov
21f8ff0168 docs 2023-04-28 13:31:07 +03:00
Yuri Kuznetsov
f865bd9282 docs fix 2023-04-28 13:29:41 +03:00
Yuri Kuznetsov
8e0770b15d entity manager params 2023-04-28 12:58:06 +03:00
Yuri Kuznetsov
0b696f8388 doc fix 2023-04-28 11:07:52 +03:00
Yuri Kuznetsov
6b6c166eca ref 2023-04-28 09:48:07 +03:00
Yuri Kuznetsov
598422a822 cs 2023-04-28 09:38:36 +03:00
Yuri Kuznetsov
15b3a89520 dashboard locked remove 2023-04-27 17:14:38 +03:00
Yuri Kuznetsov
7d06d0f6a4 cs docs 2023-04-27 16:40:15 +03:00
Yuri Kuznetsov
b975799231 layoutDefs usage 2023-04-27 15:59:19 +03:00
Yuri Kuznetsov
96a8194775 update bullbone 2023-04-27 15:58:10 +03:00
Yuri Kuznetsov
600150c363 ref 2023-04-27 15:18:09 +03:00
Yuri Kuznetsov
8a1242c312 Bull extend usage 2023-04-27 14:40:19 +03:00
Yuri Kuznetsov
b44b8721c5 layout edit impr 2023-04-27 13:56:16 +03:00
Yuri Kuznetsov
6149f1a476 no total text 2023-04-27 13:38:43 +03:00
Yuri Kuznetsov
f4eb69c831 dashboard tab name max length 2023-04-27 12:54:28 +03:00
Yuri Kuznetsov
0e94a9708a dashboard locked prefrerences param 2023-04-27 12:45:18 +03:00
Yuri Kuznetsov
97cc94d3d9 clnup 2023-04-27 11:27:16 +03:00
Yuri Kuznetsov
ec848524a6 create related link read only 2023-04-27 11:25:48 +03:00
Yuri Kuznetsov
ec20edde5e cs 2023-04-27 11:12:06 +03:00
Yuri Kuznetsov
d6093e7764 ref 2023-04-27 10:34:38 +03:00
Yuri Kuznetsov
b71bc36061 clnp 2023-04-27 10:21:56 +03:00
Yuri Kuznetsov
359fc94d99 update phpstan 2023-04-27 10:21:30 +03:00
Yuri Kuznetsov
5e59cbea8a type fixes 2023-04-27 10:21:24 +03:00
Yuri Kuznetsov
843a7c1f1e Merge branch 'fix' 2023-04-27 09:58:03 +03:00
Yuri Kuznetsov
aa5427d976 fix 2023-04-27 09:43:09 +03:00
Yuri Kuznetsov
04218e0291 fix 2023-04-27 09:43:02 +03:00
Yuri Kuznetsov
9a7c4da780 fix 2023-04-26 22:46:27 +03:00
Yuri Kuznetsov
da777f2e40 cs 2023-04-26 22:41:39 +03:00
Yuri Kuznetsov
7fc92a6a03 ref 2023-04-26 22:38:25 +03:00
Yuri Kuznetsov
f93958ffea ref 2023-04-26 22:11:18 +03:00
Yuri Kuznetsov
1f71856d59 test fix 2023-04-26 18:28:24 +03:00
Yuri Kuznetsov
097f5141d3 ref 2023-04-26 18:14:45 +03:00
Yuri Kuznetsov
9547c45109 email import ref, use locking 2023-04-26 18:12:48 +03:00
Yuri Kuznetsov
753158ae7a link multiple with id list 2023-04-26 17:56:55 +03:00
Yuri Kuznetsov
f50752df6f improve text search autocomplete 2023-04-26 16:12:18 +03:00
Yuri Kuznetsov
1525401105 style fix 2023-04-26 14:49:44 +03:00
Yuri Kuznetsov
d62d4265a8 url-multiple field 2023-04-26 14:25:56 +03:00
Yuri Kuznetsov
4aae685c6e ref 2023-04-26 13:20:37 +03:00
Yuri Kuznetsov
18cd794fe1 ref 2023-04-26 12:25:57 +03:00
Yuri Kuznetsov
b20a4e768e Merge branch 'fix' 2023-04-26 11:59:42 +03:00
Yuri Kuznetsov
c7c6cfe27f fix dompdf custom page size 2023-04-26 11:57:44 +03:00
Yuri Kuznetsov
87077c7644 ref 2023-04-26 11:34:48 +03:00
Yuri Kuznetsov
fd02e83cea css fix 2023-04-26 11:29:37 +03:00
Yuri Kuznetsov
8da8285c56 css fix 2023-04-25 17:44:13 +03:00
Yuri Kuznetsov
5c7f67a711 cleanup 2023-04-25 17:42:50 +03:00
Yuri Kuznetsov
26634de090 stored text search 2023-04-25 16:44:10 +03:00
Yuri Kuznetsov
1f2c89517e typo 2023-04-25 13:29:39 +03:00
Yuri Kuznetsov
472a4a63d7 add filter quick search 2023-04-25 12:03:23 +03:00
Yuri Kuznetsov
634315f9e4 cs 2023-04-25 10:12:11 +03:00
Yuri Kuznetsov
064ccd31fc fix edit modal header 2023-04-24 15:45:40 +03:00
Yuri Kuznetsov
0c694447d6 event filters 2023-04-24 15:36:32 +03:00
Yuri Kuznetsov
3ae84a0e5b consts 2023-04-24 14:43:06 +03:00
Yuri Kuznetsov
234de9b812 ref 2023-04-24 14:37:23 +03:00
Yuri Kuznetsov
257e3621ea email filter body contains all 2023-04-24 13:56:55 +03:00
Yuri Kuznetsov
65e31f15a2 email filter cache 2023-04-24 13:08:13 +03:00
Yuri Kuznetsov
982201a1fa ref 2023-04-24 12:22:02 +03:00
Yuri Kuznetsov
bb9c2f5e92 image radius 2023-04-24 11:46:28 +03:00
Yuri Kuznetsov
9dc04b2357 Merge branch 'fix' 2023-04-24 11:43:11 +03:00
Yuri Kuznetsov
98d457dae6 fix list view image height 2023-04-24 11:42:59 +03:00
Yuri Kuznetsov
d0f7798d55 avatar radius 2023-04-24 11:39:56 +03:00
Yuri Kuznetsov
5ee7b4ffad style fix 2023-04-24 11:24:14 +03:00
Yuri Kuznetsov
7b15b13e8b email validations 2023-04-24 11:16:11 +03:00
Yuri Kuznetsov
2b10be3464 ref 2023-04-24 10:21:57 +03:00
Yuri Kuznetsov
4911d7caa1 cs 2023-04-24 10:08:31 +03:00
Yuri Kuznetsov
a23886143a ref 2023-04-24 09:49:11 +03:00
Yuri Kuznetsov
16e0ee4576 cs 2023-04-23 09:45:33 +03:00
Yuri Kuznetsov
33dccd4ef7 cs 2023-04-22 21:27:57 +03:00
Yuri Kuznetsov
7d03ae498b ref 2023-04-22 21:20:58 +03:00
Yuri Kuznetsov
fda1dedbe4 ref 2023-04-22 21:09:37 +03:00
Yuri Kuznetsov
07c2a2e49b cs 2023-04-22 21:08:18 +03:00
Yuri Kuznetsov
4964056343 ref 2023-04-22 20:49:54 +03:00
Yuri Kuznetsov
98e15c8f87 cs 2023-04-22 15:43:10 +03:00
Yuri Kuznetsov
6c602610d4 ref 2023-04-22 15:40:34 +03:00
Yuri Kuznetsov
08efd3f979 style fix 2023-04-21 17:56:16 +03:00
Yuri Kuznetsov
49a9cf9248 ref 2023-04-21 17:54:16 +03:00
Yuri Kuznetsov
d56877c591 ref 2023-04-21 17:47:07 +03:00
Yuri Kuznetsov
6fe123b3f9 ref 2023-04-21 17:47:04 +03:00
Yuri Kuznetsov
427d2dcf52 ref 2023-04-21 17:24:54 +03:00
Yuri Kuznetsov
755f3de523 fix 2023-04-21 14:34:50 +03:00
Yuri Kuznetsov
fc72d847da print email 2023-04-21 14:14:00 +03:00
Yuri Kuznetsov
6a963f39d4 username unique index 2023-04-21 13:08:56 +03:00
Yuri Kuznetsov
5161fbb1d4 save error handling fix 2023-04-21 12:37:17 +03:00
Yuri Kuznetsov
fe32d8d47b job no table locking param 2023-04-21 11:13:09 +03:00
Yuri Kuznetsov
fffa608816 note update empty 2023-04-21 10:50:39 +03:00
Yuri Kuznetsov
fb79de7903 Merge branch 'fix' 2023-04-21 10:34:31 +03:00
Yuri Kuznetsov
007cf8c2aa action button race condition fix 2023-04-21 10:34:08 +03:00
Yuri Kuznetsov
67c86c9521 clnup 2023-04-20 19:50:01 +03:00
Yuri Kuznetsov
780d83f049 ref 2023-04-20 19:48:34 +03:00
Yuri Kuznetsov
1e420ed6f7 comment 2023-04-20 19:31:52 +03:00
Yuri Kuznetsov
e5a382b2ec ref 2023-04-20 19:26:29 +03:00
Yuri Kuznetsov
b326142952 cs 2023-04-20 19:24:25 +03:00
Yuri Kuznetsov
6870c47132 ref 2023-04-20 19:11:27 +03:00
Yuri Kuznetsov
5251fcd4ec ref 2023-04-20 18:32:24 +03:00
Yuri Kuznetsov
f9c6147396 ref 2023-04-20 18:03:30 +03:00
Yuri Kuznetsov
937b2adf57 ref 2023-04-20 17:36:41 +03:00
Yuri Kuznetsov
b4e4c875b9 ref 2023-04-20 17:18:53 +03:00
Yuri Kuznetsov
1d52afb45a ip address util 2023-04-20 16:42:26 +03:00
Yuri Kuznetsov
37165fc735 job ref 2023-04-20 16:11:43 +03:00
Yuri Kuznetsov
bed891289f ref 2023-04-20 15:45:54 +03:00
Yuri Kuznetsov
3a007518e5 ref 2023-04-20 15:00:58 +03:00
Yuri Kuznetsov
c2b856c3da cs 2023-04-20 14:40:02 +03:00
Yuri Kuznetsov
5f944554cf ip address forwarded 2023-04-20 13:56:10 +03:00
dependabot[bot]
db34791130 Bump guzzlehttp/psr7 from 1.8.4 to 1.9.1 (#2704)
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 1.8.4 to 1.9.1.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/1.9.1/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/1.8.4...1.9.1)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-20 09:20:15 +03:00
Yuri Kuznetsov
8b73912c3d revert 2023-04-19 12:17:23 +03:00
Yuri Kuznetsov
1ac2f489ab person-name use coalesce 2023-04-19 12:08:33 +03:00
Yuri Kuznetsov
4309d79c56 ref 2023-04-19 09:28:20 +03:00
Yuri Kuznetsov
a802f740c5 docs 2023-04-19 09:28:20 +03:00
dependabot[bot]
9eb4fd7cf9 Bump slim/psr7 from 1.4 to 1.6.1 (#2700)
Bumps [slim/psr7](https://github.com/slimphp/Slim-Psr7) from 1.4 to 1.6.1.
- [Release notes](https://github.com/slimphp/Slim-Psr7/releases)
- [Commits](https://github.com/slimphp/Slim-Psr7/compare/1.4...1.6.1)

---
updated-dependencies:
- dependency-name: slim/psr7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-19 09:27:44 +03:00
Yuri Kuznetsov
4dd5784838 enum title 2023-04-18 19:34:29 +03:00
Yuri Kuznetsov
87911fc3b6 ref cs 2023-04-18 19:23:01 +03:00
Yuri Kuznetsov
d2a1737a9e array style fix 2023-04-18 18:39:59 +03:00
Yuri Kuznetsov
352eefdb29 complex-text-memo 2023-04-18 16:25:26 +03:00
Yuri Kuznetsov
e3a2042990 memo dashlet 2023-04-18 16:11:26 +03:00
Yuri Kuznetsov
a492a3bbb6 doc 2023-04-18 16:06:19 +03:00
Yuri Kuznetsov
fac84830d3 Update config.yml 2023-04-18 14:25:47 +03:00
Yuri Kuznetsov
3d743d306b copy to clipboard varchar/url/number 2023-04-18 13:44:19 +03:00
Yuri Kuznetsov
6de9e82709 Merge branch 'fix' 2023-04-18 12:45:24 +03:00
Yuri Kuznetsov
773f929b01 fix language cleanup 2023-04-18 12:45:15 +03:00
Yuri Kuznetsov
3a6a185548 admin focus on search 2023-04-18 12:37:04 +03:00
Yuri Kuznetsov
32d1863411 doc 2023-04-18 12:28:24 +03:00
Yuri Kuznetsov
23dd15e979 Merge branch 'fix' 2023-04-18 12:20:28 +03:00
Yuri Kuznetsov
f3da3e0fb3 fix 2023-04-18 12:01:50 +03:00
Yuri Kuznetsov
396d74df63 side bar wider 2023-04-18 11:52:35 +03:00
Yuri Kuznetsov
7293893fc7 email folders sticked wide screen fix 2023-04-18 11:25:58 +03:00
Yuri Kuznetsov
9b796e7832 list border radius on wide screen 2023-04-18 11:08:19 +03:00
Yuri Kuznetsov
a8dab3a3b1 Merge branch 'fix' 2023-04-18 10:33:30 +03:00
Yuri Kuznetsov
e432e14a95 ref 2023-04-17 16:56:01 +03:00
Yuri Kuznetsov
ee865662b2 cs 2023-04-17 16:51:48 +03:00
Yuri Kuznetsov
a24313c789 clnup 2023-04-17 16:46:09 +03:00
Yuri Kuznetsov
02fd5c430f ref 2023-04-17 16:45:50 +03:00
Yuri Kuznetsov
4e0e223766 Update config.yml 2023-04-17 13:38:12 +03:00
Yuri Kuznetsov
2df5017e94 Update feature_request.md 2023-04-17 13:37:50 +03:00
Yuri Kuznetsov
6deb9b4ab7 Update bug_report.md 2023-04-17 13:37:37 +03:00
Yuri Kuznetsov
0a57e538d5 fix layout 2023-04-17 12:20:20 +03:00
Yuri Kuznetsov
e8dae0cfd1 account role title 2023-04-17 12:20:14 +03:00
Yuri Kuznetsov
e9c55bbd43 css fix 2023-04-15 17:22:16 +03:00
Yuri Kuznetsov
7a98325eae fix email stickable 2023-04-15 10:54:58 +03:00
Yuri Kuznetsov
b0a811c51d stream update css fix 2023-04-15 10:25:47 +03:00
Yuri Kuznetsov
da013d8aae Merge branch 'fix' 2023-04-14 17:12:53 +03:00
Yuri Kuznetsov
a1d920f003 email stickable fix 2023-04-14 11:53:16 +03:00
Yuri Kuznetsov
07443e54de createSearchView method 2023-04-14 11:00:57 +03:00
Yuri Kuznetsov
717a678c9a fix show detail 2023-04-13 23:44:32 +03:00
Yuri Kuznetsov
ffaf999587 ref 2023-04-13 21:16:33 +03:00
Yuri Kuznetsov
37fba10ba7 ref 2023-04-13 21:05:34 +03:00
Yuri Kuznetsov
0a9de8cf56 ref 2023-04-13 20:48:06 +03:00
Yuri Kuznetsov
a02be88c4a cleanup 2023-04-13 17:13:21 +03:00
Yuri Kuznetsov
e8dc05ad99 ref 2023-04-13 16:53:33 +03:00
Yuri Kuznetsov
6338a73eaa discard tryReady usage 2023-04-13 14:56:30 +03:00
Yuri Kuznetsov
73533080f6 list bottom radius class 2023-04-13 11:34:04 +03:00
Yuri Kuznetsov
1c7a6d9825 mailto fix 2023-04-13 11:08:12 +03:00
Yuri Kuznetsov
c553bc8d0a linkCheckDisabled 2023-04-12 17:17:27 +03:00
Yuri Kuznetsov
9ff967249b teams users active filter 2023-04-12 13:24:32 +03:00
Yuri Kuznetsov
3011cdf22c cs 2023-04-12 13:21:04 +03:00
Yuri Kuznetsov
4f2ade5f7f css fix 2023-04-12 11:15:45 +03:00
Yuri Kuznetsov
b882aad4ab iframe line height 2023-04-12 11:07:32 +03:00
Yuri Kuznetsov
0d6f98319d Merge branch 'fix' 2023-04-11 18:31:45 +03:00
Yuri Kuznetsov
8541c12441 dynamic logic reminder 2023-04-11 18:31:35 +03:00
Yuri Kuznetsov
57d9dbc33e fix stream dashlet 2023-04-11 16:23:35 +03:00
Yuri Kuznetsov
81d92870e9 attachmentAvailableStorageList 2023-04-11 16:08:25 +03:00
Yuri Kuznetsov
a86795ea48 forbid system field/link name 2023-04-11 15:54:19 +03:00
Yuri Kuznetsov
cb6a320eb5 fix typo 2023-04-11 15:52:42 +03:00
Yuri Kuznetsov
c9ab718666 cs 2023-04-11 15:51:53 +03:00
Yuri Kuznetsov
46cace5ed2 Merge branch 'fix' 2023-04-11 15:50:44 +03:00
Yuri Kuznetsov
2a967261b3 mass action disable 2023-04-11 14:36:52 +03:00
Yuri Kuznetsov
66ecf7794e options ref 2023-04-11 14:10:38 +03:00
Yuri Kuznetsov
7365007511 aclDependency refactoring 2023-04-11 11:55:42 +03:00
Yuri Kuznetsov
88870ccac8 Merge branch 'version/7.5' 2023-04-11 10:20:52 +03:00
Yuri Kuznetsov
12e40352fe fix jpeg 2023-04-11 10:06:26 +03:00
Yuri Kuznetsov
2b0bde8c0b cs 2023-04-10 15:45:32 +03:00
Yuri Kuznetsov
931e8b934c user view usage 2023-04-10 15:45:16 +03:00
Yuri Kuznetsov
a07cb5ef38 range fields autonumeric 2023-04-10 15:17:06 +03:00
Yuri Kuznetsov
66a6f70a30 v 2023-04-07 22:18:02 +03:00
Yuri Kuznetsov
d7a629c5a3 int/float fix 2023-04-07 22:17:25 +03:00
Yuri Kuznetsov
2010d16d4d ref 2023-03-24 20:35:12 +02:00
1274 changed files with 20106 additions and 9215 deletions

View File

@@ -1,6 +1,6 @@
---
name: Bug report
about: Create a bug report
about: Create a bug report. Not to be used for help requests or server configuration issues. We appreciate if you prefer posting bug reports on weekdays rather than weekends.
title: ''
labels: ''
assignees: ''

View File

@@ -1,4 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: EspoCRM forum
url: https://forum.espocrm.com/
about: Please use our forum to ask questions not related to product development
about: "Use our forum for help requests and questions not related to product development. We don't provide support on GitHub."

View File

@@ -1,6 +1,6 @@
---
name: Feature request
about: Suggest an idea for EspoCRM
about: Freature requests are frozen til mid-June 2023. Please post on the forum instead. (Suggest an idea for EspoCRM).
title: ''
labels: ''
assignees: ''

1
.idea/.gitignore generated vendored
View File

@@ -4,3 +4,4 @@
!/fileTemplates
!/inspectionProfiles
!misc.xml
!jsonSchemas.xml

1351
.idea/jsonSchemas.xml generated Normal file

File diff suppressed because it is too large Load Diff

3
.vscode/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!.gitignore
!settings.json

422
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,422 @@
{
"json.schemas": [
{
"fileMatch": [
"*/Resources/routes.json"
],
"url": "./schema/routes.json"
},
{
"fileMatch": [
"*/Resources/module.json"
],
"url": "./schema/routes.json"
},
{
"fileMatch": [
"*/Resources/layouts/*/detail.json",
"*/Resources/layouts/*/detailSmall.json",
"*/Resources/layouts/*/detailConvert.json"
],
"url": "./schema/layouts/detail.json"
},
{
"fileMatch": [
"*/Resources/layouts/*/list.json",
"*/Resources/layouts/*/listSmall.json",
"*/Resources/layouts/*/listFor*.json"
],
"url": "./schema/layouts/list.json"
},
{
"fileMatch": [
"*/metadata/aclDefs/*.json"
],
"url": "./schema/metadata/aclDefs.json"
},
{
"fileMatch": [
"*/metadata/authenticationMethods/*.json"
],
"url": "./schema/metadata/authenticationMethods.json"
},
{
"fileMatch": [
"*/metadata/clientDefs/*.json"
],
"url": "./schema/metadata/clientDefs.json"
},
{
"fileMatch": [
"*/metadata/dashlets/*.json"
],
"url": "./schema/metadata/dashlets.json"
},
{
"fileMatch": [
"*/metadata//*.json"
],
"url": "./schema/metadata/.json"
},
{
"fileMatch": [
"*/metadata/entityAcl/*.json"
],
"url": "./schema/metadata/entityAcl.json"
},
{
"fileMatch": [
"*/metadata/entityDefs/*.json"
],
"url": "./schema/metadata/entityDefs.json"
},
{
"fileMatch": [
"*/metadata/fields/*.json"
],
"url": "./schema/metadata/fields.json"
},
{
"fileMatch": [
"*/metadata/integrations/*.json"
],
"url": "./schema/metadata/integrations.json"
},
{
"fileMatch": [
"*/metadata/notificationDefs/*.json"
],
"url": "./schema/metadata/notificationDefs.json"
},
{
"fileMatch": [
"*/metadata/pdfDefs/*.json"
],
"url": "./schema/metadata/pdfDefs.json"
},
{
"fileMatch": [
"*/metadata/recordDefs/*.json"
],
"url": "./schema/metadata/recordDefs.json"
},
{
"fileMatch": [
"*/metadata/scopes/*.json"
],
"url": "./schema/metadata/scopes.json"
},
{
"fileMatch": [
"*/metadata/selectDefs/*.json"
],
"url": "./schema/metadata/selectDefs.json"
},
{
"fileMatch": [
"*/metadata/streamDefs/*.json"
],
"url": "./schema/metadata/streamDefs.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/acl.json"
],
"url": "./schema/metadata/app/acl.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/aclPortal.json"
],
"url": "./schema/metadata/app/aclPortal.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/actions.json"
],
"url": "./schema/metadata/app/actions.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/addressFormats.json"
],
"url": "./schema/metadata/app/addressFormats.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/adminPanel.json"
],
"url": "./schema/metadata/app/adminPanel.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/api.json"
],
"url": "./schema/metadata/app/api.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/appParams.json"
],
"url": "./schema/metadata/app/appParams.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/authentication.json"
],
"url": "./schema/metadata/app/authentication.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/authentication2FAMethods.json"
],
"url": "./schema/metadata/app/authentication2FAMethods.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/cleanup.json"
],
"url": "./schema/metadata/app/cleanup.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/client.json"
],
"url": "./schema/metadata/app/client.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/clientRoutes.json"
],
"url": "./schema/metadata/app/clientRoutes.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/complexExpression.json"
],
"url": "./schema/metadata/app/complexExpression.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/config.json"
],
"url": "./schema/metadata/app/config.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/consoleCommands.json"
],
"url": "./schema/metadata/app/consoleCommands.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/containerServices.json"
],
"url": "./schema/metadata/app/containerServices.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/currency.json"
],
"url": "./schema/metadata/app/currency.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/currencyConversion.json"
],
"url": "./schema/metadata/app/currencyConversion.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/databasePlatforms.json"
],
"url": "./schema/metadata/app/databasePlatforms.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/dateTime.json"
],
"url": "./schema/metadata/app/dateTime.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/defaultDashboardLayouts.json"
],
"url": "./schema/metadata/app/defaultDashboardLayouts.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/defaultDashboardOptions.json"
],
"url": "./schema/metadata/app/defaultDashboardOptions.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/entityManagerParams.json"
],
"url": "./schema/metadata/app/entityManagerParams.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/entityTemplateList.json"
],
"url": "./schema/metadata/app/entityTemplateList.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/export.json"
],
"url": "./schema/metadata/app/export.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/fieldProcessing.json"
],
"url": "./schema/metadata/app/fieldProcessing.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/file.json"
],
"url": "./schema/metadata/app/file.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/fileStorage.json"
],
"url": "./schema/metadata/app/fileStorage.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/formula.json"
],
"url": "./schema/metadata/app/formula.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/hook.json"
],
"url": "./schema/metadata/app/hook.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/image.json"
],
"url": "./schema/metadata/app/image.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/jsLibs.json"
],
"url": "./schema/metadata/app/jsLibs.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/language.json"
],
"url": "./schema/metadata/app/language.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/linkManager.json"
],
"url": "./schema/metadata/app/linkManager.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/massActions.json"
],
"url": "./schema/metadata/app/massActions.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/metadata.json"
],
"url": "./schema/metadata/app/metadata.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/orm.json"
],
"url": "./schema/metadata/app/orm.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/pdfEngines.json"
],
"url": "./schema/metadata/app/pdfEngines.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/popupNotifications.json"
],
"url": "./schema/metadata/app/popupNotifications.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/portalContainerServices.json"
],
"url": "./schema/metadata/app/portalContainerServices.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/rebuild.json"
],
"url": "./schema/metadata/app/rebuild.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/recordId.json"
],
"url": "./schema/metadata/app/recordId.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/regExpPatterns.json"
],
"url": "./schema/metadata/app/regExpPatterns.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/relationships.json"
],
"url": "./schema/metadata/app/relationships.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/scheduledJobs.json"
],
"url": "./schema/metadata/app/scheduledJobs.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/select.json"
],
"url": "./schema/metadata/app/select.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/smsProviders.json"
],
"url": "./schema/metadata/app/smsProviders.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/templateHelpers.json"
],
"url": "./schema/metadata/app/templateHelpers.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/templates.json"
],
"url": "./schema/metadata/app/templates.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/webSocket.json"
],
"url": "./schema/metadata/app/webSocket.json"
}
]
}

View File

@@ -90,7 +90,7 @@ module.exports = grunt => {
mkdir: {
tmp: {
options: {
mode: 0755,
mode: 0o755,
create: [
'build/tmp',
],
@@ -110,9 +110,7 @@ module.exports = grunt => {
beforeFinal: {
src: [
'build/tmp/custom/Espo/Custom/*',
'build/tmp/custom/Espo/Modules/*',
'!build/tmp/custom/Espo/Custom/.htaccess',
'!build/tmp/custom/Espo/Modules/.htaccess',
'build/tmp/install/config.php',
'build/tmp/vendor/*/*/.git',
'build/tmp/custom/Espo/Custom/*',
@@ -236,7 +234,7 @@ module.exports = grunt => {
options: {
patterns: [
{
match: /\# \{\#dev\}(.*)\{\/dev\}/gs,
match: /# \{#dev}(.*)\{\/dev}/gs,
replacement: '',
}
]

View File

@@ -27,36 +27,47 @@ For more information about server configuration see [this article](https://docs.
### Documentation
The documentation for administrators, users and developers is available [here](https://docs.espocrm.com).
See the [documentation](https://docs.espocrm.com) for administrators, users and developers.
### Bug reporting
Create an issue [here](https://github.com/espocrm/espocrm/issues) or post on our [forum](https://forum.espocrm.com/forum/bug-reports).
We'd appreciate if you prefer posting issues on weekdays rather than weekends.
Create a [GitHub issue](https://github.com/espocrm/espocrm/issues/new/choose) or post on our [forum](https://forum.espocrm.com/forum/bug-reports).
### Installing the stable version
### Installing stable version
See the [instructions](https://docs.espocrm.com/administration/installation/) on installation.
See installation instructions:
* [Manual installation](https://docs.espocrm.com/administration/installation/)
* [Installation by script](https://docs.espocrm.com/administration/installation-by-script/)
* [Installation with Docker](https://docs.espocrm.com/administration/docker/installation/)
* [Installation with Traefik](https://docs.espocrm.com/administration/docker/traefik/)
### Development
* [Getting started](https://docs.espocrm.com/development/how-to-start)
* [Running tests](https://docs.espocrm.com/development/tests)
* [Making translation](https://docs.espocrm.com/development/translation)
See the [developer documentation](https://docs.espocrm.com/development/).
We highly recommend using IDE for development. The backend codebase follows SOLID principles, utilizes interfaces, static typing and generics. We recommend to start learning EspoCRM from the Dependency Injection article in the documentation.
### Contributing
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
Contribute translations to [POEditor](https://poeditor.com/join/project/gLDKZtUF4i). Changes
are usually merged to the GitHub repository before minor releases.
Branches:
* *fix* upcoming maintenance release; minor fixes should be pushed to this branch;
* *master* develop branch; new features should be pushed to this branch;
* *stable* last stable release.
### Language
If you want to improve existing translation or add a language that is not available yet, you can contribute on our [POEditor](https://poeditor.com/join/project/gLDKZtUF4i) project. See instructions [here](https://www.espocrm.com/blog/how-to-use-poeditor-to-translate-espocrm/).
Changes on POEditor are usually merged to the GitHub repository before minor releases.
### Community & Support
If you have a question regarding some features, need help or customizations, want to get in touch with other EspoCRM users, or add a feature request, please use our [community forum](https://forum.espocrm.com/). We believe that using the forum to ask for help and share experience allows everyone in the community to contribute and use this knowledge later.
### License
EspoCRM is published under the GNU GPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).

View File

@@ -48,17 +48,12 @@ class AccessChecker implements AccessEntityCREDChecker
{
use DefaultAccessCheckerDependency;
private AclManager $aclManager;
private EntityManager $entityManager;
public function __construct(
DefaultAccessChecker $defaultAccessChecker,
AclManager $aclManager,
EntityManager $entityManager
private AclManager $aclManager,
private EntityManager $entityManager
) {
$this->defaultAccessChecker = $defaultAccessChecker;
$this->aclManager = $aclManager;
$this->entityManager = $entityManager;
}
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
@@ -126,8 +121,8 @@ class AccessChecker implements AccessEntityCREDChecker
{
if ($note->getTargetType() === Note::TARGET_TEAMS) {
$intersect = array_intersect(
$note->getLinkMultipleIdList('teams') ?? [],
$user->getLinkMultipleIdList('teams') ?? []
$note->getLinkMultipleIdList('teams'),
$user->getLinkMultipleIdList('teams')
);
if (count($intersect)) {
@@ -150,6 +145,10 @@ class AccessChecker implements AccessEntityCREDChecker
return null;
}
if ($note->getTargetType() === Note::TARGET_ALL) {
return true;
}
if (!$note->getParentId() || !$note->getParentType()) {
return null;
}

View File

@@ -126,7 +126,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($entity->getTargetType() === Note::TARGET_TEAMS) {
$targetTeamIdList = $entity->getLinkMultipleIdList('teams') ?? [];
$targetTeamIdList = $entity->getLinkMultipleIdList('teams');
foreach ($user->getTeamIdList() as $teamId) {
if (in_array($teamId, $targetTeamIdList)) {
@@ -138,7 +138,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($entity->getTargetType() === Note::TARGET_USERS) {
return in_array($user->getId(), $entity->getLinkMultipleIdList('users') ?? []);
return in_array($user->getId(), $entity->getLinkMultipleIdList('users'));
}
return false;

View File

@@ -51,8 +51,8 @@ class OwnershipChecker implements OwnershipOwnChecker, OwnershipTeamChecker
assert($entity instanceof CoreEntity);
$intersect = array_intersect(
$user->getLinkMultipleIdList('teams') ?? [],
$entity->getLinkMultipleIdList('teams') ?? []
$user->getLinkMultipleIdList('teams'),
$entity->getLinkMultipleIdList('teams')
);
if (count($intersect)) {

View File

@@ -131,8 +131,8 @@ class AccessChecker implements AccessEntityCREDChecker
if ($note->getTargetType() === Note::TARGET_PORTALS) {
$intersect = array_intersect(
$note->getLinkMultipleIdList('portals') ?? [],
$user->getLinkMultipleIdList('portals') ?? []
$note->getLinkMultipleIdList('portals'),
$user->getLinkMultipleIdList('portals')
);
if (count($intersect)) {

View File

@@ -118,7 +118,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($entity->getTargetType() === Note::TARGET_PORTALS) {
return in_array($user->getPortalId(), $entity->getLinkMultipleIdList('portals') ?? []);
return in_array($user->getPortalId(), $entity->getLinkMultipleIdList('portals'));
}
return false;

View File

@@ -29,10 +29,8 @@
namespace Espo\Classes\AddressFormatters;
use Espo\Core\{
Field\Address,
Field\Address\AddressFormatter,
};
use Espo\Core\Field\Address;
use Espo\Core\Field\Address\AddressFormatter;
class Formatter1 implements AddressFormatter
{

View File

@@ -29,10 +29,8 @@
namespace Espo\Classes\AddressFormatters;
use Espo\Core\{
Field\Address,
Field\Address\AddressFormatter,
};
use Espo\Core\Field\Address;
use Espo\Core\Field\Address\AddressFormatter;
class Formatter2 implements AddressFormatter
{

View File

@@ -29,10 +29,8 @@
namespace Espo\Classes\AddressFormatters;
use Espo\Core\{
Field\Address,
Field\Address\AddressFormatter,
};
use Espo\Core\Field\Address;
use Espo\Core\Field\Address\AddressFormatter;
class Formatter3 implements AddressFormatter
{

View File

@@ -29,10 +29,8 @@
namespace Espo\Classes\AddressFormatters;
use Espo\Core\{
Field\Address,
Field\Address\AddressFormatter,
};
use Espo\Core\Field\Address;
use Espo\Core\Field\Address\AddressFormatter;
class Formatter4 implements AddressFormatter
{

View File

@@ -29,23 +29,14 @@
namespace Espo\Classes\AppInfo;
use Espo\Core\{
Container as ContainerService,
Utils\Metadata,
Console\Command\Params,
};
use Espo\Core\Console\Command\Params;
use Espo\Core\Container as ContainerService;
use Espo\Core\Utils\Metadata;
class Container
{
private $container;
private $metadata;
public function __construct(ContainerService $container, Metadata $metadata)
{
$this->container = $container;
$this->metadata = $metadata;
}
public function __construct(private ContainerService $container, private Metadata $metadata)
{}
public function process(Params $params): string
{

View File

@@ -30,31 +30,27 @@
namespace Espo\Classes\FieldProcessing\Email;
use Espo\ORM\Entity;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
ORM\EntityManager,
};
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
use Espo\Repositories\Email as EmailRepository;
use Espo\Entities\Email;
/**
* @implements Loader<\Espo\Entities\Email>
* @implements Loader<Email>
*/
class AddressDataLoader implements Loader
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function __construct(private EntityManager $entityManager)
{}
/**
* @param Email $entity
*/
public function process(Entity $entity, Params $params): void
{
/** @var EmailRepository $repository */
$repository = $this->entityManager->getRepository('Email');
$repository = $this->entityManager->getRepository(Email::ENTITY_TYPE);
$repository->loadFromField($entity);
$repository->loadToField($entity);

View File

@@ -29,27 +29,21 @@
namespace Espo\Classes\FieldProcessing\Import;
use Espo\Entities\Import;
use Espo\ORM\Entity;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
ORM\EntityManager,
};
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
use Espo\Repositories\Import as ImportRepository;
/**
* @implements Loader<\Espo\Entities\Import>
* @implements Loader<Import>
*/
class CountsLoader implements Loader
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function __construct(private EntityManager $entityManager)
{}
public function process(Entity $entity, Params $params): void
{

View File

@@ -30,16 +30,12 @@
namespace Espo\Classes\FieldProcessing\Note;
use Espo\ORM\Entity;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
};
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Entities\Note;
/**
* @implements Loader<\Espo\Entities\Note>
* @implements Loader<Note>
*/
class AttachmentsLoader implements Loader
{

View File

@@ -33,19 +33,16 @@ use Espo\ORM\Entity;
use Espo\Repositories\Portal as PortalRepository;
use Espo\Entities\Portal;
use Espo\Core\{
FieldProcessing\Loader,
FieldProcessing\Loader\Params,
ORM\EntityManager,
};
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
/**
* @implements Loader<\Espo\Entities\Portal>
* @implements Loader<Portal>
*/
class UrlLoader implements Loader
{
private $entityManager;
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{

View File

@@ -40,7 +40,7 @@ class ArrayType
{
private const DEFAULT_MAX_ITEM_LENGTH = 100;
public function __construct(private Metadata $metadata, private Defs $defs)
public function __construct(protected Metadata $metadata, private Defs $defs)
{}
public function checkRequired(Entity $entity, string $field): bool
@@ -128,6 +128,14 @@ class ArrayType
/** @var ?string $path */
$path = $fieldDefs->getParam('optionsPath');
/** @var ?string $path */
$ref = $fieldDefs->getParam('optionsReference');
if (!$path && $ref && str_contains($ref, '.')) {
[$refEntityType, $refField] = explode('.', $ref);
$path = "entityDefs.{$refEntityType}.fields.{$refField}.options";
}
/** @var string[]|null|false $optionList */
$optionList = $path ?

View File

@@ -0,0 +1,76 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\FieldValidators\Email\Addresses;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\Core\Utils\Config;
use Espo\Entities\Email;
use Espo\ORM\Entity;
use LogicException;
/**
* @implements Validator<Email>
*/
class MaxCount implements Validator
{
private const MAX_COUNT = 100;
public function __construct(private Config $config) {}
/**
* @param Email $entity
*/
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
if ($field === 'to') {
$addresses = $entity->getToAddressList();
}
else if ($field === 'cc') {
$addresses = $entity->getCcAddressList();
}
else if ($field === 'bcc') {
$addresses = $entity->getBccAddressList();
}
else {
throw new LogicException();
}
$maxCount = $this->config->get('emailRecipientAddressMaxCount') ?? self::MAX_COUNT;
if (count($addresses) > $maxCount) {
return Failure::create();
}
return null;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\FieldValidators\Email\Addresses;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\ORM\Entity;
use Espo\Entities\Email;
use LogicException;
/**
* @implements Validator<Email>
*/
class Valid implements Validator
{
/**
* @param Email $entity
*/
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
if ($field === 'to') {
$addresses = $entity->getToAddressList();
}
else if ($field === 'cc') {
$addresses = $entity->getCcAddressList();
}
else if ($field === 'bcc') {
$addresses = $entity->getBccAddressList();
}
else {
throw new LogicException();
}
foreach ($addresses as $address) {
if (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
return Failure::create();
}
}
return null;
}
}

View File

@@ -29,13 +29,17 @@
namespace Espo\Classes\FieldValidators\Email;
use Espo\Entities\Email;
use Espo\ORM\Entity;
class EmailAddresses
{
/**
* @param Email $entity
*/
public function checkRequired(Entity $entity, string $field): bool
{
if ($entity->get('status') === 'Draft') {
if ($entity->getStatus() === Email::STATUS_DRAFT) {
return true;
}

View File

@@ -37,7 +37,6 @@ use Espo\ORM\Entity;
class EnumType
{
private Metadata $metadata;
private Defs $defs;
private const DEFAULT_MAX_LENGTH = 255;
@@ -65,6 +64,14 @@ class EnumType
/** @var ?string $path */
$path = $fieldDefs->getParam('optionsPath');
/** @var ?string $path */
$ref = $fieldDefs->getParam('optionsReference');
if (!$path && $ref && str_contains($ref, '.')) {
[$refEntityType, $refField] = explode('.', $ref);
$path = "entityDefs.{$refEntityType}.fields.{$refField}.options";
}
/** @var string[]|null|false $optionList */
$optionList = $path ?

View File

@@ -117,7 +117,7 @@ class LinkMultipleType
return true;
}
/** @var ?array<string,string> $columnsMap */
/** @var ?array<string, string> $columnsMap */
$columnsMap = $fieldDefs->getParam('columns');
if ($columnsMap === null || $columnsMap === []) {

View File

@@ -30,19 +30,12 @@
namespace Espo\Classes\FieldValidators;
use Espo\ORM\Entity;
use Espo\Core\{
Utils\FieldUtil,
};
use Espo\Core\Utils\FieldUtil;
class PersonNameType
{
private $fieldUtil;
public function __construct(FieldUtil $fieldUtil)
{
$this->fieldUtil = $fieldUtil;
}
public function __construct(private FieldUtil $fieldUtil)
{}
public function checkRequired(Entity $entity, string $field): bool
{

View File

@@ -0,0 +1,55 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\FieldValidators;
use Espo\ORM\Entity;
class UrlMultipleType extends ArrayType
{
private const MAX_ITEM_LENGTH = 255;
public function checkNoEmptyString(Entity $entity, string $field, ?bool $validationValue): bool
{
return parent::checkNoEmptyString($entity, $field, true);
}
public function checkMaxItemLength(Entity $entity, string $field, ?int $validationValue): bool
{
return parent::checkMaxItemLength($entity, $field, self::MAX_ITEM_LENGTH);
}
public function checkPattern(Entity $entity, string $field, ?string $validationValue): bool
{
/** @var string $pattern */
$pattern = $this->metadata->get(['app', 'regExpPatterns', 'uriOptionalProtocol', 'pattern']);
return parent::checkPattern($entity, $field, $pattern);
}
}

View File

@@ -29,31 +29,28 @@
namespace Espo\Classes\JobPreparators;
use Espo\Core\Utils\DateTime;
use Espo\Core\Job\Job\Status;
use Espo\Core\Job\Preparator;
use Espo\Core\Job\Preparator\Data;
use Espo\Entities\EmailAccount;
use Espo\ORM\EntityManager;
use Espo\Entities\Job as JobEntity;
use Espo\Entities\EmailAccount;
use Espo\Core\Job\Preparator\CollectionHelper;
use DateTimeImmutable;
class CheckEmailAccounts implements Preparator
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param CollectionHelper<EmailAccount> $helper
*/
public function __construct(
private EntityManager $entityManager,
private CollectionHelper $helper
) {}
public function prepare(Data $data, DateTimeImmutable $executeTime): void
{
$collection = $this->entityManager
->getRDBRepository(EmailAccount::ENTITY_TYPE)
->getRDBRepositoryByClass(EmailAccount::class)
->join('assignedUser', 'assignedUserAdditional')
->where([
'status' => EmailAccount::STATUS_ACTIVE,
@@ -62,49 +59,6 @@ class CheckEmailAccounts implements Preparator
])
->find();
foreach ($collection as $entity) {
$running = $this->entityManager
->getRDBRepository(JobEntity::ENTITY_TYPE)
->where([
'scheduledJobId' => $data->getId(),
'status' => [
Status::RUNNING,
Status::READY,
],
'targetType' => EmailAccount::ENTITY_TYPE,
'targetId' => $entity->getId(),
])
->findOne();
if ($running) {
continue;
}
$countPending = $this->entityManager
->getRDBRepository(JobEntity::ENTITY_TYPE)
->where([
'scheduledJobId' => $data->getId(),
'status' => Status::PENDING,
'targetType' => EmailAccount::ENTITY_TYPE,
'targetId' => $entity->getId(),
])
->count();
if ($countPending > 1) {
continue;
}
$jobEntity = $this->entityManager->getNewEntity(JobEntity::ENTITY_TYPE);
$jobEntity->set([
'name' => $data->getName(),
'scheduledJobId' => $data->getId(),
'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT),
'targetType' => EmailAccount::ENTITY_TYPE,
'targetId' => $entity->getId(),
]);
$this->entityManager->saveEntity($jobEntity);
}
$this->helper->prepare($collection, $data, $executeTime);
}
}

View File

@@ -29,80 +29,34 @@
namespace Espo\Classes\JobPreparators;
use Espo\Core\Utils\DateTime;
use Espo\Core\Job\Job\Status;
use Espo\Core\Job\Preparator;
use Espo\Core\Job\Preparator\Data;
use Espo\Entities\InboundEmail;
use Espo\ORM\EntityManager;
use Espo\Entities\Job as JobEntity;
use Espo\Entities\InboundEmail;
use Espo\Core\Job\Preparator\CollectionHelper;
use DateTimeImmutable;
class CheckInboundEmails implements Preparator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param CollectionHelper<InboundEmail> $helper
*/
public function __construct(
private EntityManager $entityManager,
private CollectionHelper $helper
) {}
public function prepare(Data $data, DateTimeImmutable $executeTime): void
{
$collection = $this->entityManager
->getRDBRepository(InboundEmail::ENTITY_TYPE)
->getRDBRepositoryByClass(InboundEmail::class)
->where([
'status' => InboundEmail::STATUS_ACTIVE,
'useImap' => true,
])
->find();
foreach ($collection as $entity) {
$running = $this->entityManager
->getRDBRepository(JobEntity::ENTITY_TYPE)
->where([
'scheduledJobId' => $data->getId(),
'status' => [
Status::RUNNING,
Status::READY,
],
'targetType' => InboundEmail::ENTITY_TYPE,
'targetId' => $entity->getId(),
])
->findOne();
if ($running) {
continue;
}
$countPending = $this->entityManager
->getRDBRepository(JobEntity::ENTITY_TYPE)
->where([
'scheduledJobId' => $data->getId(),
'status' => Status::PENDING,
'targetType' => InboundEmail::ENTITY_TYPE,
'targetId' => $entity->getId(),
])
->count();
if ($countPending > 1) {
continue;
}
$jobEntity = $this->entityManager->getNewEntity(JobEntity::ENTITY_TYPE);
$jobEntity->set([
'name' => $data->getName(),
'scheduledJobId' => $data->getId(),
'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT),
'targetType' => InboundEmail::ENTITY_TYPE,
'targetId' => $entity->getId(),
]);
$this->entityManager->saveEntity($jobEntity);
}
$this->helper->prepare($collection, $data, $executeTime);
}
}

View File

@@ -609,8 +609,6 @@ class Cleanup implements JobDataLess
$repository->deleteFromDb($entity->getId());
$query = $this->entityManager->getQueryComposer();
foreach ($entity->getRelationList() as $relation) {
if ($entity->getRelationType($relation) !== Entity::MANY_MANY) {
continue;
@@ -763,6 +761,15 @@ class Cleanup implements JobDataLess
'deleted' => true,
];
if (
!$this->entityManager
->getDefs()
->getEntity($scope)
->hasAttribute('deleted')
) {
continue;
}
if ($this->metadata->get(['entityDefs', $scope, 'fields', 'modifiedAt'])) {
$whereClause['modifiedAt<'] = $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
}

View File

@@ -29,19 +29,13 @@
namespace Espo\Classes\Jobs;
use Espo\Core\{
Job\JobDataLess,
Webhook\Queue,
};
use Espo\Core\Job\JobDataLess;
use Espo\Core\Webhook\Queue;
class ProcessWebhookQueue implements JobDataLess
{
private $queue;
public function __construct(Queue $queue)
{
$this->queue = $queue;
}
public function __construct(private Queue $queue)
{}
public function run(): void
{

View File

@@ -29,20 +29,14 @@
namespace Espo\Classes\Select\ActionHistoryRecord\AccessControlFilters;
use Espo\{
Core\Select\AccessControl\Filter,
ORM\Query\SelectBuilder as QueryBuilder,
Entities\User,
};
use Espo\Core\Select\AccessControl\Filter;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyOwn implements Filter
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function __construct(private User $user)
{}
public function apply(QueryBuilder $queryBuilder): void
{

View File

@@ -29,22 +29,16 @@
namespace Espo\Classes\Select\ActionHistoryRecord\BoolFilters;
use Espo\{
Core\Select\Bool\Filter,
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereClause,
ORM\Query\Part\Where\OrGroupBuilder,
Entities\User,
};
use Espo\Core\Select\Bool\Filter;
use Espo\Entities\User;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyMy implements Filter
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function __construct(private User $user)
{}
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
{

View File

@@ -29,27 +29,15 @@
namespace Espo\Classes\Select\Email\AccessControlFilters;
use Espo\Core\{
Select\AccessControl\Filter,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
Classes\Select\Email\Helpers\JoinHelper,
Entities\User,
};
use Espo\Core\Select\AccessControl\Filter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class PortalOnlyAccount implements Filter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function apply(QueryBuilder $queryBuilder): void
{

View File

@@ -29,27 +29,15 @@
namespace Espo\Classes\Select\Email\AccessControlFilters;
use Espo\Core\{
Select\AccessControl\Filter,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
Classes\Select\Email\Helpers\JoinHelper,
Entities\User,
};
use Espo\Core\Select\AccessControl\Filter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class PortalOnlyContact implements Filter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function apply(QueryBuilder $queryBuilder): void
{

View File

@@ -29,26 +29,17 @@
namespace Espo\Classes\Select\Email\BoolFilters;
use Espo\{
Core\Select\Bool\Filter,
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereClause,
ORM\Query\Part\Where\OrGroupBuilder,
Classes\Select\Email\Helpers\JoinHelper,
Entities\User,
};
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Core\Select\Bool\Filter;
use Espo\Entities\User;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyMy implements Filter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
{

View File

@@ -29,32 +29,20 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
Select\Helpers\RandomStringGenerator,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Classes\Select\Email\Helpers\EmailAddressHelper,
};
use Espo\Core\Select\Helpers\RandomStringGenerator;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\EmailAddressHelper;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class EmailAddressEquals implements ItemConverter
{
private $emailAddressHelper;
private $randomStringGenerator;
public function __construct(
EmailAddressHelper $emailAddressHelper,
RandomStringGenerator $randomStringGenerator
) {
$this->emailAddressHelper = $emailAddressHelper;
$this->randomStringGenerator = $randomStringGenerator;
}
private EmailAddressHelper $emailAddressHelper,
private RandomStringGenerator $randomStringGenerator
) {}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,38 +29,18 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
ORM\EntityManager,
Classes\Select\Email\Helpers\EmailAddressHelper,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\EmailAddressHelper;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class FromEquals implements ItemConverter
{
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var EmailAddressHelper
*/
protected $emailAddressHelper;
public function __construct(
EntityManager $entityManager,
EmailAddressHelper $emailAddressHelper
) {
$this->entityManager = $entityManager;
$this->emailAddressHelper = $emailAddressHelper;
}
private EmailAddressHelper $emailAddressHelper
) {}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,30 +29,18 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Entities\User,
Classes\Select\Email\Helpers\JoinHelper,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsImportantIsFalse implements ItemConverter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,30 +29,18 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Entities\User,
Classes\Select\Email\Helpers\JoinHelper,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsImportantIsTrue implements ItemConverter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,30 +29,18 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Entities\User,
Classes\Select\Email\Helpers\JoinHelper,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsNotReadIsFalse implements ItemConverter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,30 +29,18 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Entities\User,
Classes\Select\Email\Helpers\JoinHelper,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsNotReadIsTrue implements ItemConverter
{
private $user;
private $joinHelper;
public function __construct(User $user, JoinHelper $joinHelper)
{
$this->user = $user;
$this->joinHelper = $joinHelper;
}
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,16 +29,11 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsNotRepliedIsFalse implements ItemConverter
{

View File

@@ -29,16 +29,11 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
};
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class IsNotRepliedIsTrue implements ItemConverter
{

View File

@@ -29,32 +29,20 @@
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\{
Select\Where\ItemConverter,
Select\Where\Item,
Select\Helpers\RandomStringGenerator,
};
use Espo\{
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereItem as WhereClauseItem,
ORM\Query\Part\WhereClause,
Classes\Select\Email\Helpers\EmailAddressHelper,
};
use Espo\Core\Select\Helpers\RandomStringGenerator;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\EmailAddressHelper;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class ToEquals implements ItemConverter
{
private $emailAddressHelper;
private $randomStringGenerator;
public function __construct(
EmailAddressHelper $emailAddressHelper,
RandomStringGenerator $randomStringGenerator
) {
$this->emailAddressHelper = $emailAddressHelper;
$this->randomStringGenerator = $randomStringGenerator;
}
private EmailAddressHelper $emailAddressHelper,
private RandomStringGenerator $randomStringGenerator
) {}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{

View File

@@ -29,20 +29,14 @@
namespace Espo\Classes\Select\EmailAccount\AccessControlFilters;
use Espo\{
Core\Select\AccessControl\Filter,
ORM\Query\SelectBuilder as QueryBuilder,
Entities\User,
};
use Espo\Core\Select\AccessControl\Filter;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class Mandatory implements Filter
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function __construct(private User $user)
{}
public function apply(QueryBuilder $queryBuilder): void
{

View File

@@ -29,13 +29,11 @@
namespace Espo\Classes\Select\EmailFilter\AccessControlFilters;
use Espo\{
Core\Select\AccessControl\Filter,
Entities\EmailAccount,
ORM\Query\SelectBuilder as QueryBuilder,
ORM\EntityManager,
Entities\User,
};
use Espo\Core\Select\AccessControl\Filter;
use Espo\Entities\EmailAccount;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyOwn implements Filter
{

View File

@@ -29,26 +29,18 @@
namespace Espo\Classes\Select\EmailFilter\BoolFilters;
use Espo\{
Core\Select\Bool\Filter,
Entities\EmailAccount,
ORM\Query\SelectBuilder as QueryBuilder,
ORM\Query\Part\WhereClause,
ORM\Query\Part\Where\OrGroupBuilder,
ORM\EntityManager,
Entities\User,
};
use Espo\Core\Select\Bool\Filter;
use Espo\Entities\EmailAccount;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyMy implements Filter
{
private User $user;
private EntityManager $entityManager;
public function __construct(User $user, EntityManager $entityManager)
{
$this->user = $user;
$this->entityManager = $entityManager;
}
public function __construct(private User $user, private EntityManager $entityManager)
{}
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
{

View File

@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\Select\Event\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Query\SelectBuilder;
class Held implements Filter
{
public function __construct(
private string $entityType,
private Metadata $metadata
) {}
public function apply(SelectBuilder $queryBuilder): void
{
$statusList = $this->metadata->get(['scopes', $this->entityType, 'completedStatusList']) ?? [];
$queryBuilder->where(['status' => $statusList]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\Select\Event\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Query\SelectBuilder;
class Planned implements Filter
{
public function __construct(
private string $entityType,
private Metadata $metadata
) {}
public function apply(SelectBuilder $queryBuilder): void
{
$statusList = $this->metadata->get(['scopes', $this->entityType, 'activityStatusList']) ?? [];
$queryBuilder->where(['status' => $statusList]);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 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\Classes\Select\Event\PrimaryFilters;
use Espo\Core\Exceptions\Error;
use Espo\Core\Select\Primary\Filter;
use Espo\Core\Select\Helpers\UserTimeZoneProvider;
use Espo\Core\Select\Where\ConverterFactory;
use Espo\Core\Select\Where\Item;
use Espo\ORM\Query\SelectBuilder;
use Espo\Entities\User;
use LogicException;
class Todays implements Filter
{
public function __construct(
private User $user,
private UserTimeZoneProvider $userTimeZoneProvider,
private ConverterFactory $converterFactory,
private string $entityType
) {}
public function apply(SelectBuilder $queryBuilder): void
{
$item = Item::fromRaw([
'type' => Item\Type::TODAY,
'attribute' => 'dateStart',
'timeZone' => $this->userTimeZoneProvider->get(),
'dateTime' => true,
]);
try {
$whereItem = $this->converterFactory
->create($this->entityType, $this->user)
->convert($queryBuilder, $item);
}
catch (Error $e) {
throw new LogicException($e->getMessage());
}
$queryBuilder->where($whereItem);
}
}

View File

@@ -29,33 +29,21 @@
namespace Espo\Classes\Select\Template\AccessControlFilters;
use Espo\ORM\{
Query\SelectBuilder,
Defs,
};
use Espo\Core\{
Select\AccessControl\Filter,
AclManager,
Acl\Exceptions\NotImplemented,
};
use Espo\ORM\Defs;
use Espo\ORM\Query\SelectBuilder;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\AclManager;
use Espo\Core\Select\AccessControl\Filter;
use Espo\Entities\User;
class Mandatory implements Filter
{
private $user;
private $defs;
private $aclManager;
public function __construct(User $user, Defs $defs, AclManager $aclManager)
{
$this->user = $user;
$this->defs = $defs;
$this->aclManager = $aclManager;
}
public function __construct(
private User $user,
private Defs $defs,
private AclManager $aclManager
) {}
public function apply(SelectBuilder $queryBuilder): void
{

View File

@@ -30,26 +30,16 @@
namespace Espo\Classes\Select\User\AccessControlFilters;
use Espo\ORM\Query\SelectBuilder;
use Espo\Core\{
Select\AccessControl\Filter,
AclManager,
Acl\Table,
};
use Espo\Core\Acl\Table;
use Espo\Core\AclManager;
use Espo\Core\Select\AccessControl\Filter;
use Espo\Entities\User;
class OnlyOwn implements Filter
{
private $user;
private $aclManager;
public function __construct(User $user, AclManager $aclManager)
{
$this->user = $user;
$this->aclManager = $aclManager;
}
public function __construct(private User $user, private AclManager $aclManager)
{}
public function apply(SelectBuilder $queryBuilder): void
{
@@ -57,7 +47,7 @@ class OnlyOwn implements Filter
$queryBuilder->where([
'OR' => [
'id' => $this->user->getId(),
'type' => 'portal',
'type' => User::TYPE_PORTAL,
],
]);

View File

@@ -83,7 +83,7 @@ class Admin
/**
* @param array<string,mixed> $params
* @param array<string, mixed> $params
* @param string $data
* @return array{
* id: string,
@@ -145,7 +145,7 @@ class Admin
}
/**
* @return array<int,array{id:string,type:string,message:string}>
* @return array<int, array{id: string, type: string, message: string}>
*/
public function actionAdminNotificationList(): array
{
@@ -154,9 +154,9 @@ class Admin
/**
* @return array{
* php: array<string,array<string,mixed>>,
* database: array<string,array<string,mixed>>,
* permission: array<string,array<string,mixed>>,
* php: array<string, array<string, mixed>>,
* database: array<string, array<string, mixed>>,
* permission: array<string, array<string, mixed>>,
* }
*/
public function actionSystemRequirementList(): array

View File

@@ -29,6 +29,8 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Entities\User;
use Espo\Tools\EntityManager\EntityManager as EntityManagerTool;
@@ -38,7 +40,9 @@ use Espo\Core\Exceptions\Forbidden;
class EntityManager
{
/**
* @throws Forbidden
*/
public function __construct(
private User $user,
private EntityManagerTool $entityManagerTool
@@ -49,6 +53,11 @@ class EntityManager
}
}
/**
* @throws BadRequest
* @throws Error
* @throws Conflict
*/
public function postActionCreateEntity(Request $request): bool
{
$data = $request->getParsedBody();
@@ -130,6 +139,10 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
* @throws Error
*/
public function postActionUpdateEntity(Request $request): bool
{
$data = $request->getParsedBody();
@@ -153,6 +166,11 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
* @throws Forbidden
* @throws Error
*/
public function postActionRemoveEntity(Request $request): bool
{
$data = $request->getParsedBody();
@@ -176,6 +194,11 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
* @throws Error
* @throws Conflict
*/
public function postActionCreateLink(Request $request): bool
{
$data = $request->getParsedBody();
@@ -354,6 +377,10 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
* @throws Error
*/
public function postActionFormula(Request $request): bool
{
$data = $request->getParsedBody();

View File

@@ -62,7 +62,7 @@ class FieldManager
}
/**
* @return array<string,mixed>
* @return array<string, mixed>
* @throws BadRequest
* @throws Error
*/

View File

@@ -43,7 +43,7 @@ class I18n
}
/**
* @return array<string,mixed>
* @return array<string, mixed>
*/
public function getActionRead(Request $request): array
{

View File

@@ -29,34 +29,26 @@
namespace Espo\Controllers;
use Espo\Core\{
Exceptions\Forbidden,
Exceptions\BadRequest,
Api\Request,
DataManager,
};
use Espo\{
Tools\LabelManager\LabelManager as LabelManagerTool,
Entities\User,
};
use Espo\Core\Api\Request;
use Espo\Core\DataManager;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Entities\User;
use Espo\Tools\LabelManager\LabelManager as LabelManagerTool;
use stdClass;
class LabelManager
{
private $user;
private $dataManager;
private $labelManagerTool;
public function __construct(User $user, DataManager $dataManager, LabelManagerTool $labelManagerTool)
{
$this->user = $user;
$this->dataManager = $dataManager;
$this->labelManagerTool = $labelManagerTool;
/**
* @throws Forbidden
*/
public function __construct(
private User $user,
private DataManager $dataManager,
private LabelManagerTool $labelManagerTool
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
}

View File

@@ -29,25 +29,16 @@
namespace Espo\Controllers;
use Espo\Core\{
Api\Request,
Record\SearchParamsFetcher,
};
use Espo\Core\Api\Request;
use Espo\Core\Record\SearchParamsFetcher;
use Espo\Tools\ActionHistory\Service as Service;
use stdClass;
class LastViewed
{
private SearchParamsFetcher $searchParamsFetcher;
private Service $service;
public function __construct(SearchParamsFetcher $searchParamsFetcher, Service $service)
{
$this->searchParamsFetcher = $searchParamsFetcher;
$this->service = $service;
}
public function __construct(private SearchParamsFetcher $searchParamsFetcher, private Service $service)
{}
public function getActionIndex(Request $request): stdClass
{

View File

@@ -31,15 +31,14 @@ namespace Espo\Controllers;
use Espo\Tools\Notification\RecordService as Service;
use Espo\Core\{
Controllers\RecordBase,
Api\Request,
Api\Response,
Exceptions\BadRequest,
Exceptions\Error,
Exceptions\Forbidden,
Select\SearchParams,
Select\Where\Item as WhereItem};
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Select\SearchParams;
use Espo\Core\Select\Where\Item as WhereItem;
use stdClass;

View File

@@ -29,10 +29,8 @@
namespace Espo\Controllers;
use Espo\Core\{
Controllers\Record,
Acl\Table,
};
use Espo\Core\Acl\Table;
use Espo\Core\Controllers\Record;
class Portal extends Record
{

View File

@@ -31,33 +31,24 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\TemplateFileManager;
use Espo\Core\ApplicationState;
use Espo\Core\{
Api\Request,
};
use Espo\Core\Api\Request;
use stdClass;
class TemplateManager
{
private $metadata;
private $templateFileManager;
private $applicationState;
/**
* @throws Forbidden
*/
public function __construct(
Metadata $metadata,
TemplateFileManager $templateFileManager,
ApplicationState $applicationState
private Metadata $metadata,
private TemplateFileManager $templateFileManager,
private ApplicationState $applicationState
) {
$this->metadata = $metadata;
$this->templateFileManager = $templateFileManager;
$this->applicationState = $applicationState;
if (!$this->applicationState->isAdmin()) {
throw new Forbidden();

View File

@@ -29,11 +29,9 @@
namespace Espo\Controllers;
use Espo\Core\{
Controllers\RecordBase,
Api\Request,
Api\Response,
};
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use stdClass;

View File

@@ -29,41 +29,27 @@
namespace Espo\Core\Acl\AccessChecker;
use Espo\Core\{
Utils\ClassFinder,
Utils\Metadata,
InjectableFactory,
Acl\Exceptions\NotImplemented,
Acl\DefaultAccessChecker,
Acl\AccessChecker,
AclManager,
Binding\BindingContainer,
Binding\Binder,
Binding\BindingData,
};
use Espo\Core\Acl\AccessChecker;
use Espo\Core\Acl\DefaultAccessChecker;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\ClassFinder;
use Espo\Core\Utils\Metadata;
class AccessCheckerFactory
{
/**
* @var class-string<AccessChecker>
*/
/** @var class-string<AccessChecker> */
private string $defaultClassName = DefaultAccessChecker::class;
private ClassFinder $classFinder;
private Metadata $metadata;
private InjectableFactory $injectableFactory;
public function __construct(
ClassFinder $classFinder,
Metadata $metadata,
InjectableFactory $injectableFactory
) {
$this->classFinder = $classFinder;
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
private ClassFinder $classFinder,
private Metadata $metadata,
private InjectableFactory $injectableFactory
) {}
/**
* Create an access checker.
@@ -112,7 +98,6 @@ class AccessCheckerFactory
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder->bindInstance(AclManager::class, $aclManager);
return new BindingContainer($bindingData);

View File

@@ -29,10 +29,8 @@
namespace Espo\Core\Acl\AccessChecker;
use Espo\Core\{
Acl\ScopeData,
Acl\Table,
};
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
/**
* Checks scope access.
@@ -40,8 +38,7 @@ use Espo\Core\{
class ScopeChecker
{
public function __construct()
{
}
{}
public function check(ScopeData $data, ?string $action = null, ?ScopeCheckerData $checkerData = null): bool
{

View File

@@ -29,20 +29,17 @@
namespace Espo\Core\Acl\AccessChecker;
use Closure;
/**
* Scope checker data.
*/
class ScopeCheckerData
{
private $isOwnChecker;
private $inTeamChecker;
public function __construct(callable $isOwnChecker, callable $inTeamChecker)
{
$this->isOwnChecker = $isOwnChecker;
$this->inTeamChecker = $inTeamChecker;
}
public function __construct(
private Closure $isOwnChecker,
private Closure $inTeamChecker
) {}
public function isOwn(): bool
{

View File

@@ -36,9 +36,8 @@ use Closure;
*/
class ScopeCheckerDataBuilder
{
private $isOwnChecker;
private $inTeamChecker;
private Closure $isOwnChecker;
private Closure $inTeamChecker;
public function __construct()
{

View File

@@ -29,37 +29,28 @@
namespace Espo\Core\Acl\AssignmentChecker;
use Espo\Core\{
Utils\Metadata,
InjectableFactory,
Acl\AssignmentChecker,
Acl\DefaultAssignmentChecker,
Acl\Exceptions\NotImplemented,
};
use Espo\Core\Acl\AssignmentChecker;
use Espo\Core\Acl\DefaultAssignmentChecker;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\InjectableFactory;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
class AssignmentCheckerFactory
{
/**
* @var class-string<AssignmentChecker<\Espo\Core\ORM\Entity>>
*/
/** @var class-string<AssignmentChecker<CoreEntity>> */
private string $defaultClassName = DefaultAssignmentChecker::class;
private Metadata $metadata;
private InjectableFactory $injectableFactory;
public function __construct(
Metadata $metadata,
InjectableFactory $injectableFactory
) {
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
private Metadata $metadata,
private InjectableFactory $injectableFactory
) {}
/**
* Create an access checker.
*
* @return AssignmentChecker<\Espo\ORM\Entity>
* @return AssignmentChecker<Entity>
* @throws NotImplemented
*/
public function create(string $scope): AssignmentChecker
@@ -70,12 +61,12 @@ class AssignmentCheckerFactory
}
/**
* @return class-string<AssignmentChecker<\Espo\ORM\Entity>>
* @return class-string<AssignmentChecker<Entity>>
* @throws NotImplemented
*/
private function getClassName(string $scope): string
{
/** @var ?class-string<AssignmentChecker<\Espo\ORM\Entity>> $className */
/** @var ?class-string<AssignmentChecker<Entity>> $className */
$className = $this->metadata->get(['aclDefs', $scope, 'assignmentCheckerClassName']);
if ($className) {
@@ -86,7 +77,7 @@ class AssignmentCheckerFactory
throw new NotImplemented();
}
/** @var class-string<AssignmentChecker<\Espo\ORM\Entity>> */
/** @var class-string<AssignmentChecker<Entity>> */
return $this->defaultClassName;
}
}

View File

@@ -30,24 +30,16 @@
namespace Espo\Core\Acl\AssignmentChecker;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Acl\AssignmentChecker;
class AssignmentCheckerManager
{
/**
* @var array<string,AssignmentChecker<Entity>>
*/
/** @var array<string, AssignmentChecker<Entity>> */
private $checkerCache = [];
private AssignmentCheckerFactory $factory;
public function __construct(AssignmentCheckerFactory $factory)
{
$this->factory = $factory;
}
public function __construct(private AssignmentCheckerFactory $factory)
{}
public function check(User $user, Entity $entity): bool
{

View File

@@ -32,16 +32,12 @@ namespace Espo\Core\Acl;
use Espo\Core\Interfaces\Injectable;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\{
ORM\EntityManager,
Acl\AccessChecker\ScopeCheckerData,
Acl\AccessChecker\ScopeChecker,
AclManager,
Utils\Config,
};
use Espo\Core\Acl\AccessChecker\ScopeChecker;
use Espo\Core\Acl\AccessChecker\ScopeCheckerData;
use Espo\Core\AclManager;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Config;
/**
* @deprecated As of v6.0. Use AccessChecker interfaces instead.

View File

@@ -39,14 +39,8 @@ use Espo\ORM\EntityManager;
*/
class Clearer
{
private FileManager $fileManager;
private EntityManager $entityManager;
public function __construct(FileManager $fileManager, EntityManager $entityManager)
{
$this->fileManager = $fileManager;
$this->entityManager = $entityManager;
}
public function __construct(private FileManager $fileManager, private EntityManager $entityManager)
{}
public function clearForAllInternalUsers(): void
{

View File

@@ -29,18 +29,14 @@
namespace Espo\Core\Acl\Map;
use Espo\Core\{
Acl\Table,
Utils\FieldUtil,
};
use Espo\Core\Acl\Table;
use Espo\Core\Utils\FieldUtil;
use stdClass;
class DataBuilder
{
/**
* @var string[]
*/
/** @var string[] */
private $actionList = [
Table::ACTION_READ,
Table::ACTION_STREAM,
@@ -48,32 +44,19 @@ class DataBuilder
Table::ACTION_DELETE,
Table::ACTION_CREATE,
];
/**
* @var string[]
*/
/** @var string[] */
private $fieldActionList = [
Table::ACTION_READ,
Table::ACTION_EDIT,
];
/**
* @var string[]
*/
/** @var string[] */
private $fieldLevelList = [
Table::LEVEL_YES,
Table::LEVEL_NO,
];
private MetadataProvider $metadataProvider;
private FieldUtil $fieldUtil;
public function __construct(MetadataProvider $metadataProvider, FieldUtil $fieldUtil)
{
$this->metadataProvider = $metadataProvider;
$this->fieldUtil = $fieldUtil;
}
public function __construct(private MetadataProvider $metadataProvider, private FieldUtil $fieldUtil)
{}
public function build(Table $table): stdClass
{

View File

@@ -33,12 +33,8 @@ use Espo\Entities\User;
class DefaultCacheKeyProvider implements CacheKeyProvider
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function __construct(private User $user)
{}
public function get(): string
{

View File

@@ -29,14 +29,10 @@
namespace Espo\Core\Acl\Map;
use Espo\Entities\User;
use Espo\Core\{
Acl\Table,
Utils\Config,
Utils\DataCache,
Utils\ObjectUtil,
};
use Espo\Core\Acl\Table;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\ObjectUtil;
use stdClass;
use RuntimeException;
@@ -47,50 +43,24 @@ use RuntimeException;
class Map
{
private stdClass $data;
private string $cacheKey;
/**
* @var array<string,string[]>
*/
/** @var array<string, string[]> */
private $forbiddenFieldsCache = [];
/**
* @var array<string,string[]>
*/
/** @var array<string, string[]> */
private $forbiddenAttributesCache;
/**
* @var string[]
*/
/** @var string[] */
private $fieldLevelList = [
Table::LEVEL_YES,
Table::LEVEL_NO,
];
private User $user;
private Table $table;
private Config $config;
private DataCache $dataCache;
private DataBuilder $dataBuilder;
public function __construct(
User $user,
Table $table,
DataBuilder $dataBuilder,
Config $config,
DataCache $dataCache,
private DataBuilder $dataBuilder,
private Config $config,
private DataCache $dataCache,
CacheKeyProvider $cacheKeyProvider
) {
$this->user = $user;
$this->table = $table;
$this->dataBuilder = $dataBuilder;
$this->config = $config;
$this->dataCache = $dataCache;
$this->cacheKey = $cacheKeyProvider->get();

View File

@@ -31,23 +31,16 @@ namespace Espo\Core\Acl\Map;
use Espo\Entities\User;
use Espo\Core\{
InjectableFactory,
Acl\Table,
Acl\Map\Map,
Binding\BindingContainer,
Binding\Binder,
Binding\BindingData,
};
use Espo\Core\Acl\Table;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
class MapFactory
{
private $injectableFactory;
public function __construct(InjectableFactory $injectableFactory)
{
$this->injectableFactory = $injectableFactory;
}
public function __construct(private InjectableFactory $injectableFactory)
{}
public function create(User $user, Table $table): Map
{
@@ -61,7 +54,6 @@ class MapFactory
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $user)
->bindInstance(Table::class, $table)

View File

@@ -35,12 +35,8 @@ class MetadataProvider
{
protected string $type = 'acl';
private Metadata $metadata;
public function __construct(Metadata $metadata)
{
$this->metadata = $metadata;
}
public function __construct(private Metadata $metadata)
{}
/**
* @return string[]
@@ -66,7 +62,7 @@ class MetadataProvider
}
/**
* @return array<int,string>
* @return array<int, string>
*/
public function getPermissionList(): array
{
@@ -74,7 +70,7 @@ class MetadataProvider
return array_map(
function (string $item): string {
if (substr($item, -10) === 'Permission') {
if (str_ends_with($item, 'Permission')) {
return substr($item, 0, -10);
}

View File

@@ -29,36 +29,25 @@
namespace Espo\Core\Acl\OwnershipChecker;
use Espo\Core\{
Utils\Metadata,
InjectableFactory,
Acl\Exceptions\NotImplemented,
Acl\DefaultOwnershipChecker,
Acl\OwnershipChecker,
AclManager,
Binding\BindingContainer,
Binding\Binder,
Binding\BindingData,
};
use Espo\Core\Acl\DefaultOwnershipChecker;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\Acl\OwnershipChecker;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Metadata;
class OwnershipCheckerFactory
{
/**
* @var class-string<OwnershipChecker>
*/
/** @var class-string<OwnershipChecker> */
private string $defaultClassName = DefaultOwnershipChecker::class;
private Metadata $metadata;
private InjectableFactory $injectableFactory;
public function __construct(
Metadata $metadata,
InjectableFactory $injectableFactory
) {
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
private Metadata $metadata,
private InjectableFactory $injectableFactory
) {}
/**
* Create an ownership checker.

View File

@@ -40,7 +40,7 @@ class ScopeData
{
/** @var stdClass|bool */
private $raw;
/** @var array<string,string> */
/** @var array<string, string> */
private $actionData = [];
private bool $isBoolean = false;

View File

@@ -30,28 +30,16 @@
namespace Espo\Core\Acl\Table;
use Espo\Entities\User;
use Espo\Core\{
InjectableFactory,
Acl\Table,
Acl\Table\DefaultTable,
Acl\Table\CacheKeyProvider,
Acl\Table\DefaultCacheKeyProvider,
Acl\Table\RoleListProvider,
Acl\Table\DefaultRoleListProvider,
Binding\BindingContainer,
Binding\Binder,
Binding\BindingData,
};
use Espo\Core\Acl\Table;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
class DefaultTableFactory implements TableFactory
{
private $injectableFactory;
public function __construct(InjectableFactory $injectableFactory)
{
$this->injectableFactory = $injectableFactory;
}
public function __construct(private InjectableFactory $injectableFactory)
{}
/**
* Create a table.

View File

@@ -30,24 +30,18 @@
namespace Espo\Core\AclPortal;
use Espo\Core\Interfaces\Injectable;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\AclManager;
use Espo\Core\{
ORM\EntityManager,
Portal\AclManager as PortalAclManager,
Utils\Config,
Acl\AccessChecker,
Acl\ScopeData,
Portal\Acl\Table,
Portal\Acl\AccessChecker\ScopeChecker,
Portal\Acl\AccessChecker\ScopeCheckerData,
Portal\Acl\DefaultAccessChecker,
};
use Espo\Core\Acl\AccessChecker;
use Espo\Core\Acl\ScopeData;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Portal\Acl\AccessChecker\ScopeChecker;
use Espo\Core\Portal\Acl\AccessChecker\ScopeCheckerData;
use Espo\Core\Portal\Acl\DefaultAccessChecker;
use Espo\Core\Portal\Acl\Table;
use Espo\Core\Portal\AclManager as PortalAclManager;
use Espo\Core\Utils\Config;
/**
* @deprecated Use AccessChecker interfaces instead.

View File

@@ -30,30 +30,32 @@
namespace Espo\Core\Action\Actions;
use Espo\Core\Acl;
use Espo\Core\Acl\Table;
use Espo\Core\Action\Action;
use Espo\Core\Action\Data;
use Espo\Core\Action\Params;
use Espo\Core\Currency\ConfigDataProvider as CurrencyConfigDataProvider;
use Espo\Core\Currency\Converter as CurrencyConverter;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Currency\Rates as CurrencyRates;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Field\Currency;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Tools\Currency\Conversion\EntityConverterFactory;
use RuntimeException;
class ConvertCurrency implements Action
{
public function __construct(
private EntityConverterFactory $converterFactory,
private Acl $acl,
private EntityManager $entityManager,
private FieldUtil $fieldUtil,
private Metadata $metadata,
private CurrencyConfigDataProvider $configDataProvider,
private CurrencyConverter $currencyConverter
private User $user
) {}
public function process(Params $params, Data $data): void
@@ -65,22 +67,16 @@ class ConvertCurrency implements Action
throw new Forbidden();
}
$fieldList = $this->getFieldList($entityType, $data);
if (empty($fieldList)) {
throw new Forbidden("No fields to convert.");
}
$this->checkFieldAccess($entityType);
$baseCurrency = $this->configDataProvider->getBaseCurrency();
$targetCurrency = $data->get('targetCurrency');
if (!$targetCurrency) {
throw new BadRequest("No target currency.");
}
$rates =
$this->getRatesFromData($data) ??
$rates = $this->getRatesFromData($data) ??
$this->configDataProvider->getCurrencyRates();
if ($targetCurrency !== $baseCurrency && !$rates->hasRate($targetCurrency)) {
@@ -94,56 +90,21 @@ class ConvertCurrency implements Action
}
if (!$this->acl->checkEntityEdit($entity)) {
throw new Forbidden();
throw new Forbidden("No 'edit' access.");
}
$this->convertEntity($entity, $fieldList, $targetCurrency, $rates);
}
/**
* @param string[] $fieldList
*/
protected function convertEntity(
Entity $entity,
array $fieldList,
string $targetCurrency,
CurrencyRates $rates
): void {
$entityDefs = $this->entityManager
->getDefs()
->getEntity($entity->getEntityType());
foreach ($fieldList as $field) {
$disabled = $entityDefs->getField($field)->getParam('conversionDisabled');
if ($disabled) {
continue;
}
$amount = $entity->get($field);
$code = $entity->get($field . 'Currency');
if ($amount === null) {
continue;
}
if ($targetCurrency === $code) {
continue;
}
$value = new Currency($amount, $code);
$convertedValue = $this->currencyConverter->convertWithRates($value, $targetCurrency, $rates);
$entity->set($field, $convertedValue->getAmount());
$entity->set($field . 'Currency', $convertedValue->getCode());
if (!$entity instanceof CoreEntity) {
throw new RuntimeException("Only Core-Entity allowed.");
}
$this->entityManager->saveEntity($entity);
$converter = $this->converterFactory->create($entityType);
$converter->convert($entity, $targetCurrency, $rates);
$this->entityManager->saveEntity($entity, [SaveOption::MODIFIED_BY_ID => $this->user->getId()]);
}
protected function getRatesFromData(Data $data): ?CurrencyRates
private function getRatesFromData(Data $data): ?CurrencyRates
{
if ($data->get('rates') === null) {
return null;
@@ -152,37 +113,27 @@ class ConvertCurrency implements Action
$baseCurrency = $this->configDataProvider->getBaseCurrency();
$ratesArray = get_object_vars($data->get('rates'));
$ratesArray[$baseCurrency] = 1.0;
return CurrencyRates::fromAssoc($ratesArray, $baseCurrency);
}
/**
* @return string[]
* @throws Forbidden
*/
protected function getFieldList(string $entityType, Data $data): array
private function checkFieldAccess(string $entityType): void
{
$forbiddenFieldList = $this->acl->getScopeForbiddenFieldList($entityType, 'edit');
/** @var string[] $requiredFieldList */
$requiredFieldList = $this->metadata->get(['scopes', $entityType, 'currencyConversionAccessRequiredFieldList']);
$resultList = [];
$fieldList = $data->get('fieldList') ?? $this->fieldUtil->getEntityTypeFieldList($entityType);
foreach ($fieldList as $field) {
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
if ($type !== 'currency') {
continue;
}
if (in_array($field, $forbiddenFieldList)) {
continue;
}
$resultList[] = $field;
if ($requiredFieldList === null) {
return;
}
return $resultList;
foreach ($requiredFieldList as $field) {
if (!$this->acl->checkField($entityType, $field, Table::ACTION_EDIT)) {
throw new Forbidden("No edit access to field `$field`.");
}
}
}
}

View File

@@ -38,9 +38,8 @@ use Espo\Core\ORM\EntityManager;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\ObjectUtil;
use Espo\Entities\ActionHistoryRecord;
use Espo\ORM\Entity;
use Espo\Entities\EmailAddress;
use Espo\Entities\PhoneNumber;
@@ -136,7 +135,7 @@ class Merger
foreach ($sourceEntityList as $sourceEntity) {
$this->entityManager->removeEntity($sourceEntity);
$service->processActionHistoryRecord('delete', $sourceEntity);
$service->processActionHistoryRecord(ActionHistoryRecord::ACTION_DELETE, $sourceEntity);
}
if ($hasPhoneNumber) {
@@ -151,7 +150,7 @@ class Merger
$this->entityManager->saveEntity($entity);
$service->processActionHistoryRecord('update', $entity);
$service->processActionHistoryRecord(ActionHistoryRecord::ACTION_UPDATE, $entity);
}
/**

View File

@@ -29,10 +29,14 @@
namespace Espo\Core\Api;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\HasBody;
use Espo\Core\Exceptions\HasLogLevel;
use Espo\Core\Exceptions\HasLogMessage;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
@@ -67,11 +71,13 @@ class ErrorOutput
503,
];
/**
* @var class-string[]
*/
private $ignorePrintXStatusReasonExceptionClassNameList = [
'PDOException',
/** @var class-string<Throwable>[] */
private array $printStatusReasonExceptionClassNameList = [
Error::class,
Forbidden::class,
Conflict::class,
BadRequest::class,
NotFound::class,
];
public function __construct(private Log $log, private Config $config)
@@ -141,18 +147,13 @@ class ErrorOutput
$this->log->log($logLevel, $logMessage);
$toPrintBodyXStatusReason = !in_array(
get_class($exception),
$this->ignorePrintXStatusReasonExceptionClassNameList
);
if (!in_array($statusCode, $this->allowedStatusCodeList)) {
$statusCode = 500;
}
$response->setStatus($statusCode);
if ($toPrintBodyXStatusReason) {
if ($this->toPrintExceptionStatusReason($exception)) {
$response->setHeader('X-Status-Reason', $this->stripInvalidCharactersFromHeaderValue($message));
}
@@ -256,4 +257,16 @@ class ErrorOutput
{
return (bool) $this->config->get('logger.printTrace');
}
private function toPrintExceptionStatusReason(Throwable $exception): bool
{
foreach ($this->printStatusReasonExceptionClassNameList as $clasName) {
if ($exception instanceof ($clasName)) {
return true;
}
}
return false;
}
}

View File

@@ -51,7 +51,7 @@ interface Request
/**
* Get all query parameters.
*
* @return array<string,string|array<scalar,mixed>>
* @return array<string, string|array<scalar, mixed>>
*/
public function getQueryParams(): array;
@@ -68,7 +68,7 @@ interface Request
/**
* Get all route parameters.
*
* @return array<string,string>
* @return array<string, string>
*/
public function getRouteParams(): array;

View File

@@ -29,10 +29,13 @@
namespace Espo\Core\Api;
use Espo\Core\Utils\Config;
use stdClass;
class Util
{
public function __construct(private Config $config) {}
public static function cloneObject(stdClass $source): stdClass
{
$cloned = (object) [];
@@ -70,4 +73,11 @@ class Util
return $item;
}
public function obtainIpFromRequest(Request $request): ?string
{
$param = $this->config->get('ipAddressServerParam') ?? 'REMOTE_ADDR';
return $request->getServerParam($param);
}
}

View File

@@ -34,17 +34,15 @@ namespace Espo\Core\Application\Runner;
*/
class Params
{
/** @var array<string,mixed> */
/** @var array<string, mixed> */
private $data = [];
public function __construct() {}
/**
* Get a parameter value.
*
* @return mixed
*/
public function get(string $name)
public function get(string $name): mixed
{
return $this->data[$name] ?? null;
}
@@ -59,13 +57,10 @@ class Params
/**
* Clone with a parameter value.
*
* @param mixed $value
*/
public function with(string $name, $value): self
public function with(string $name, mixed $value): self
{
$obj = clone $this;
$obj->data[$name] = $value;
return $obj;
@@ -74,12 +69,11 @@ class Params
/**
* Create from an associative array.
*
* @param array<string,mixed> $data
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
$obj = new self();
$obj->data = $data;
return $obj;

View File

@@ -42,19 +42,11 @@ use ReflectionClass;
*/
class RunnerRunner
{
private Log $log;
private ApplicationUser $applicationUser;
private InjectableFactory $injectableFactory;
public function __construct(
Log $log,
ApplicationUser $applicationUser,
InjectableFactory $injectableFactory
) {
$this->log = $log;
$this->applicationUser = $applicationUser;
$this->injectableFactory = $injectableFactory;
}
private Log $log,
private ApplicationUser $applicationUser,
private InjectableFactory $injectableFactory
) {}
/**
* @param class-string<Runner|RunnerParameterized> $className
@@ -72,7 +64,7 @@ class RunnerRunner
if (
$class->getStaticPropertyValue('cli', false) &&
substr(php_sapi_name() ?: '', 0, 3) !== 'cli'
!str_starts_with(php_sapi_name() ?: '', 'cli')
) {
throw new RunnerException("Can be run only via CLI.");
}
@@ -83,19 +75,12 @@ class RunnerRunner
$runner = $this->injectableFactory->create($className);
if ($runner instanceof Runner) {
$runner->run();
return;
}
if ($runner instanceof RunnerParameterized) {
$runner->run($params ?? Params::create());
return;
}
/** @phpstan-ignore-next-line */
throw new RunnerException("Class should implement Runner or RunnerParameterized interface.");
$runner->run();
}
}

View File

@@ -29,11 +29,9 @@
namespace Espo\Core\ApplicationRunners;
use Espo\Core\{
Application\Runner,
DataManager,
Exceptions\Error,
};
use Espo\Core\Application\Runner;
use Espo\Core\DataManager;
use Espo\Core\Exceptions\Error;
/**
* Clears an application cache.
@@ -42,12 +40,8 @@ class ClearCache implements Runner
{
use Cli;
private DataManager $dataManager;
public function __construct(DataManager $dataManager)
{
$this->dataManager = $dataManager;
}
public function __construct(private DataManager $dataManager)
{}
/**
* @throws Error

View File

@@ -47,53 +47,71 @@ class Data
private function __construct()
{}
/**
* A user ID.
*/
public function getUserId(): string
{
return $this->userId;
}
/**
* A portal ID.
*/
public function getPortalId(): ?string
{
return $this->portalId;
}
/**
* A hash.
*/
public function getHash(): ?string
{
return $this->hash;
}
/**
* An ID address.
*/
public function getIpAddress(): ?string
{
return $this->ipAddress;
}
/**
* To create a secret.
*/
public function toCreateSecret(): bool
{
return $this->createSecret;
}
/**
* @param array<string, mixed> $data
* @param array{
* userId: string,
* portalId?: ?string,
* hash?: ?string,
* ipAddress?: ?string,
* createSecret?: ?bool,
* } $data
*/
public static function create(array $data): self
{
$object = new self();
$obj = new self();
$object->userId = $data['userId'] ?? null;
$object->portalId = $data['portalId'] ?? null;
$object->hash = $data['hash'] ?? null;
$object->ipAddress = $data['ipAddress'] ?? null;
$object->createSecret = $data['createSecret'] ?? false;
$userId = $data['userId'] ?? null;
$object->validate();
return $object;
}
private function validate(): void
{
if (!$this->userId) {
if (!$userId) {
throw new RuntimeException("No user ID.");
}
$obj->userId = $userId;
$obj->portalId = $data['portalId'] ?? null;
$obj->hash = $data['hash'] ?? null;
$obj->ipAddress = $data['ipAddress'] ?? null;
$obj->createSecret = $data['createSecret'] ?? false;
return $obj;
}
}

View File

@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Repositories\UserData as UserDataRepository;
@@ -37,7 +38,6 @@ use Espo\Entities\User;
use Espo\Entities\AuthLogRecord;
use Espo\Entities\AuthToken as AuthTokenEntity;
use Espo\Entities\UserData;
use Espo\Core\Exceptions\Error\Body;
use Espo\Core\Authentication\Logout\Params as LogoutParams;
use Espo\Core\Authentication\Util\MethodProvider;
@@ -52,6 +52,7 @@ use Espo\Core\ApplicationUser;
use Espo\Core\ApplicationState;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Api\Util;
use Espo\Core\Utils\Log;
use Espo\Core\ORM\EntityManagerProxy;
use Espo\Core\Exceptions\ServiceUnavailable;
@@ -85,7 +86,8 @@ class Authentication
private HookManager $hookManager,
private Log $log,
private LogoutFactory $logoutFactory,
private MethodProvider $methodProvider
private MethodProvider $methodProvider,
private Util $util
) {}
/**
@@ -219,7 +221,7 @@ class Authentication
$user->loadLinkMultipleField('teams');
}
$user->set('ipAddress', $request->getServerParam('REMOTE_ADDR') ?? null);
$user->set('ipAddress', $this->util->obtainIpFromRequest($request));
[$loggedUser, $anotherUserFailReason] = $this->getLoggedUser($request, $user);
@@ -464,15 +466,19 @@ class Authentication
$request->getHeader(self::HEADER_CREATE_TOKEN_SECRET) === 'true' &&
!$this->configDataProvider->isAuthTokenSecretDisabled();
$arrayData = [
'hash' => $user->get('password'),
'ipAddress' => $request->getServerParam('REMOTE_ADDR'),
'userId' => $user->hasId() ? $user->getId() : null,
/** @var ?string $password */
$password = $user->get('password');
$ipAddress = $this->util->obtainIpFromRequest($request);
$authTokenData = AuthTokenData::create([
'hash' => $password,
'ipAddress' => $ipAddress,
'userId' => $user->getId(),
'portalId' => $this->isPortal() ? $this->getPortal()->getId() : null,
'createSecret' => $createSecret,
];
]);
$authToken = $this->authTokenManager->create(AuthTokenData::create($arrayData));
$authToken = $this->authTokenManager->create($authTokenData);
if ($createSecret) {
$this->setSecretInCookie($authToken->getSecret(), $response, $request);
@@ -590,7 +596,7 @@ class Authentication
$authLogRecord
->setUsername($username)
->setIpAddress($request->getServerParam('REMOTE_ADDR'))
->setIpAddress($this->util->obtainIpFromRequest($request))
->setRequestTime($request->getServerParam('REQUEST_TIME_FLOAT'))
->setRequestMethod($request->getMethod())
->setRequestUrl($requestUrl)

View File

@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication\Hook\Hooks;
use Espo\Core\Api\Util;
use Espo\Core\Authentication\Hook\BeforeLogin;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
@@ -45,7 +46,8 @@ class FailedAttemptsLimit implements BeforeLogin
public function __construct(
private ConfigDataProvider $configDataProvider,
private EntityManager $entityManager,
private Log $log
private Log $log,
private Util $util
) {}
/**
@@ -70,7 +72,7 @@ class FailedAttemptsLimit implements BeforeLogin
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
$ip = $request->getServerParam('REMOTE_ADDR');
$ip = $this->util->obtainIpFromRequest($request);
$where = [
'requestTime>' => $requestTimeFrom->format('U'),

View File

@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication\Ldap;
use Espo\Core\Api\Util;
use Espo\Core\FieldProcessing\Relation\LinkMultipleSaver;
use Espo\Core\FieldProcessing\EmailAddress\Saver as EmailAddressSaver;
use Espo\Core\FieldProcessing\PhoneNumber\Saver as PhoneNumberSaver;
@@ -56,50 +57,30 @@ class LdapLogin implements Login
{
private LDAPUtils $utils;
private ?Client $client = null;
private bool $isPortal;
private Config $config;
private EntityManager $entityManager;
private PasswordHash $passwordHash;
private Language $language;
private Log $log;
private Espo $baseLogin;
private ClientFactory $clientFactory;
private LinkMultipleSaver $linkMultipleSaver;
private EmailAddressSaver $emailAddressSaver;
private PhoneNumberSaver $phoneNumberSaver;
public function __construct(
Config $config,
EntityManager $entityManager,
PasswordHash $passwordHash,
private Config $config,
private EntityManager $entityManager,
private PasswordHash $passwordHash,
Language $defaultLanguage,
Log $log,
Espo $baseLogin,
ClientFactory $clientFactory,
LinkMultipleSaver $linkMultipleSaver,
EmailAddressSaver $emailAddressSaver,
PhoneNumberSaver $phoneNumberSaver,
bool $isPortal = false
private Log $log,
private Espo $baseLogin,
private ClientFactory $clientFactory,
private LinkMultipleSaver $linkMultipleSaver,
private EmailAddressSaver $emailAddressSaver,
private PhoneNumberSaver $phoneNumberSaver,
private Util $util,
private bool $isPortal = false
) {
$this->config = $config;
$this->entityManager = $entityManager;
$this->passwordHash = $passwordHash;
$this->language = $defaultLanguage;
$this->log = $log;
$this->baseLogin = $baseLogin;
$this->clientFactory = $clientFactory;
$this->linkMultipleSaver = $linkMultipleSaver;
$this->emailAddressSaver = $emailAddressSaver;
$this->phoneNumberSaver = $phoneNumberSaver;
$this->isPortal = $isPortal;
$this->utils = new LDAPUtils($config);
}
/**
* @var array<string,string>
* @var array<string, string>
*/
private $ldapFieldMap = [
'userName' => 'userNameAttribute',
@@ -111,7 +92,7 @@ class LdapLogin implements Login
];
/**
* @var array<string,string>
* @var array<string, string>
*/
private $userFieldMap = [
'teamsIds' => 'userTeamsIds',
@@ -119,7 +100,7 @@ class LdapLogin implements Login
];
/**
* @var array<string,string>
* @var array<string, string>
*/
private $portalUserFieldMap = [
'portalsIds' => 'portalUserPortalsIds',
@@ -135,7 +116,7 @@ class LdapLogin implements Login
$isPortal = $this->isPortal;
if ($authToken) {
$user = $this->loginByToken($username, $authToken);
$user = $this->loginByToken($username, $authToken, $request);
if ($user) {
return Result::success($user);
@@ -278,9 +259,9 @@ class LdapLogin implements Login
/**
* Login by authorization token.
*/
private function loginByToken(?string $username, AuthToken $authToken = null): ?User
private function loginByToken(?string $username, AuthToken $authToken, Request $request): ?User
{
if (!isset($authToken) || $username === null) {
if ($username === null) {
return null;
}
@@ -296,11 +277,9 @@ class LdapLogin implements Login
$tokenUsername = $user->getUserName() ?? '';
if (strtolower($username) !== strtolower($tokenUsername)) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$ip = $this->util->obtainIpFromRequest($request);
$this->log->alert(
'Unauthorized access attempt for user [' . $username . '] from IP [' . $ip . ']'
);
$this->log->alert('Unauthorized access attempt for user [' . $username . '] from IP [' . $ip . ']');
return null;
}
@@ -331,7 +310,7 @@ class LdapLogin implements Login
/**
* Create Espo user with data gets from LDAP server.
*
* @param array<string,mixed> $userData
* @param array<string, mixed> $userData
*/
private function createUser(array $userData, bool $isPortal = false): ?User
{
@@ -415,7 +394,7 @@ class LdapLogin implements Login
'(' . $options['userNameAttribute'] . '=' . $username . ')' .
$loginFilterString . ')';
/** @var array<int,array{dn: string}> $result */
/** @var array<int, array{dn: string}> $result */
$result = $ldapClient->search($searchString, null, Client::SEARCH_SCOPE_SUB);
$this->log->debug('LDAP: user search string: "' . $searchString . '"');
@@ -448,7 +427,7 @@ class LdapLogin implements Login
/**
* Load fields for a user.
*
* @return array<string,mixed>
* @return array<string, mixed>
*/
private function loadFields(string $type): array
{

View File

@@ -36,12 +36,12 @@ class Utils
private Config $config;
/**
* @var ?array<string,mixed>
* @var ?array<string, mixed>
*/
private ?array $options = null;
/**
* @var array<string,string>
* @var array<string, string>
*/
private $fieldMap = [
'host' => 'ldapHost',
@@ -76,7 +76,7 @@ class Utils
];
/**
* @var array<int,string>
* @var array<int, string>
*/
private $permittedEspoOptions = [
'createEspoUser',
@@ -98,7 +98,7 @@ class Utils
/**
* AccountCanonicalForm Map between Espo and Laminas value.
*
* @var array<string,int>
* @var array<string, int>
*/
private $accountCanonicalFormMap = [
'Dn' => 1,
@@ -117,7 +117,7 @@ class Utils
/**
* Get Options from espo config according to $this->fieldMap.
*
* @return array<string,mixed>
* @return array<string, mixed>
*/
public function getOptions(): array
{
@@ -143,9 +143,9 @@ class Utils
/**
* Normalize options to LDAP client format
*
* @param array<string,mixed> $options
* @param array<string, mixed> $options
*
* @return array<string,mixed>
* @return array<string, mixed>
*/
public function normalizeOptions(array $options): array
{
@@ -179,7 +179,7 @@ class Utils
/**
* Get Laminas options for using Laminas\Ldap.
*
* @return array<string,mixed>
* @return array<string, mixed>
*/
public function getLdapClientOptions(): array
{

View File

@@ -114,14 +114,32 @@ class BindingContainer
$key = '$' . $param->getName();
}
if ($className && $key && $this->data->hasContext($className, $key)) {
$type = $param->getType();
if (
$className &&
$key &&
$this->data->hasContext($className, $key)
) {
// @todo For v7.6. Uncomment, then remove the return statement below.
/*$binding = $this->data->getContext($className, $key);
$notMatching =
$type &&
$type instanceof ReflectionNamedType &&
!$type->isBuiltin() &&
$binding->getType() === Binding::VALUE &&
is_scalar($binding->getValue());
if (!$notMatching) {
return $binding;
}*/
return $this->data->getContext($className, $key);
}
$dependencyClassName = null;
$type = $param->getType();
if (
$type &&
$type instanceof ReflectionNamedType &&

View File

@@ -38,7 +38,7 @@ use Espo\Core\Utils\Util;
*/
class Params
{
/** @var array<string,string> */
/** @var array<string, string> */
private $options;
/** @var string[] */
private $flagList;
@@ -46,7 +46,7 @@ class Params
private $argumentList;
/**
* @param array<string,string>|null $options
* @param array<string, string>|null $options
* @param string[]|null $flagList
* @param string[]|null $argumentList
*/
@@ -58,7 +58,7 @@ class Params
}
/**
* @return array<string,string>
* @return array<string, string>
*/
public function getOptions(): array
{
@@ -114,7 +114,7 @@ class Params
}
/**
* @param array<int,string> $args
* @param array<int, string> $args
*/
public static function fromArgs(array $args): self
{
@@ -123,17 +123,17 @@ class Params
$flagList = [];
foreach ($args as $i => $item) {
if (strpos($item, '--') === 0 && strpos($item, '=') > 2) {
list($name, $value) = explode('=', substr($item, 2));
if (str_starts_with($item, '--') && strpos($item, '=') > 2) {
[$name, $value] = explode('=', substr($item, 2));
$name = Util::hyphenToCamelCase($name);
$options[$name] = $value;
}
else if (strpos($item, '--') === 0) {
else if (str_starts_with($item, '--')) {
$flagList[] = Util::hyphenToCamelCase(substr($item, 2));
}
else if (strpos($item, '-') === 0) {
else if (str_starts_with($item, '-')) {
$flagList[] = substr($item, 1);
}
else if ($i > 0) {

View File

@@ -54,7 +54,7 @@ class CommandManager
}
/**
* @param array<int,string> $argv
* @param array<int, string> $argv
*
* @return int<0, 255> Exit-status.
*/
@@ -98,7 +98,7 @@ class CommandManager
}
/**
* @param array<int,string> $argv
* @param array<int, string> $argv
*/
private function getCommandNameFromArgv(array $argv): ?string
{
@@ -146,7 +146,7 @@ class CommandManager
}
/**
* @param array<int,string> $argv
* @param array<int, string> $argv
*/
private function createParamsFromArgv(array $argv): Params
{

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