Compare commits

...

344 Commits

Author SHA1 Message Date
Yuri Kuznetsov 8ef2ed4144 7.5.4 2023-06-22 09:35:19 +03:00
Yuri Kuznetsov befec82120 fix history 2023-06-22 09:27:11 +03:00
Yuri Kuznetsov e88bd5898c 7.5.3 2023-06-21 13:09:42 +03:00
Yuri Kuznetsov a5bb5005f8 fix upgrade 2023-06-21 13:03:58 +03:00
Yuri Kuznetsov 324f776ed5 css fix 2023-06-20 09:29:48 +03:00
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 20111 additions and 9215 deletions
+1 -1
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: ''
+2 -1
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."
+1 -1
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
View File
@@ -4,3 +4,4 @@
!/fileTemplates
!/inspectionProfiles
!misc.xml
!jsonSchemas.xml
+1351
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
*
!.gitignore
!settings.json
+422
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"
}
]
}
+2 -4
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: '',
}
]
+22 -11
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).
@@ -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;
}
@@ -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;
@@ -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)) {
@@ -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)) {
@@ -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;
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
+5 -14
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
{
@@ -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);
@@ -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
{
@@ -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
{
@@ -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)
{
@@ -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 ?
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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 ?
@@ -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 === []) {
@@ -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
{
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
+9 -2
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);
}
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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
{
@@ -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]);
}
}
@@ -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]);
}
}
@@ -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);
}
}
@@ -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
{
@@ -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,
],
]);
+5 -5
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
+28 -1
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();
@@ -62,7 +62,7 @@ class FieldManager
}
/**
* @return array<string,mixed>
* @return array<string, mixed>
* @throws BadRequest
* @throws Error
*/
+1 -1
View File
@@ -43,7 +43,7 @@ class I18n
}
/**
* @return array<string,mixed>
* @return array<string, mixed>
*/
public function getActionRead(Request $request): array
{
+14 -22
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();
}
+4 -13
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
{
@@ -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;
+2 -4
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
{
@@ -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();
+3 -5
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;
@@ -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);
@@ -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
{
@@ -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
{
@@ -36,9 +36,8 @@ use Closure;
*/
class ScopeCheckerDataBuilder
{
private $isOwnChecker;
private $inTeamChecker;
private Closure $isOwnChecker;
private Closure $inTeamChecker;
public function __construct()
{
@@ -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;
}
}
@@ -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
{
+5 -9
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.
+2 -8
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
{
+7 -24
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
{
@@ -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
{
+10 -40
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();
+7 -15
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)
@@ -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);
}
@@ -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.
+1 -1
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;
@@ -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.
+9 -15
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.
@@ -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`.");
}
}
}
}
@@ -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);
}
/**
+25 -12
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;
}
}
+2 -2
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;
+10
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);
}
}
@@ -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;
@@ -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();
}
}
@@ -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
@@ -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;
}
}
@@ -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)
@@ -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'),
@@ -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
{
@@ -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
{
@@ -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 &&
@@ -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) {
@@ -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