Compare commits

...

749 Commits

Author SHA1 Message Date
Yuri Kuznetsov 268354f3c6 8.2.2 2024-04-04 10:21:51 +03:00
Yuri Kuznetsov 3ca68f2539 email tempalte importable 2024-04-04 09:55:47 +03:00
Yuri Kuznetsov 08f2c5ea72 disable options reference for salutation 2024-04-04 09:32:53 +03:00
Yuri Kuznetsov 4d1f3d214d label format 2024-04-03 15:52:09 +03:00
Yuri Kuznetsov fab13b3140 fix import action translation 2024-04-03 15:36:25 +03:00
Yuri Kuznetsov 45ac0f7fee fix autocomplete 2024-04-03 10:19:37 +03:00
Yuri Kuznetsov 406487d50c before upgrade extension check 2024-04-03 09:38:52 +03:00
Yuri Kuznetsov d73608e16b 8.2.1 2024-04-02 20:55:39 +03:00
Yuri Kuznetsov 8328e8c644 fix action history order 2024-04-02 19:38:09 +03:00
Yuri Kuznetsov 19a96d2e3d disable customization for user portal 2024-04-02 17:32:30 +03:00
Yuri Kuznetsov a823e06f13 dynamic logic lin one 2024-04-02 15:42:02 +03:00
Yuri Kuznetsov 0336b76a96 before upgrade script 2024-04-02 10:45:35 +03:00
Yuri Kuznetsov 76cac35c54 link name default middle name prefix 2024-04-02 10:10:32 +03:00
Yuri Kuznetsov 088edbf708 avatar colors 2024-03-31 21:14:21 +03:00
Yuri Kuznetsov e2f6c8abe7 ref 2024-03-31 20:56:03 +03:00
Yuri Kuznetsov f2735e5fbc avatar color 2024-03-31 17:31:18 +03:00
Yuri Kuznetsov f8106a81c7 ref meeting buttons 2024-03-31 15:57:59 +03:00
Yuri Kuznetsov 1d7bce5343 ref 2024-03-31 15:37:16 +03:00
Yuri Kuznetsov 224e0e8625 internal note icon position 2024-03-30 16:42:06 +02:00
Yuri Kuznetsov d5b3a7d2ea calendar mode buttons change 2024-03-30 13:14:19 +02:00
Yuri Kuznetsov bd0be4542e calendar mode buttons change 2024-03-30 13:12:09 +02:00
Yuri Kuznetsov e78052ce33 ref 2024-03-30 13:01:26 +02:00
Yuri Kuznetsov 24f79b9206 avatar color change 2024-03-30 12:39:49 +02:00
Yuri Kuznetsov df9633503b email account filters 2024-03-30 10:07:38 +02:00
Yuri Kuznetsov 5e1c12f4b1 reminder fix 2024-03-30 09:55:02 +02:00
Yuri Kuznetsov 5699c3d15e close button style fix 2024-03-30 09:49:00 +02:00
Yuri Kuznetsov cd88e8ac7a cs 2024-03-30 09:31:35 +02:00
Yuri Kuznetsov 3c83e1dcd3 hide complex modified field if not modified 2024-03-30 09:23:56 +02:00
Yuri Kuznetsov d232838676 stream abort last fetch 2024-03-30 09:08:52 +02:00
Yuri Kuznetsov 2ae0f48619 avatar color change 2024-03-29 16:52:11 +02:00
Yuri Kuznetsov 170782d17f add BDT currency 2024-03-29 16:49:22 +02:00
Yuri Kuznetsov 2cbdbbf6fb avatar color changes 2024-03-29 16:44:14 +02:00
Yuri Kuznetsov 0a750f373f avatar font change 2024-03-29 16:39:51 +02:00
Yuri Kuznetsov 92ff5e1859 avatar explicit font file 2024-03-29 15:58:18 +02:00
Yuri Kuznetsov cb3257a890 notification list view refresh ui fix 2024-03-29 13:49:05 +02:00
Yuri Kuznetsov 9377cd150d img preview border radius small 2024-03-29 13:33:34 +02:00
Yuri Kuznetsov e1f2fd8094 avatar color change 2024-03-29 11:54:43 +02:00
Yuri Kuznetsov 70dcf6ed69 avatar colors change 2024-03-29 11:47:26 +02:00
Yuri Kuznetsov 6c62dbc604 portal avatar color 2024-03-29 11:34:52 +02:00
Yuri Kuznetsov b6807091a5 user avater css fix 2024-03-29 11:25:11 +02:00
Yuri Kuznetsov 33f3784b3a lang 2024-03-29 09:49:28 +02:00
Yuri Kuznetsov 0e9b552796 teams importable 2024-03-29 09:35:13 +02:00
Yuri Kuznetsov 111e1a278c ref 2024-03-29 09:33:48 +02:00
Yuri Kuznetsov 34ecdd7533 export ignore utility 2024-03-29 09:28:15 +02:00
Yuri Kuznetsov 7ec9c1c5ae team export 2024-03-29 09:24:00 +02:00
Yuri Kuznetsov 1055fd8a79 lang 2024-03-29 09:23:33 +02:00
Yuri Kuznetsov 03102da373 fix format person name 2024-03-28 15:38:22 +02:00
Yuri Kuznetsov 23ea8b418a ref 2024-03-28 15:16:12 +02:00
Yuri Kuznetsov 9269fa1933 cs 2024-03-28 15:14:33 +02:00
Yuri Kuznetsov 5a1a9f17a0 ref 2024-03-28 10:25:44 +02:00
Yuri Kuznetsov c4ca71a7f6 firefox calendar style fix 2024-03-28 10:23:48 +02:00
Yuri Kuznetsov 991d111ac3 auth token expiration support decimals 2024-03-27 21:59:42 +02:00
Yuri Kuznetsov 7f18fb0cba fix foreign enum error 2024-03-27 21:49:04 +02:00
Yuri Kuznetsov c4d54ffa71 avatar font size 2024-03-27 19:38:10 +02:00
Yuri Kuznetsov 1ee2d5c58b avatar change sizes 2024-03-27 19:31:14 +02:00
Yuri Kuznetsov 94d9d0fc38 cleanup 2024-03-27 16:53:31 +02:00
dependabot[bot] 9252905103 Bump express from 4.18.1 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.1 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.1...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 15:27:22 +02:00
Yuri Kuznetsov e9b49a5317 cleanup vendor 2024-03-27 14:30:37 +02:00
Yuri Kuznetsov 9e50337c99 avatar fallback to userName 2024-03-27 13:02:44 +02:00
Yuri Kuznetsov 07b68d9b7d number power function 2024-03-27 10:11:03 +02:00
Yuri Kuznetsov c7d1fc7c35 ref 2024-03-26 19:02:16 +02:00
Yuri Kuznetsov 18b5b99854 opp by lead source chart changes 2024-03-26 18:01:23 +02:00
Yuri Kuznetsov e60d4ba19b fix sticky bar destroy 2024-03-26 17:39:52 +02:00
Yuri Kuznetsov dcf2b49b19 pie chart select none 2024-03-26 16:36:35 +02:00
Yuri Kuznetsov 4aecb4a255 chart no user select 2024-03-26 16:31:18 +02:00
Yuri Kuznetsov 7cf7f2ffd8 update note style change 2024-03-26 15:36:46 +02:00
Yuri Kuznetsov a78161799c scroll after go to page 2024-03-26 15:09:03 +02:00
Yuri Kuznetsov 6a37eee973 thousand separator validator 2024-03-26 14:19:17 +02:00
Yuri Kuznetsov bb5be010f2 pagination true 2024-03-26 12:29:36 +02:00
Yuri Kuznetsov dcd4e56aa7 sticky bar ref and fix 2024-03-26 12:09:59 +02:00
Yuri Kuznetsov 12f928c192 cs 2024-03-26 11:15:35 +02:00
Yuri Kuznetsov 1b47b94154 ref 2024-03-26 10:08:44 +02:00
Yuri Kuznetsov f64f4ea1b1 total text style fix 2024-03-26 09:58:03 +02:00
Yuri Kuznetsov faa33769f1 list top bar style fixes 2024-03-26 09:54:41 +02:00
Yuri Kuznetsov 1b0487eeea sort + sticky bar fix 2024-03-25 17:46:11 +02:00
Yuri Kuznetsov 7ffc06065f fix last page menu item 2024-03-25 16:11:00 +02:00
Yuri Kuznetsov 515720afda cleanup 2024-03-25 16:00:35 +02:00
Yuri Kuznetsov 3f1e6a6628 pagination go to page 2024-03-25 15:33:58 +02:00
Yuri Kuznetsov a94deb9f33 event confirmation note style fix 2024-03-25 13:37:52 +02:00
Yuri Kuznetsov b2d91b3fe9 pagination shortcuts 2024-03-25 12:17:53 +02:00
Yuri Kuznetsov 1f7e23af28 collection has page methods 2024-03-25 11:57:09 +02:00
Yuri Kuznetsov 0275b7fe4d pagination show more fixes 2024-03-25 11:52:21 +02:00
Yuri Kuznetsov d4b25090aa do not fetch after remove 2024-03-25 11:41:01 +02:00
Yuri Kuznetsov 7b14e7fb79 chart legend style fix 2024-03-25 11:32:48 +02:00
Yuri Kuznetsov 1d156e6af6 caret hover color 2024-03-24 23:12:10 +02:00
Yuri Kuznetsov b91e40b0e7 fix calendar 7d event issue 2024-03-24 18:23:02 +02:00
Yuri Kuznetsov fab908c313 contributing note 2024-03-24 18:09:17 +02:00
Yuri Kuznetsov 24e5cd67dd pagination warning fix 2024-03-24 17:02:17 +02:00
Yuri Kuznetsov 7297e0eb83 pagination display top bar if out of bound 2024-03-24 16:58:26 +02:00
Yuri Kuznetsov 81cce0ddb7 light theme by default 2024-03-24 12:46:14 +02:00
Yuri Kuznetsov 8232ef3dd2 label-state 2024-03-24 12:12:53 +02:00
Yuri Kuznetsov b930180907 stream update note: show fields 2024-03-24 11:19:50 +02:00
Yuri Kuznetsov ef6866773c stream notes style changes 2024-03-24 10:54:00 +02:00
Yuri Kuznetsov 415dc7607e cs 2024-03-24 10:15:24 +02:00
Yuri Kuznetsov 726f2abd96 email body insert field button in the end 2024-03-23 20:33:23 +02:00
Yuri Kuznetsov 894acad7ec less ref 2024-03-23 20:24:14 +02:00
Yuri Kuznetsov d9d7de8804 ref 2024-03-23 13:31:09 +02:00
Yuri Kuznetsov dcd0469977 fix collection set offset 2024-03-23 13:19:32 +02:00
Yuri Kuznetsov 434112eff3 pagination w/ show-more fixes 2024-03-23 12:17:42 +02:00
Yuri Kuznetsov ace9186691 update bullbone 2024-03-23 12:02:01 +02:00
Yuri Kuznetsov 29cc42e2e8 pagination offset fix 2024-03-23 11:43:13 +02:00
Yuri Kuznetsov 3ae6120067 ref 2024-03-23 10:07:14 +02:00
Yuri Kuznetsov 377f51a962 ref 2024-03-23 09:37:36 +02:00
Yuri Kuznetsov dc61e630b4 primary filter menu style 2024-03-23 09:19:07 +02:00
Yuri Kuznetsov d2ec8eda86 color fix 2024-03-23 08:58:19 +02:00
Yuri Kuznetsov 376de3b6d9 style fix 2024-03-22 11:53:08 +02:00
Yuri Kuznetsov 82ad0cb5cd selectize small style fix 2024-03-22 11:46:08 +02:00
Yuri Kuznetsov df9431670a schema 2024-03-22 11:41:43 +02:00
Yuri Kuznetsov e96f4fcedd api key copy to clipboard 2024-03-22 11:37:51 +02:00
Yuri Kuznetsov 1e96b90e28 status styles 2024-03-22 11:12:20 +02:00
Yuri Kuznetsov 46897ec3dd status styles changes 2024-03-22 11:04:30 +02:00
Yuri Kuznetsov ffa8248ac1 small input 29px 2024-03-22 10:35:59 +02:00
Yuri Kuznetsov 7d90a0c59b fix 2024-03-21 21:23:35 +02:00
Yuri Kuznetsov e33e77cb9e style fix 2024-03-21 13:28:32 +02:00
Yuri Kuznetsov f7a7c3bc72 comment 2024-03-21 13:15:07 +02:00
Yuri Kuznetsov c8371bef3f dynamic logic style fix 2024-03-21 13:06:29 +02:00
Yuri Kuznetsov 7c3f285342 opp probability not required 2024-03-21 11:56:24 +02:00
Yuri Kuznetsov ed3a7d64c6 sanitize date and date time, parse ATOM format 2024-03-21 11:35:47 +02:00
Yuri Kuznetsov 00b4569f8e webhook queue change scheduling 2024-03-20 12:02:39 +02:00
Yuri Kuznetsov 41e0b90850 cs 2024-03-20 12:02:10 +02:00
Yuri Kuznetsov 4d35bce189 metadata get by key endpoint 2024-03-20 11:42:11 +02:00
Yuri Kuznetsov e6631ba1d1 ref 2024-03-20 09:44:26 +02:00
Yuri Kuznetsov 6bae952947 Merge branch 'fix' 2024-03-20 09:17:30 +02:00
Yuri Kuznetsov 2e550af196 templte helper data context mixed 2024-03-20 09:16:08 +02:00
Yuri Kuznetsov 95ecc416f1 tpl fix 2024-03-19 15:54:09 +02:00
Yuri Kuznetsov ee3b0cb53d bc fix 2024-03-19 15:48:08 +02:00
Yuri Kuznetsov bfa0ea53a4 avatar field rounded 2024-03-19 12:21:41 +02:00
Yuri Kuznetsov 2f338d6eda lang files 2024-03-19 12:13:08 +02:00
Yuri Kuznetsov 6e59165786 v 2024-03-19 11:06:54 +02:00
Yuri Kuznetsov a5a6c8a0a1 cleanup 2024-03-19 10:19:45 +02:00
Yuri Kuznetsov cd0c397b07 ref 2024-03-19 10:13:27 +02:00
Yuri Kuznetsov 7ba7f8aa14 load assigned user name always 2024-03-19 09:07:42 +02:00
Yuri Kuznetsov 91d0660cba load assigned user name if null 2024-03-19 08:56:54 +02:00
Yuri Kuznetsov e8bbcb2ae4 tabindex 2024-03-18 17:57:58 +02:00
Yuri Kuznetsov 834d1808a1 cleanup preferences params 2024-03-18 17:10:43 +02:00
Yuri Kuznetsov 2d264eccb7 fix pagination error from detail pagination 2024-03-18 16:26:51 +02:00
Yuri Kuznetsov 3ec0fe1e03 fetch after remove if pagination 2024-03-18 15:58:09 +02:00
Yuri Kuznetsov 2f85c5cb3c ref 2024-03-18 15:10:38 +02:00
Yuri Kuznetsov 69cd4386f1 fix pagination 2024-03-18 15:07:24 +02:00
Yuri Kuznetsov effb81837f fix 2024-03-18 14:42:52 +02:00
Yuri Kuznetsov d0139ada06 list expanded force no pagination 2024-03-18 14:34:50 +02:00
Yuri Kuznetsov 2920793e40 style fix 2024-03-18 14:28:31 +02:00
Yuri Kuznetsov 570f886a1f reseting collection offset 2024-03-18 13:54:28 +02:00
Yuri Kuznetsov 21dab40b43 rebuild full on pg 2024-03-18 13:24:40 +02:00
Yuri Kuznetsov 801ccf94c7 pagination impr 2024-03-18 13:17:33 +02:00
Yuri Kuznetsov 7114b99cbc sticky bar fix 2024-03-18 11:07:22 +02:00
Yuri Kuznetsov 94c99f298e pagination stlye fixes 2024-03-18 10:55:10 +02:00
Yuri Kuznetsov ac37a4c232 notficaition bc fix 2024-03-18 10:11:31 +02:00
Yuri Kuznetsov 5ad4f30505 ref 2024-03-18 10:07:46 +02:00
Yuri Kuznetsov b4773ade9b fix 2024-03-17 18:14:22 +02:00
Yuri Kuznetsov 63f422f93a ref, return promise 2024-03-17 13:02:44 +02:00
Yuri Kuznetsov 1134f89a5a suppress 2024-03-17 12:56:30 +02:00
Yuri Kuznetsov fe0b1b62d1 ref 2024-03-17 12:50:52 +02:00
Yuri Kuznetsov d549ccb300 style fix 2024-03-17 12:47:52 +02:00
Yuri Kuznetsov 06d596b5cf pagination changes 2024-03-17 11:46:58 +02:00
Yuri Kuznetsov 1091b21306 clear stored email view 2024-03-16 12:28:16 +02:00
Yuri Kuznetsov 780b66d5b4 docs 2024-03-16 12:13:04 +02:00
Yuri Kuznetsov 10982e1b19 store main view ref 2024-03-15 18:35:15 +02:00
Yuri Kuznetsov 068375022e grid layout manager style impr 2024-03-15 17:41:28 +02:00
Yuri Kuznetsov d6bd701492 remove eot svg fonts 2024-03-15 16:44:42 +02:00
Yuri Kuznetsov 223b4ab8aa extension version print 2024-03-15 15:35:42 +02:00
Yuri Kuznetsov 6aff357473 cleanup 2024-03-15 15:08:20 +02:00
Yuri Kuznetsov 3ef24bb5ad avatars 2024-03-15 14:54:02 +02:00
Yuri Kuznetsov 9e60ea2209 image preview border radius 2024-03-15 13:45:02 +02:00
Yuri Kuznetsov 86904380bb image list preview style fix 2024-03-15 12:59:41 +02:00
Yuri Kuznetsov 9cdef865fe ref 2024-03-14 11:51:52 +02:00
Yuri Kuznetsov 1d611720c6 remove title 2024-03-14 11:43:27 +02:00
Yuri Kuznetsov 7285e058cd fix link field unset on clear 2024-03-14 10:57:32 +02:00
Yuri Kuznetsov e44617e276 fix extend 2024-03-14 10:16:48 +02:00
Yuri Kuznetsov 81972e26ff restore opp service class 2024-03-14 10:07:23 +02:00
Yuri Kuznetsov 5a7a4608c9 fix message 2024-03-14 10:00:04 +02:00
Yuri Kuznetsov 9161e64932 update icons 2024-03-14 09:56:12 +02:00
Yuri Kuznetsov cfd514c5a5 remove test 2024-03-13 22:55:02 +02:00
Yuri Kuznetsov b42f4ee256 no order 2024-03-13 22:10:52 +02:00
Yuri Kuznetsov a5a4fbca2f fix field manager when no params for type 2024-03-13 19:48:29 +02:00
Yuri Kuznetsov a706811338 attachment remove 2024-03-13 16:26:58 +02:00
Yuri Kuznetsov ec120dfd75 ref 2024-03-13 16:01:49 +02:00
Yuri Kuznetsov fe7b19b96d color fix 2024-03-13 15:15:15 +02:00
Yuri Kuznetsov ff5e1dac43 color fixes 2024-03-13 14:39:45 +02:00
Yuri Kuznetsov e73f7e9807 group folder ui impr 2024-03-13 14:13:32 +02:00
Yuri Kuznetsov 15e99e3063 schema 2024-03-13 13:24:20 +02:00
Yuri Kuznetsov 4edc909f2a fix 2024-03-13 12:36:16 +02:00
Yuri Kuznetsov d1fe29adbe throw tag 2024-03-13 12:32:20 +02:00
Yuri Kuznetsov 980226c4a2 cs 2024-03-13 12:30:39 +02:00
Yuri Kuznetsov c4c4306ad3 import encode URL 2024-03-13 12:29:53 +02:00
Yuri Kuznetsov 10732c0a90 fix 2024-03-13 11:00:30 +02:00
Yuri Kuznetsov ca2c4b3ee4 Merge branch 'fix' 2024-03-12 13:42:45 +02:00
Yuri Kuznetsov 03671efe11 category default 2024-03-12 13:38:29 +02:00
Yuri Kuznetsov 748529afac ref 2024-03-11 18:43:55 +02:00
Yuri Kuznetsov 1a003e5905 ref 2024-03-11 18:37:47 +02:00
Eymen Elkum d6c66b8b43 fix entity rename command 2024-03-11 11:00:22 +02:00
Yuri Kuznetsov 813bc05e74 foreign field do not list utility link 2024-03-11 09:47:03 +02:00
Yuri Kuznetsov ef447b400c fix test send button 2024-03-09 21:34:00 +02:00
Yuri Kuznetsov aca823df64 Merge branch 'fix' 2024-03-09 21:26:58 +02:00
Yuri Kuznetsov 0832faa2f2 fix port is string 2024-03-09 21:26:47 +02:00
Yuri Kuznetsov 7573bd1f92 link parent autocomplete fix 2024-03-08 16:27:14 +02:00
Yuri Kuznetsov 35eb013c97 schema 2024-03-08 11:10:07 +02:00
Yuri Kuznetsov 110f769384 ics description fix line break 2024-03-07 17:02:31 +02:00
Yuri Kuznetsov 6ce7c6ccda schema fix 2024-03-07 12:38:16 +02:00
Yuri Kuznetsov 350141727b disable merge 2024-03-06 13:47:16 +02:00
Yuri Kuznetsov c63fed50d5 fix install labels 2024-03-06 13:46:42 +02:00
Yuri Kuznetsov 24a22328c9 prop helper undefined warn 2024-03-05 10:39:32 +02:00
Yuri Kuznetsov 0504410ee9 fix type check 2024-03-05 10:06:38 +02:00
Yuri Kuznetsov 3a1bc73692 update phpseclib 2024-03-05 10:05:41 +02:00
Yuri Kuznetsov f02ee87344 customizationReadOnlyDisabled for account field 2024-03-04 17:00:40 +02:00
Yuri Kuznetsov 7cdcb375b3 wysiwyg audited 2024-03-04 16:09:28 +02:00
Yuri Kuznetsov 52c07987c6 move 2024-03-04 16:06:11 +02:00
Yuri Kuznetsov 11dee6e984 global stream prepare note 2024-03-04 16:05:05 +02:00
Yuri Kuznetsov 036cf4330f fix stream 2024-03-04 15:59:33 +02:00
Yuri Kuznetsov 4fec73307b pg orm tests 2024-03-04 13:42:36 +02:00
Yuri Kuznetsov a58047433b eol 2024-03-04 13:06:10 +02:00
Yuri Kuznetsov e004884352 schema 2024-03-04 13:06:03 +02:00
Yuri Kuznetsov 8aa7236dd6 link select filter 2024-03-04 12:56:07 +02:00
Yuri Kuznetsov 1f297aa42e setOptionList promise 2024-03-04 11:54:36 +02:00
Yuri Kuznetsov b5a0848513 style fix 2024-03-04 11:38:40 +02:00
Yuri Kuznetsov c2ea9781ee cs 2024-03-04 11:04:13 +02:00
Yuri Kuznetsov 93255071ac color fix 2024-03-04 10:27:17 +02:00
Yuri Kuznetsov 313293b57b comments 2024-03-03 13:32:46 +02:00
Yuri Kuznetsov 687ceca26e fix hooksDisabled 2024-03-03 13:25:54 +02:00
Yuri Kuznetsov 55b1c1511a cs 2024-03-03 13:16:35 +02:00
Yuri Kuznetsov 04ccf36966 ref 2024-03-02 19:15:02 +02:00
Yuri Kuznetsov 3eba144998 event repository ref 2024-03-02 19:10:39 +02:00
Yuri Kuznetsov e2bd509aeb hooksDisabled 2024-03-02 18:51:18 +02:00
Yuri Kuznetsov 9be5342220 repositoryClassName, entityClassName in metadata 2024-03-02 18:42:36 +02:00
Yuri Kuznetsov e96c1deffe comment 2024-03-02 18:02:26 +02:00
Yuri Kuznetsov aa53bc89b4 output filters 2024-03-02 13:30:41 +02:00
Yuri Kuznetsov e57bd13f73 lead capture copy to clipboard url 2024-03-01 16:38:46 +02:00
Yuri Kuznetsov 00f30e01d5 style fix 2024-03-01 16:38:32 +02:00
Yuri Kuznetsov fcb73fb3a8 ref 2024-03-01 16:32:00 +02:00
Yuri Kuznetsov fba82f8697 ref, comments 2024-03-01 16:07:57 +02:00
Yuri Kuznetsov 13c8fc15f4 ref 2024-03-01 16:03:27 +02:00
Yuri Kuznetsov 3a3cac54a2 ref 2024-03-01 15:49:30 +02:00
Yuri Kuznetsov 303860d509 forceValidation, ref 2024-03-01 15:39:49 +02:00
Yuri Kuznetsov ba2a740d38 ref 2024-03-01 15:02:37 +02:00
Yuri Kuznetsov 87f4508d1c schema 2024-03-01 14:04:01 +02:00
Yuri Kuznetsov fea79ba8c9 after link record hooks 2024-03-01 13:49:35 +02:00
Yuri Kuznetsov 734ca9b488 cleanup 2024-03-01 13:37:00 +02:00
Yuri Kuznetsov c695e64652 ref, noinsp 2024-03-01 13:20:25 +02:00
Yuri Kuznetsov 77218b788a cleanup 2024-03-01 13:18:02 +02:00
Yuri Kuznetsov e5063c2016 ref 2024-03-01 13:15:01 +02:00
Yuri Kuznetsov a6cfb38433 fix 2024-03-01 12:54:45 +02:00
Yuri Kuznetsov ae657ada61 ref 2024-03-01 12:36:07 +02:00
Yuri Kuznetsov ee8963ace8 ref 2024-03-01 11:12:41 +02:00
Yuri Kuznetsov aca76ae3a6 ref 2024-02-29 20:04:34 +02:00
Yuri Kuznetsov 5e3cdc594d user stream note helper 2024-02-29 17:49:35 +02:00
Yuri Kuznetsov 959fdbab94 ref 2024-02-29 17:38:48 +02:00
Yuri Kuznetsov f19952af12 ref 2024-02-29 17:25:08 +02:00
Yuri Kuznetsov 9b3c59bfa4 ref 2024-02-29 15:47:59 +02:00
Yuri Kuznetsov 55dd4ecf74 formula priority fix 2024-02-29 14:42:25 +02:00
Yuri Kuznetsov bbff632fbc bind user 2024-02-29 12:44:29 +02:00
Yuri Kuznetsov 4127be7f2f record service bind user 2024-02-29 12:05:41 +02:00
Yuri Kuznetsov 3a77ea83a3 record input filters 2024-02-29 11:46:28 +02:00
Yuri Kuznetsov 2bd70eee4b Merge branch 'fix' 2024-02-29 10:15:36 +02:00
Yuri Kuznetsov 0dd34df7d7 isWorkingDay fix 2024-02-29 10:10:33 +02:00
Yuri Kuznetsov 41c0567a4f ref 2024-02-28 15:10:32 +02:00
Yuri Kuznetsov dcaa1e209e rename 2024-02-28 14:59:33 +02:00
Yuri Kuznetsov f1e67d943d rename 2024-02-28 14:54:39 +02:00
Yuri Kuznetsov 70afd19f7f ref 2024-02-28 14:42:22 +02:00
Yuri Kuznetsov 7f6ce95fd6 record create defaults populator 2024-02-28 14:37:45 +02:00
Yuri Kuznetsov 035e1ef9eb Merge branch 'fix' 2024-02-28 10:14:46 +02:00
Yuri Kuznetsov 5988642a89 8.1.5 2024-02-28 10:05:22 +02:00
Yuri Kuznetsov 4964fbb1b2 load link multiple audit was names 2024-02-27 17:55:55 +02:00
Yuri Kuznetsov 2ca3aade8c fix link multiple factory 2024-02-27 12:55:49 +02:00
Yuri Kuznetsov 878f33929b upper case multi word note string items 2024-02-27 12:34:31 +02:00
Yuri Kuznetsov ae340b3279 highlight field on restore 2024-02-27 11:48:08 +02:00
Yuri Kuznetsov 0da0b8974c audit restore 2024-02-27 11:10:52 +02:00
Yuri Kuznetsov d7804bfa79 ref 2024-02-27 10:20:29 +02:00
Yuri Kuznetsov e451126af7 ref 2024-02-26 19:43:47 +02:00
Yuri Kuznetsov 38049d0ef4 upper case status 2024-02-26 18:12:35 +02:00
Yuri Kuznetsov 31e25047cc ref 2024-02-26 18:08:47 +02:00
Yuri Kuznetsov 8d105465a3 ref 2024-02-26 18:06:44 +02:00
Yuri Kuznetsov d8c021def2 fix 2024-02-26 17:57:19 +02:00
Yuri Kuznetsov ba08b8a8af ref 2024-02-26 17:56:17 +02:00
Yuri Kuznetsov 6974c00d02 fix layout manager label conflict 2024-02-26 17:06:57 +02:00
Yuri Kuznetsov 5446914131 ref 2024-02-26 16:54:27 +02:00
Yuri Kuznetsov 68add0bbd7 Update feature_request.md 2024-02-26 16:25:39 +02:00
Yuri Kuznetsov 28d0c4dd6e Update feature_request.md 2024-02-26 16:24:53 +02:00
Yuri Kuznetsov a201f61eeb ref 2024-02-26 15:05:20 +02:00
Yuri Kuznetsov 0ec428b1ed target list mandatoryAttributeList usage 2024-02-26 14:59:03 +02:00
Yuri Kuznetsov a088ca0875 import add field quick search 2024-02-26 14:47:17 +02:00
Yuri Kuznetsov d412766794 fix roles 2024-02-26 13:54:56 +02:00
Yuri Kuznetsov 3cd2a6b74e fix role translations 2024-02-26 13:45:58 +02:00
Yuri Kuznetsov 136ae8ae24 fix link multiple value object factory 2024-02-26 13:28:52 +02:00
Yuri Kuznetsov d80b8ce76b mass link restriction, ref 2024-02-26 13:22:08 +02:00
Yuri Kuznetsov 392616bdd3 ref, deprecate 2024-02-26 12:14:33 +02:00
Yuri Kuznetsov 4d6387e69d duplicateLinkList and ref 2024-02-26 12:04:57 +02:00
Yuri Kuznetsov eebe244247 ref 2024-02-26 11:56:04 +02:00
Yuri Kuznetsov dbfb1c696f role select style 2024-02-26 11:21:53 +02:00
Yuri Kuznetsov 2ed620335f cs 2024-02-26 11:15:09 +02:00
Yuri Kuznetsov 09efbd175d ref 2024-02-26 11:00:45 +02:00
Yuri Kuznetsov e4c67a4a6f fix test 2024-02-25 13:41:07 +02:00
Yuri Kuznetsov e8dd049baf fix test 2024-02-25 12:45:05 +02:00
Yuri Kuznetsov 4c0f3413f3 audit cleanup 2024-02-25 11:21:42 +02:00
Yuri Kuznetsov a052c65b89 fix tests 2024-02-24 14:30:57 +02:00
Yuri Kuznetsov 1bcc81018b fix tests 2024-02-24 12:51:21 +02:00
Yuri Kuznetsov 81ba1b8790 link multiple audited 2024-02-24 10:42:36 +02:00
Yuri Kuznetsov ed946a532e user teams field fix 2024-02-24 10:36:27 +02:00
Yuri Kuznetsov db34e75d1f audited fields 2024-02-24 10:34:49 +02:00
Yuri Kuznetsov bcb588b968 css fix 2024-02-23 17:20:37 +02:00
Yuri Kuznetsov 474e787234 ref 2024-02-23 16:18:21 +02:00
Yuri Kuznetsov 691c62f65d user stream preview size small 2024-02-23 16:17:38 +02:00
Yuri Kuznetsov a3a4d7bf36 fix tests 2024-02-23 16:00:33 +02:00
Yuri Kuznetsov 0bcb9acb02 category entity type metadata params 2024-02-23 11:37:18 +02:00
Yuri Kuznetsov e148b16882 target list ref 2024-02-23 11:05:57 +02:00
Yuri Kuznetsov 3a764fae00 target list opt out ref 2024-02-23 10:43:33 +02:00
Yuri Kuznetsov f46c2d6079 stream followers find refactoring 2024-02-23 10:23:26 +02:00
Yuri Kuznetsov 1847132ded cleanup 2024-02-23 10:14:03 +02:00
Yuri Kuznetsov 5765247f9e followers record service 2024-02-23 10:03:13 +02:00
Yuri Kuznetsov 0ef6c7ed55 cleanup 2024-02-22 22:49:11 +02:00
Yuri Kuznetsov e2e6398026 ref 2024-02-22 22:39:14 +02:00
Yuri Kuznetsov 9cd19228a3 cleanup 2024-02-22 22:37:13 +02:00
Yuri Kuznetsov 4268794990 cleanup 2024-02-22 22:35:17 +02:00
Yuri Kuznetsov de4dcaaecb list rebuild-category-paths 2024-02-22 21:11:40 +02:00
Yuri Kuznetsov b7b1e3056a ref 2024-02-22 20:03:30 +02:00
Yuri Kuznetsov 458a5250f4 recordDefs relationship countDisabled 2024-02-22 20:01:29 +02:00
Yuri Kuznetsov 5cf3856b6a suppress insp 2024-02-22 19:54:47 +02:00
Yuri Kuznetsov 8ddd139111 ref, cleanup 2024-02-22 19:51:26 +02:00
Yuri Kuznetsov 952b94d6fe forceSelectAllAttributes 2024-02-22 19:46:23 +02:00
Yuri Kuznetsov 42e68ecd63 suppress inspection 2024-02-22 18:53:18 +02:00
Yuri Kuznetsov 41fac7ef9d ref 2024-02-22 18:50:13 +02:00
Yuri Kuznetsov 253736870e ref, mandatoryAttributeList 2024-02-22 18:30:16 +02:00
Yuri Kuznetsov 569ff9a81b ref, mandatoryAttributeList 2024-02-22 18:01:37 +02:00
Yuri Kuznetsov 3b17174431 cleanup 2024-02-22 17:43:18 +02:00
Yuri Kuznetsov 52436afda9 ref, docs 2024-02-22 17:34:30 +02:00
Yuri Kuznetsov 531b4cccca cleanup, docs 2024-02-22 16:54:48 +02:00
Yuri Kuznetsov e06544cc0f cleanup 2024-02-22 16:52:03 +02:00
Yuri Kuznetsov c48f4c065c postgres for update fix 2024-02-22 16:29:11 +02:00
Yuri Kuznetsov c4d7a794ea ref 2024-02-22 15:35:53 +02:00
Yuri Kuznetsov e7c777281d ref 2024-02-22 15:30:22 +02:00
Yuri Kuznetsov f687b1543d ref 2024-02-22 15:17:42 +02:00
Yuri Kuznetsov 869bee454d ref 2024-02-22 14:54:13 +02:00
Yuri Kuznetsov 83bcb9176b ref 2024-02-22 14:13:32 +02:00
Yuri Kuznetsov f72c902b6a ref 2024-02-22 14:04:27 +02:00
Yuri Kuznetsov e3f68e1e98 fix tests 2024-02-22 13:55:43 +02:00
Yuri Kuznetsov b002846f7c force append 2024-02-22 13:30:31 +02:00
Yuri Kuznetsov f55cccd7c6 ref 2024-02-22 13:29:15 +02:00
Yuri Kuznetsov 929badd208 ref 2024-02-22 13:02:02 +02:00
Yuri Kuznetsov d347fe66ca suppress inspection implemented 2024-02-22 12:51:32 +02:00
Yuri Kuznetsov 42921ac205 ref 2024-02-22 12:35:18 +02:00
Yuri Kuznetsov 58304ceded ref 2024-02-22 11:40:05 +02:00
Yuri Kuznetsov 0a7d2055be ref 2024-02-22 11:09:17 +02:00
Yuri Kuznetsov 1edc3ead8d ref 2024-02-22 10:50:11 +02:00
dependabot[bot] 231bd7699a Bump phenx/php-svg-lib from 0.5.1 to 0.5.2
Bumps [phenx/php-svg-lib](https://github.com/PhenX/php-svg-lib) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/PhenX/php-svg-lib/releases)
- [Commits](https://github.com/PhenX/php-svg-lib/compare/0.5.1...0.5.2)

---
updated-dependencies:
- dependency-name: phenx/php-svg-lib
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-22 09:38:54 +02:00
Yuri Kuznetsov 4dee2ebb15 relate API option 2024-02-21 18:12:05 +02:00
Yuri Kuznetsov 16f956bdf8 change record create hook order 2024-02-21 16:56:39 +02:00
Yuri Kuznetsov a20ab17236 record update keep entity dirty for after update hook 2024-02-21 16:54:46 +02:00
Yuri Kuznetsov 68c5fe94bf record after hooks 2024-02-21 16:33:47 +02:00
Yuri Kuznetsov 78f536e7f1 ref & deprecations 2024-02-21 16:00:42 +02:00
Yuri Kuznetsov ee408300fd acceptance status direct access disabled 2024-02-21 15:09:10 +02:00
Yuri Kuznetsov 0f146959e5 link field foreign load 2024-02-21 14:08:30 +02:00
Yuri Kuznetsov 449d6a6fcd update test 2024-02-21 13:35:44 +02:00
Yuri Kuznetsov 63afa92fc1 update related fields 2024-02-21 13:25:16 +02:00
Yuri Kuznetsov 2b786af35b email/phone edit mode opt-out invalid cues 2024-02-21 12:33:11 +02:00
Yuri Kuznetsov 231f498eca ref 2024-02-21 12:10:00 +02:00
Yuri Kuznetsov 046aafd97e ref 2024-02-21 12:07:26 +02:00
Yuri Kuznetsov d376847d12 ref 2024-02-21 11:44:41 +02:00
Yuri Kuznetsov befd48d053 dompdf page size in mm 2024-02-21 11:23:52 +02:00
Yuri Kuznetsov ee294f889c ref 2024-02-20 22:33:35 +02:00
Yuri Kuznetsov 5fddac8c6b dashlet autoRefresh method 2024-02-20 16:35:31 +02:00
Yuri Kuznetsov a3d73848a9 ref 2024-02-20 16:31:56 +02:00
Yuri Kuznetsov b829e25f5a email address select isInvalid = false filter 2024-02-20 15:40:26 +02:00
Yuri Kuznetsov 58a6cc1658 clone 2024-02-20 15:29:34 +02:00
Yuri Kuznetsov f7eef2d3c7 ref 2024-02-20 15:25:38 +02:00
Yuri Kuznetsov b5d007c61c ref 2024-02-20 14:03:41 +02:00
Yuri Kuznetsov 8570b6f768 webhook queue items admin ui 2024-02-20 13:22:03 +02:00
Yuri Kuznetsov b02e2f72b6 webhook ui small impr 2024-02-20 12:25:54 +02:00
Yuri Kuznetsov e546509f53 admin panel change 2024-02-20 12:24:26 +02:00
Yuri Kuznetsov 244864c984 menu action items data hyphen key 2024-02-20 11:50:43 +02:00
Yuri Kuznetsov 33af4be469 frontend route specific controller class 2024-02-19 16:28:43 +02:00
Yuri Kuznetsov 29e3470587 stream websocket fix 2024-02-19 14:53:05 +02:00
Yuri Kuznetsov 2b8e764deb Merge branch 'fix' 2024-02-19 14:48:57 +02:00
Yuri Kuznetsov f503144c62 category performance fix 2024-02-19 14:43:04 +02:00
Yuri Kuznetsov 79f7194429 Merge branch 'fix' 2024-02-19 09:55:18 +02:00
Yuri Kuznetsov 6ac13521e8 lang 2024-02-19 09:55:07 +02:00
Yuri Kuznetsov ca0625f15e select where converters inCategory, isUserFromTeams 2024-02-19 09:43:43 +02:00
Yuri Kuznetsov 88c9fbd153 ref 2024-02-18 15:33:07 +02:00
Yuri Kuznetsov 7f51b3a31e cleanup 2024-02-17 22:20:58 +02:00
Yuri Kuznetsov ac0b08fdb7 list sticky bar helper 2024-02-17 22:20:06 +02:00
Yuri Kuznetsov de0e5ea72d copy primary filter hash to clipboard 2024-02-17 19:47:11 +02:00
Yuri Kuznetsov e06d60a3c3 color fix 2024-02-17 19:27:58 +02:00
Yuri Kuznetsov 128bfb09ea fix test 2024-02-17 12:56:59 +02:00
Yuri Kuznetsov 7f0283caf8 list with category support primary filter 2024-02-16 17:55:35 +02:00
Yuri Kuznetsov a00cf5e696 entity manager show primary filters 2024-02-16 16:39:35 +02:00
Yuri Kuznetsov 65a9a0db41 detail view as obj read only 2024-02-16 16:36:40 +02:00
Yuri Kuznetsov 8ac98067de jsdocs 2024-02-16 15:02:22 +02:00
Yuri Kuznetsov f725dc395e ref 2024-02-16 14:50:33 +02:00
Yuri Kuznetsov 6e8368c7c5 fix tests 2024-02-16 13:35:39 +02:00
Yuri Kuznetsov ddd9425463 list view primaryFilter uri param 2024-02-16 13:30:49 +02:00
Yuri Kuznetsov 4739cd1510 docs 2024-02-16 12:03:40 +02:00
Yuri Kuznetsov 5a3c977759 jsdoc 2024-02-16 10:44:21 +02:00
Yuri Kuznetsov 85889ab99d fix deprecation 2024-02-16 10:09:05 +02:00
Yuri Kuznetsov a9dc3a62fb remove preferences link 2024-02-16 09:47:05 +02:00
Yuri Kuznetsov c2ddf89747 fix test 2024-02-15 16:30:19 +02:00
Yuri Kuznetsov 9f743b718a custom prefix 2024-02-15 16:20:51 +02:00
Yuri Kuznetsov 0db4a9c672 address fields max length decrease 2024-02-15 14:03:06 +02:00
Yuri Kuznetsov 2dbdb0da60 ref 2024-02-15 13:37:38 +02:00
Yuri Kuznetsov ba59cb1010 enum max length 100 2024-02-15 13:30:34 +02:00
Yuri Kuznetsov ee36f73ec2 enum 100 max length by default 2024-02-15 13:28:25 +02:00
Yuri Kuznetsov abcbabfbd8 varchar max length 100 by default 2024-02-15 12:57:54 +02:00
Yuri Kuznetsov 5be76d2657 triggerSelectOnValidInput by default 2024-02-15 12:14:06 +02:00
Yuri Kuznetsov ddb92f66e7 search ui autocomplete 2024-02-15 12:11:41 +02:00
Yuri Kuznetsov f52759cd00 autocomplete fixes 2024-02-15 12:09:35 +02:00
Yuri Kuznetsov b0d9d7779f ui autocomplete usage 2024-02-15 10:44:42 +02:00
Yuri Kuznetsov 8cd6912fee fix record prev/next if no total 2024-02-15 09:19:44 +02:00
Yuri Kuznetsov bc58ca647d disabled button fix 2024-02-15 09:09:35 +02:00
Yuri Kuznetsov 1456569cf6 ui autocomplete changes 2024-02-14 21:36:20 +02:00
Yuri Kuznetsov e1e7d3d093 link multiple column ui autocomplete usage 2024-02-14 20:57:18 +02:00
Yuri Kuznetsov 495cd0f259 autocomplete email addresses 2024-02-14 16:40:25 +02:00
Yuri Kuznetsov f4371efa1c ui autocomplete 2024-02-14 16:17:07 +02:00
Yuri Kuznetsov d83b05e030 auth log layout 2024-02-14 13:06:38 +02:00
Yuri Kuznetsov bf8267c2a8 move 2024-02-14 13:04:41 +02:00
Yuri Kuznetsov 3bad29ed6d use force settings 2024-02-14 13:01:01 +02:00
Yuri Kuznetsov cbae4d89e4 force and disable list view settings 2024-02-14 12:56:42 +02:00
Yuri Kuznetsov 18f89aebbb update cron-expression 2024-02-14 12:47:33 +02:00
Yuri Kuznetsov c2b4caf723 menu item disabled 2024-02-14 12:14:42 +02:00
Yuri Kuznetsov c5f3a9e366 Merge branch 'fix' 2024-02-14 11:54:19 +02:00
Yuri Kuznetsov da826eace1 fix menu items disable 2024-02-14 11:48:57 +02:00
Yuri Kuznetsov 54de942e7f modal clear on close 2024-02-14 11:17:14 +02:00
Yuri Kuznetsov 0093bf9b79 fix 2024-02-14 11:08:43 +02:00
Yuri Kuznetsov 8e9dcb5d62 role add field ignore fields 2024-02-14 11:04:52 +02:00
Yuri Kuznetsov 38e909dfca role add field focus 2024-02-14 10:57:27 +02:00
Yuri Kuznetsov 6b0276dc22 role: add multiple fields 2024-02-14 10:52:22 +02:00
Yuri Kuznetsov 0d19dfa7b8 role add field quick search 2024-02-13 19:51:18 +02:00
Yuri Kuznetsov 13052b2969 log failed queries 2024-02-13 16:52:36 +02:00
Yuri Kuznetsov 1f9de30ff0 import step2 route change 2024-02-13 15:15:58 +02:00
Yuri Kuznetsov 5b708c1d8d import underscore match 2024-02-13 15:03:47 +02:00
Yuri Kuznetsov ed92d68317 Merge branch 'fix' 2024-02-13 14:38:41 +02:00
Yuri Kuznetsov 2a3bbf13a5 import preivew fix 2024-02-13 14:38:23 +02:00
Yuri Kuznetsov 330fccad25 email address insert fix 2024-02-13 14:06:39 +02:00
Rabii Brahimi 14243b5ef7 Remove Duplication 2024-02-13 14:06:31 +02:00
Yuri Kuznetsov 9c702814b2 onlyMy filter 2024-02-13 13:05:54 +02:00
Yuri Kuznetsov ba7acc5443 ref 2024-02-13 12:47:18 +02:00
Yuri Kuznetsov 10557ca1ec load accountIsInactive on list view 2024-02-13 12:19:04 +02:00
Rabii Brahimi 2085701516 Update Layout index.tpl 2024-02-13 11:14:14 +02:00
Yuri Kuznetsov 88ba56618e formula today honot default timezone 2024-02-13 11:00:37 +02:00
Yuri Kuznetsov c35545c209 label manager quick search 2024-02-12 15:05:11 +02:00
Yuri Kuznetsov 585a859340 ref 2024-02-12 14:10:41 +02:00
Yuri Kuznetsov cf214de06c Merge branch 'fix' 2024-02-12 12:08:37 +02:00
Yuri Kuznetsov 2c83edf691 email skip index if one 2024-02-12 12:01:47 +02:00
Yuri Kuznetsov 2f14c49215 ref 2024-02-12 11:40:40 +02:00
Yuri Kuznetsov 72e68e1a6f ref 2024-02-12 10:33:39 +02:00
Yuri Kuznetsov 5ff535cab1 ref 2024-02-12 10:16:17 +02:00
Yuri Kuznetsov 90e6acf8b9 lang fix 2024-02-12 10:15:04 +02:00
Yuri Kuznetsov e5e29101fd web socket wait interval 2024-02-11 16:46:19 +02:00
Yuri Kuznetsov 47735aaa0b email attachment show alert 2024-02-11 10:08:42 +02:00
Yuri Kuznetsov 43eb2f8d36 date picker assumeNearbyYear 2024-02-10 22:33:39 +02:00
Yuri Kuznetsov 1d3c0074d4 ref 2024-02-10 19:14:03 +02:00
Yuri Kuznetsov e8f07e0765 supresses 2024-02-10 18:35:55 +02:00
Yuri Kuznetsov 54216709b2 text field audited 2024-02-10 15:30:34 +02:00
Yuri Kuznetsov 9f6f034f97 cs 2024-02-10 14:38:23 +02:00
Yuri Kuznetsov e0c74aa2ad fix docs 2024-02-10 11:37:41 +02:00
Yuri Kuznetsov c083e1b97d clear attribute function 2024-02-10 09:43:36 +02:00
Yuri Kuznetsov c4078ed8f1 fix doc 2024-02-10 09:10:59 +02:00
Yuri Kuznetsov 4001e40613 rename 2024-02-09 16:03:09 +02:00
Yuri Kuznetsov a19deac279 error msg 2024-02-09 14:30:54 +02:00
Yuri Kuznetsov f655e543b1 websocket ping 2024-02-09 14:15:20 +02:00
Yuri Kuznetsov 5031e7f430 fix 2024-02-09 13:28:16 +02:00
Yuri Kuznetsov 7f75ee6ed8 websocket re-subscribe on connection restore 2024-02-09 12:33:54 +02:00
Yuri Kuznetsov bde2e0284a ws keep alive 2024-02-09 10:24:09 +02:00
Yuri Kuznetsov 026826fc84 preferences tab fields translations 2024-02-08 17:40:23 +02:00
Yuri Kuznetsov c5f5d88f75 style enum color 2024-02-08 17:15:08 +02:00
Yuri Kuznetsov 3ae4478a70 schema fix 2024-02-08 16:01:55 +02:00
Yuri Kuznetsov 246ece8e3d audit log 2024-02-08 15:53:01 +02:00
Yuri Kuznetsov 2681acebc6 fix schema 2024-02-08 15:50:27 +02:00
Yuri Kuznetsov a611301c5a css fix 2024-02-08 13:29:22 +02:00
Yuri Kuznetsov 3f2ffb851f ref 2024-02-08 10:24:08 +02:00
Yuri Kuznetsov daa37961e4 tryGetForeignEntityType 2024-02-08 09:59:12 +02:00
Yuri Kuznetsov 66b336279a layout css change 2024-02-07 16:47:32 +02:00
Yuri Kuznetsov c99804621d remove labels from tempalte detail layouts 2024-02-07 16:44:46 +02:00
Yuri Kuznetsov 2ebbc942d3 RDBRelation generic 2024-02-07 15:56:56 +02:00
Yuri Kuznetsov b81d6962ae cs 2024-02-07 15:43:22 +02:00
Yuri Kuznetsov dc89f75678 translatable panel note 2024-02-07 14:12:46 +02:00
Yuri Kuznetsov 1ccd70b3fc panel notes 2024-02-07 13:28:20 +02:00
Yuri Kuznetsov 0b3002979e alert content style 2024-02-07 13:26:01 +02:00
Yuri Kuznetsov 25dfa76807 merge 2024-02-07 11:58:36 +02:00
Yuri Kuznetsov db26c579b0 8.1.4 2024-02-07 11:35:44 +02:00
Yuri Kuznetsov 623b26f60f email phone fields save fix 2024-02-07 09:42:59 +02:00
Yuri Kuznetsov fec6bf8ee0 mass email link checker 2024-02-07 09:01:56 +02:00
Yuri Kuznetsov 46760ccfba calendar suppress loading alert 2024-02-06 15:44:57 +02:00
Yuri Kuznetsov 214a32c472 preferences: hide panel 2024-02-06 13:34:52 +02:00
Yuri Kuznetsov ee6a4607d8 fix 2024-02-06 13:29:23 +02:00
Yuri Kuznetsov 5a3b3ab2e8 url tab 2024-02-06 13:20:50 +02:00
Yuri Kuznetsov 5d5000fbbd url not optional protocol 2024-02-06 13:18:43 +02:00
Yuri Kuznetsov 554668f1bd url reg exp 2024-02-06 13:18:24 +02:00
Yuri Kuznetsov 7ff8b1ecee fix validation msg 2024-02-06 10:41:40 +02:00
Yuri Kuznetsov 0858b845fb ref 2024-02-06 10:33:56 +02:00
Yuri Kuznetsov d480113b90 cs 2024-02-06 10:24:16 +02:00
Yuri Kuznetsov 8f8370b5bc jsdoc fix 2024-02-06 10:24:11 +02:00
Yuri Kuznetsov cfcc1b06f9 calendar scroll hour 2024-02-06 09:57:03 +02:00
Yuri Kuznetsov 7dbb842085 field generics 2024-02-06 09:36:55 +02:00
Yuri Kuznetsov 76bb7d3e49 cs 2024-02-06 09:15:47 +02:00
Yuri Kuznetsov 3d79861ffb options 0 fix 2024-02-06 09:11:43 +02:00
Yuri Kuznetsov 236942c902 preferences layout 2024-02-06 09:00:48 +02:00
Yuri Kuznetsov 3efe0a0fe0 mailto: cut body length 2024-02-06 08:30:21 +02:00
Yuri Kuznetsov 9d933a5d95 mailto error fix 2024-02-06 08:22:42 +02:00
Yuri Kuznetsov 24eb879352 ref mailto 2024-02-05 21:58:56 +02:00
Yuri Kuznetsov f0141cd725 remove phpstan ignore 2024-02-05 19:41:11 +02:00
Yuri Kuznetsov 9a82bc7c5a cs 2024-02-05 19:38:05 +02:00
Yuri Kuznetsov b42bfefef9 signature codeview 2024-02-05 18:20:51 +02:00
Yuri Kuznetsov 47e881af60 ref 2024-02-05 17:44:42 +02:00
Yuri Kuznetsov 442284fee2 enum label in link mode 2024-02-05 17:28:18 +02:00
Yuri Kuznetsov 49a6454999 middle dot usage 2024-02-05 17:05:47 +02:00
Yuri Kuznetsov a95e52a10c checkFilePermissions command 2024-02-05 16:20:09 +02:00
Yuri Kuznetsov b2f1f00875 ref 2024-02-05 16:19:07 +02:00
Yuri Kuznetsov 91b50cdcc4 typo 2024-02-05 16:07:51 +02:00
Yuri Kuznetsov 078bd7c80e ref 2024-02-05 15:58:48 +02:00
Yuri Kuznetsov 7641918224 image entryPoint ref 2024-02-05 15:24:41 +02:00
Yuri Kuznetsov a90198d50e composer email pass accountId 2024-02-05 14:53:54 +02:00
Yuri Kuznetsov dcff1748b7 email full form fix dialog issue 2024-02-05 14:13:23 +02:00
Yuri Kuznetsov 5f903d8ce2 email/phone not null performance 2024-02-05 14:10:49 +02:00
Yuri Kuznetsov 92fdea37d1 fix 2024-02-05 13:40:36 +02:00
Yuri Kuznetsov 7e59fafb3f cs ref 2024-02-05 13:17:52 +02:00
Yuri Kuznetsov ae58224f95 email search null 2024-02-05 13:14:15 +02:00
Yuri Kuznetsov 857c5eee6f ref 2024-02-05 13:14:07 +02:00
Yuri Kuznetsov adb7f876aa email address select 2024-02-05 12:41:24 +02:00
Yuri Kuznetsov 6b282c378a jsdoc fix 2024-02-05 09:51:22 +02:00
Yuri Kuznetsov aaca0fc47b add title 2024-02-05 09:38:48 +02:00
Yuri Kuznetsov 4681493b58 suppress inspection 2024-02-05 09:36:03 +02:00
Yuri Kuznetsov 37b0c15c1a ref 2024-02-04 14:57:28 +02:00
Yuri Kuznetsov f7c29ef4e6 wysiwyg disable dnd 2024-02-03 18:11:39 +02:00
Yuri Kuznetsov 15c08c347d renaming 2024-02-03 16:48:20 +02:00
Yuri Kuznetsov 4bf5f85644 email address do not search in portal users 2024-02-03 16:47:20 +02:00
Yuri Kuznetsov dfe09dac0e ref 2024-02-03 16:44:38 +02:00
Yuri Kuznetsov ce26ecc8c1 calendar: fix loading notify 2024-02-03 15:07:23 +02:00
Yuri Kuznetsov 356971cc66 calendar scroll hour 8 if duration is 15 2024-02-03 15:03:13 +02:00
Yuri Kuznetsov 82413cd3fd calendar slot duration 1h 2024-02-03 15:00:22 +02:00
Yuri Kuznetsov f07f3de1bd settings layout 2024-02-03 14:47:07 +02:00
Yuri Kuznetsov 33072072c4 ref cs 2024-02-03 13:52:37 +02:00
Yuri Kuznetsov 94351646b1 add ext-ctype 2024-02-03 13:42:30 +02:00
Yuri Kuznetsov c766465527 dashlet chart no overflow hidden 2024-02-03 12:40:35 +02:00
Yuri Kuznetsov b982ab9daf ref 2024-02-03 10:26:48 +02:00
Yuri Kuznetsov 114682b49f cs 2024-02-03 09:25:24 +02:00
Yuri Kuznetsov 0e5dcb0d40 email queue item fixes 2024-02-02 18:21:12 +02:00
Yuri Kuznetsov 3f491f1a9a Merge branch 'fix' 2024-02-02 16:37:51 +02:00
Yuri Kuznetsov f5a655b9fc markdlow blockquote fix 2024-02-02 16:30:08 +02:00
Yuri Kuznetsov 62494e9c12 ref 2024-02-02 16:19:15 +02:00
Yuri Kuznetsov c88e34fe8f Merge branch 'fix' 2024-02-02 16:09:58 +02:00
Yuri Kuznetsov 1a5abe6363 calendar fix is today 2024-02-02 16:09:34 +02:00
Yuri Kuznetsov 2f3c775d4f calendar: scroll to now 2024-02-02 16:08:01 +02:00
Yuri Kuznetsov 7078efcc20 calendar: do not add 30 mins 2024-02-02 14:54:40 +02:00
Yuri Kuznetsov e73eb87374 preferences ctrl+s 2024-02-02 14:49:34 +02:00
Yuri Kuznetsov 471a209d86 ref 2024-02-02 14:47:44 +02:00
Yuri Kuznetsov fabb88d611 prefernces: calendar slot duration 2024-02-02 14:40:17 +02:00
Yuri Kuznetsov d7d7752868 enum-int/float support null 2024-02-02 14:03:21 +02:00
Yuri Kuznetsov 0fe9041272 preferences layout change 2024-02-02 13:47:24 +02:00
Yuri Kuznetsov 860680aaa1 fix formula function return type 2024-02-01 18:10:24 +02:00
Yuri Kuznetsov 1640bdf172 Merge branch 'fix' 2024-02-01 16:22:12 +02:00
Yuri Kuznetsov aced5fcab9 8.1.3 2024-02-01 16:14:48 +02:00
Yuri Kuznetsov f786690f1e markdown fix 2024-02-01 15:58:10 +02:00
Yuri Kuznetsov 9a1495199b Merge branch 'fix' 2024-02-01 11:52:00 +02:00
Yuri Kuznetsov 15d264acce merge 2024-02-01 11:01:13 +02:00
Yuri Kuznetsov 959e8d3acc Merge branch 'fix' 2024-01-31 15:24:26 +02:00
Yuri Kuznetsov b2edf702ce role: store in memory 2024-01-31 12:58:46 +02:00
Yuri Kuznetsov 4837474bb0 role table dropdown hiding fix 2024-01-31 12:30:22 +02:00
Yuri Kuznetsov b052eacba7 role table do not show not-set as no 2024-01-31 12:27:59 +02:00
Yuri Kuznetsov 0129305c5f portal role table fix 2024-01-31 12:17:15 +02:00
Yuri Kuznetsov 5a725b79c9 entity manager list module 2024-01-31 12:10:06 +02:00
Yuri Kuznetsov c4f706e918 role table: group by module 2024-01-31 11:58:22 +02:00
Yuri Kuznetsov 9673f09c9f role table style fix 2024-01-31 11:04:40 +02:00
Yuri Kuznetsov 6a84ccbd4e Merge branch 'fix' 2024-01-30 18:11:32 +02:00
Yuri Kuznetsov 14587ee65c wysiwyg: hide attach button in inlide edit 2024-01-30 16:39:12 +02:00
Yuri Kuznetsov ef2d129699 ref 2024-01-30 16:34:35 +02:00
Yuri Kuznetsov 580c0bef0a ref 2024-01-30 14:29:43 +02:00
Yuri Kuznetsov eef7fef69b cs 2024-01-30 14:16:52 +02:00
Yuri Kuznetsov 4d1776f9ff date filter between range 2024-01-30 13:20:27 +02:00
Yuri Kuznetsov f92c21c9c0 ref 2024-01-30 12:40:10 +02:00
Yuri Kuznetsov fac8730ddd cs 2024-01-30 12:36:25 +02:00
Yuri Kuznetsov a3fe58f61b update phpstan 2024-01-30 11:40:31 +02:00
Yuri Kuznetsov 696a25fe82 update symfony libs 2024-01-30 11:01:05 +02:00
Yuri Kuznetsov 04b28dc88f pass date: true 2024-01-29 18:15:27 +02:00
Yuri Kuznetsov abbffb9b15 date filters time zone 2024-01-29 16:45:15 +02:00
Yuri Kuznetsov 1cd914e5a7 ref 2024-01-29 10:59:52 +02:00
Yuri Kuznetsov 6b7bf55acb cs 2024-01-29 10:56:59 +02:00
Yuri Kuznetsov eddcb797e4 catching exception 2024-01-29 10:42:04 +02:00
Yuri Kuznetsov 3014e9253b phpdocs, cs 2024-01-29 10:19:20 +02:00
Yuri Kuznetsov 016489ffba ref, cs 2024-01-29 10:16:25 +02:00
Yuri Kuznetsov 96ea9e225c date-time transformer default time zone 2024-01-29 10:12:14 +02:00
Yuri Kuznetsov c4cfc204e7 date time transformer interface 2024-01-28 17:08:48 +02:00
Yuri Kuznetsov 00f5433518 fix role validation 2024-01-28 12:45:22 +02:00
Yuri Kuznetsov 3077589cff todo 2024-01-27 19:58:15 +02:00
Yuri Kuznetsov 3e3258a4b7 cs ref 2024-01-27 19:55:43 +02:00
Yuri Kuznetsov dee0f9937e role create update test 2024-01-27 19:53:23 +02:00
Yuri Kuznetsov 1138a4deb1 role, validate field exists 2024-01-27 19:53:14 +02:00
Yuri Kuznetsov 720b14b804 roles ui ref 2024-01-27 18:38:53 +02:00
Yuri Kuznetsov 196f328312 role not modified fix 2024-01-27 18:38:53 +02:00
Yuri Kuznetsov 1eb7215162 ref 2024-01-27 18:38:53 +02:00
Yuri Kuznetsov 52762cc738 roles validation 2024-01-27 18:38:53 +02:00
Yuri Kuznetsov c3c38e3510 Update README.md 2024-01-27 14:12:09 +02:00
Yuri Kuznetsov 5dcb112621 Update CONTRIBUTING.md 2024-01-27 14:09:38 +02:00
Yuri Kuznetsov 3a9e5fc0cd Update README.md 2024-01-27 14:08:06 +02:00
Yuri Kuznetsov a789fabe2e Update CONTRIBUTING.md 2024-01-27 14:06:53 +02:00
Yuri Kuznetsov 6c8f5e8a16 Update README.md 2024-01-27 13:54:54 +02:00
Yuri Kuznetsov cf08bbdd00 disalbe field level for template 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 80a7765fdd role permissions style 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 119948938e fix role add field 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov b9563cfdc0 acl field level disabled param 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 97e9786fa6 translation 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 60a4541e24 role ui impr 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 52ae19b896 style fix 2024-01-27 12:30:21 +02:00
Yuri Kuznetsov 8973e976ca Update CONTRIBUTING.md 2024-01-26 22:58:50 +02:00
Yuri Kuznetsov 5ebee4bec7 roles quick search 2024-01-26 15:23:32 +02:00
Yuri Kuznetsov c34b2acf9b cs 2024-01-26 15:07:37 +02:00
Yuri Kuznetsov cd400e5090 ref 2024-01-26 14:44:14 +02:00
Yuri Kuznetsov b433538ab2 link-parent: autocomplete on empty input 2024-01-26 14:20:26 +02:00
Yuri Kuznetsov 0a8cc398fe ref cs 2024-01-26 13:42:25 +02:00
Yuri Kuznetsov a8e37d9486 oidc: nonce and state longer 2024-01-26 13:36:56 +02:00
Yuri Kuznetsov 288b017355 sanitizers 2024-01-26 13:02:49 +02:00
Yuri Kuznetsov 91745580d8 module route order 2024-01-26 11:34:33 +02:00
Yuri Kuznetsov d0d652dba9 route overriding 2024-01-26 11:12:17 +02:00
Yuri Kuznetsov 96f9f89b7b cs 2024-01-26 11:11:52 +02:00
Yuri Kuznetsov 2f093011a2 ref and cleanup 2024-01-26 11:06:52 +02:00
Yuri Kuznetsov cfe55c1975 task list view layout change 2024-01-26 10:34:50 +02:00
Yuri Kuznetsov 42dc0d754f schema 2024-01-25 17:28:11 +02:00
Yuri Kuznetsov 1623257b17 auth log method full list 2024-01-25 16:28:02 +02:00
Yuri Kuznetsov 3f27256589 auth log more fields 2024-01-25 16:21:39 +02:00
Yuri Kuznetsov 52c34b6d35 Merge branch 'fix' 2024-01-25 16:13:11 +02:00
Yuri Kuznetsov 485bcfc039 alert style 2024-01-25 11:48:53 +02:00
Yuri Kuznetsov 1cbcf7048c color fix 2024-01-25 11:41:08 +02:00
Yuri Kuznetsov 4d90aec5a6 orm: clear query composer seed cache on metadata update 2024-01-25 11:28:33 +02:00
Yuri Kuznetsov 85b2a72624 msg change 2024-01-24 16:36:28 +02:00
Yuri Kuznetsov acd5d78d30 import: relate by foreign field 2024-01-24 16:23:57 +02:00
Yuri Kuznetsov c81e4a8194 currency not modified fix 2024-01-24 15:23:06 +02:00
Yuri Kuznetsov 81fcd57e3a ref 2024-01-24 14:38:06 +02:00
Yuri Kuznetsov 6dd940cfab import ref 2024-01-24 14:26:15 +02:00
Yuri Kuznetsov a1bdb6c308 send test email error message translation 2024-01-24 12:19:21 +02:00
Yuri Kuznetsov 525397e64d config override 2024-01-24 11:36:32 +02:00
Yuri Kuznetsov e4af67aa68 ref 2024-01-24 10:44:24 +02:00
Yuri Kuznetsov 2d7d8812fd Merge branch 'fix' 2024-01-23 19:59:37 +02:00
Yuri Kuznetsov 726ecefd55 array focus on main element 2024-01-23 14:10:47 +02:00
Yuri Kuznetsov 69fbf9d1ad not read emails bolder font 2024-01-23 13:43:32 +02:00
Yuri Kuznetsov 38e89f4e4d table border changes wysiwyg 2024-01-23 13:20:23 +02:00
Yuri Kuznetsov b3ff273b7c ref 2024-01-23 11:29:49 +02:00
Yuri Kuznetsov fb0be72b56 clearnup 2024-01-23 11:20:02 +02:00
Yuri Kuznetsov 339fbffbb7 wysiwyg param to disable code editor 2024-01-23 11:11:06 +02:00
Yuri Kuznetsov 5c308bb60c wysiwyg: remove beautifier 2024-01-23 11:02:50 +02:00
Yuri Kuznetsov 514fe6e98d wysiwyg: disable prettifyHtml 2024-01-23 10:53:02 +02:00
Yuri Kuznetsov a80d7fddba try catch gotoline 2024-01-23 10:38:07 +02:00
Yuri Kuznetsov ec11a89496 pdf template: add created at filter 2024-01-23 10:23:47 +02:00
Yuri Kuznetsov 61aa5f907d fix 2024-01-22 21:21:40 +02:00
Yuri Kuznetsov ac7ba173cd htmlizer fix iterate and test 2024-01-22 20:12:29 +02:00
Yuri Kuznetsov fe7a764935 ref 2024-01-22 19:51:00 +02:00
Yuri Kuznetsov c1190d348b Merge branch 'master' into f/iterate 2024-01-22 16:40:47 +02:00
Yuri Kuznetsov a81c8518b8 wysiwyg code view improvements 2024-01-22 15:56:26 +02:00
Yuri Kuznetsov d7f533118a naming 2024-01-22 11:18:10 +02:00
Yuri Kuznetsov 252fdc4208 cs 2024-01-22 09:22:40 +02:00
Yuri Kuznetsov c1d28655da template iterate 2024-01-21 18:26:39 +02:00
Yuri Kuznetsov 77c2e8abc4 Merge branch 'fix' 2024-01-20 21:37:25 +02:00
Yuri Kuznetsov b6179463b1 style completion 2024-01-20 20:11:05 +02:00
Yuri Kuznetsov 99bd7ae437 pdf template style 2024-01-20 19:37:15 +02:00
Yuri Kuznetsov e968388ba7 wysiwyg codeview min height 2024-01-20 18:56:59 +02:00
Yuri Kuznetsov 60be576f9d Merge branch 'fix' 2024-01-18 15:07:38 +02:00
Yuri Kuznetsov 47249776a0 ref 2024-01-17 13:05:00 +02:00
Yuri Kuznetsov c8f4fa437d ref 2024-01-17 12:41:55 +02:00
Yuri Kuznetsov 09c1b575a7 ref 2024-01-17 12:32:17 +02:00
Yuri Kuznetsov 2437b0d901 Merge branch 'fix' 2024-01-17 10:28:38 +02:00
Yuri Kuznetsov 0a33ad6542 remove CURLOPT_BINARYTRANSFER usage 2024-01-16 17:57:55 +02:00
Yuri Kuznetsov 7ee626edf8 select field handler pass model 2024-01-16 17:29:29 +02:00
Yuri Kuznetsov 600b58be75 select handler order 2024-01-16 17:27:43 +02:00
Yuri Kuznetsov 5601704ac1 Merge branch 'fix' 2024-01-16 17:01:18 +02:00
Yuri Kuznetsov 4eb6386b6e stream super parent performance impr 2024-01-16 10:12:33 +02:00
Yuri Kuznetsov 9c78970712 layoutIgnoreList 2024-01-16 09:50:05 +02:00
Yuri Kuznetsov e465e25adb syle fix 2024-01-15 17:04:02 +02:00
Yuri Kuznetsov 4d735e9e26 user stream activity 2024-01-15 16:19:53 +02:00
Yuri Kuznetsov 28bc720bbc rename 2024-01-15 16:19:21 +02:00
Yuri Kuznetsov 0fc170e1c3 ref 2024-01-15 15:42:47 +02:00
Yuri Kuznetsov 012d98d303 ref 2024-01-15 15:39:39 +02:00
Yuri Kuznetsov 180bc99726 Merge branch 'fix' 2024-01-15 13:29:24 +02:00
Yuri Kuznetsov e537d64c5b user bottom panels layout 2024-01-15 13:09:28 +02:00
Yuri Kuznetsov 03de09c836 user stream panel in layout manager 2024-01-15 12:03:15 +02:00
Yuri Kuznetsov 9dc309390a cs 2024-01-15 12:02:32 +02:00
Yuri Kuznetsov 8280f31180 global stream optimization 2024-01-15 11:44:37 +02:00
Yuri Kuznetsov 61a1227f87 user stream ref, add index 2024-01-15 11:18:06 +02:00
Yuri Kuznetsov 02c37924aa created complex impr 2024-01-15 10:09:25 +02:00
Yuri Kuznetsov ddc4baf5e2 Merge branch 'fix' 2024-01-15 09:58:34 +02:00
Yuri Kuznetsov 63f975516a note fix 2024-01-14 18:53:27 +02:00
Yuri Kuznetsov 9160c8319d global stream optimization 2024-01-14 18:47:37 +02:00
Yuri Kuznetsov d268b0335e global stream 2024-01-14 14:08:43 +02:00
Yuri Kuznetsov 6b355c645c Merge branch 'fix' 2024-01-14 11:14:50 +02:00
Yuri Kuznetsov 951e981d8e stream posts optimization 2024-01-13 15:35:26 +02:00
Yuri Kuznetsov c4c301d363 Merge branch 'fix' 2024-01-13 12:35:56 +02:00
Yuri Kuznetsov 50821924f2 field validation popover fix 2024-01-13 12:29:20 +02:00
Yuri Kuznetsov 3c01011c28 cs 2024-01-13 12:00:53 +02:00
Yuri Kuznetsov d674176356 stream refresh btn impr 2024-01-13 11:33:01 +02:00
Yuri Kuznetsov 8c60396197 spin animation 2024-01-13 11:32:23 +02:00
Yuri Kuznetsov 4a5b442f35 stream ui changes 2024-01-13 11:12:35 +02:00
Yuri Kuznetsov 80ea398660 add note index type 2024-01-13 11:00:06 +02:00
Yuri Kuznetsov 69aae1abfa comment 2024-01-12 18:03:50 +02:00
Yuri Kuznetsov c528a98820 stream comment and ref 2024-01-12 17:39:48 +02:00
Yuri Kuznetsov 89fa12c6db ref 2024-01-12 17:25:56 +02:00
Yuri Kuznetsov 34598d73a3 ref 2024-01-12 16:55:52 +02:00
Yuri Kuznetsov c507aeae4a ref 2024-01-12 16:35:38 +02:00
Yuri Kuznetsov 356ce3cc42 stream user service ref 2024-01-12 16:02:11 +02:00
Yuri Kuznetsov 560f145324 add indexes 2024-01-12 15:29:31 +02:00
Yuri Kuznetsov 4308fb3f9b ref stream user record service 2024-01-12 15:29:22 +02:00
Yuri Kuznetsov a17b66c6dc revert regexp fix 2024-01-12 14:38:31 +02:00
Yuri Kuznetsov f52a3ba773 ref cs 2024-01-12 13:53:54 +02:00
Yuri Kuznetsov f865338ad0 comment 2024-01-12 13:36:36 +02:00
Yuri Kuznetsov 7221207fe5 user stream query change 2024-01-12 13:16:47 +02:00
Yuri Kuznetsov cbc61b533f ref 2024-01-12 13:11:17 +02:00
Yuri Kuznetsov bdff8767f7 stream user query impr 2024-01-12 13:10:09 +02:00
Yuri Kuznetsov 26f3a9215d user stream service ref 2024-01-12 11:39:52 +02:00
Yuri Kuznetsov 15e34647fb ref stream service 2024-01-12 11:19:46 +02:00
Yuri Kuznetsov d121aa5a9f ref 2024-01-12 10:51:07 +02:00
Yuri Kuznetsov 7d77f754c5 ref 2024-01-12 10:13:45 +02:00
Yuri Kuznetsov 2012c4d161 select ref 2024-01-12 10:05:44 +02:00
Yuri Kuznetsov fed9ede878 Merge branch 'fix' 2024-01-11 19:26:38 +02:00
Yuri Kuznetsov 4b12716961 calendar loading 2024-01-11 17:06:28 +02:00
Yuri Kuznetsov db2f387a77 cs 2024-01-11 17:03:27 +02:00
Yuri Kuznetsov cb972fdf17 entity: assoc array to stdClass 2024-01-11 16:47:37 +02:00
Yuri Kuznetsov 5c34d57012 ref 2024-01-11 16:38:17 +02:00
Yuri Kuznetsov 3906aa245c ref 2024-01-11 16:36:37 +02:00
Yuri Kuznetsov c86daf3a68 cs 2024-01-11 16:23:47 +02:00
Yuri Kuznetsov 9bafd81093 phone/email set data make first primary 2024-01-11 16:11:14 +02:00
Yuri Kuznetsov 5305a27c60 ref 2024-01-11 15:19:41 +02:00
Yuri Kuznetsov 6b30ca05db compose email address for base plus 2024-01-11 13:39:19 +02:00
Yuri Kuznetsov b99c17c7cd cs 2024-01-11 12:35:50 +02:00
Yuri Kuznetsov fabaccd3da case compose email support person 2024-01-11 12:14:28 +02:00
Yuri Kuznetsov 48c0ae93f4 cs 2024-01-11 12:11:19 +02:00
Yuri Kuznetsov 30768071bf cs 2024-01-11 11:50:34 +02:00
Yuri Kuznetsov 68b619e276 ui impr 2024-01-11 11:15:06 +02:00
Yuri Kuznetsov d5a441b0e8 confirm messages 2024-01-11 10:54:55 +02:00
Yuri Kuznetsov a5ed0864be ref 2024-01-11 10:40:56 +02:00
Yuri Kuznetsov 7db9fe46ff confirm msg 2024-01-11 10:24:42 +02:00
Yuri Kuznetsov 99df1bfbaa cs 2024-01-11 10:16:29 +02:00
Yuri Kuznetsov 067a4a95e0 ref 2024-01-11 10:11:08 +02:00
Yuri Kuznetsov 2154a51831 link manager: delete labels 2024-01-11 09:58:42 +02:00
Yuri Kuznetsov f0bc58e289 fix schema 2024-01-10 15:39:53 +02:00
Yuri Kuznetsov 5fd2f15a5d selectOrderBy 2024-01-10 15:33:36 +02:00
Yuri Kuznetsov baeef22f4c Merge branch 'fix' 2024-01-10 15:27:37 +02:00
Yuri Kuznetsov 2f6fef0817 Merge branch 'fix' 2024-01-10 14:20:43 +02:00
Yuri Kuznetsov 9cc97c44d5 fix schema doc 2024-01-08 16:31:22 +02:00
Yuri Kuznetsov 6350e881da cs 2024-01-08 14:14:29 +02:00
Yuri Kuznetsov ec5a799b31 cs 2024-01-08 13:24:07 +02:00
Yuri Kuznetsov 026238b125 reset order on field deletion 2024-01-07 19:42:45 +02:00
Yuri Kuznetsov 46712cd967 ref 2024-01-07 19:36:15 +02:00
Yuri Kuznetsov 273ca15c17 ref 2024-01-07 18:27:37 +02:00
Yuri Kuznetsov 94d8a6039e cs 2024-01-07 18:23:33 +02:00
Yuri Kuznetsov 61fde5ed42 scheduled jobs timezone 2024-01-07 16:19:29 +02:00
Yuri Kuznetsov 27a0127556 cs 2024-01-07 15:41:20 +02:00
Yuri Kuznetsov 585fedd824 extension upload size warning 2024-01-07 12:17:39 +02:00
Yuri Kuznetsov 49ea990519 dialog-confirm class by default 2024-01-07 12:16:46 +02:00
Yuri Kuznetsov 31e06538a2 jsdoc fix 2024-01-07 12:11:04 +02:00
Yuri Kuznetsov 22657f030c ref 2024-01-07 11:52:03 +02:00
Yuri Kuznetsov 3805ecca02 cs 2024-01-07 11:13:54 +02:00
Yuri Kuznetsov 31ddef5ddb cs 2024-01-07 11:10:37 +02:00
Yuri Kuznetsov 6b4d3e5bbd cs 2024-01-06 19:41:04 +02:00
Yuri Kuznetsov acaeb46d5f cs, ref 2024-01-06 13:41:11 +02:00
Yuri Kuznetsov ce70c3b62a fix 2024-01-06 13:40:48 +02:00
Yuri Kuznetsov 55ff63ffeb ref 2024-01-06 13:22:05 +02:00
1138 changed files with 36519 additions and 24593 deletions
+13 -14
View File
@@ -1,25 +1,24 @@
## Issues
When reporting a possible bug, provide detail steps so that we will be able
to reproduce the issue. Try not to use phrases like "very big bug",
"huge issue", "useless feature", etc. No need to use exclamation marks as well.
Steps to reproduce should be clear and unambiguous.
Note that we don't provide developer help or any kind of support on GitHub.
For this, please use our [forum](https://forum.espocrm.com).
## Pull Requests
We are open for contributions that are bug fixes and small improvements. If you would like to contribute something that is not a small fix, please reach out to maintainers before submitting your PR (by creating a GitHub issue).
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla).
It's desirable that one PR solves one specific problem. Do not include code style changes to PRs
(unless the main purpose of the PR is a code style fix).
If you would like to contribute something that is not a small fix, it's reasonable to create an issue first
(a bug report or feature request).
Branches:
* *master* the develop branch; new features should be pushed to here;
* *fix* the upcoming maintenance release; small fixes should be pushed to here.
## Issues
We'd appreciate if you prefer posting issues on weekdays rather than weekends.
When reporting a possible bug, please provide detail steps so that we will be able
to reproduce the issue. Please try not to use phrases like "very big bug",
"huge issue", etc. No need to use exclamation marks as well.
Note that we don't provide developer help or any kind of support on GitHub.
For this, please use our [forum](https://forum.espocrm.com).
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: Feature request
about: Suggest an idea for EspoCRM. For high-level features, consider creating feature requests on the forum. For low-level (framework) here on GitHub.
about: Feature requests are not desired at the moment. Need to polish the system. For high-level features, consider creating feature requests on the forum. For low-level (framework) here on GitHub.
title: ''
labels: ''
assignees: ''
+2
View File
@@ -5,6 +5,8 @@
/data/.backup/*
/data/config.php
/data/config-internal.php
/data/config-override.php
/data/config-internal-override.php
/data/tmp/*
/build
/node_modules
+1
View File
@@ -3,5 +3,6 @@
<component name="PhpEntryPointsManager">
<pattern value="\Espo\Controllers\*" member="*Action*" />
<pattern value="\Espo\Modules\*\Controllers\*" member="*Action*" />
<suppressed_annotations>@implements</suppressed_annotations>
</component>
</project>
+1 -7
View File
@@ -16,7 +16,7 @@
"fileMatch": [
"*/Resources/module.json"
],
"url": "./schema/routes.json"
"url": "./schema/module.json"
},
{
"fileMatch": [
@@ -58,12 +58,6 @@
],
"url": "./schema/metadata/dashlets.json"
},
{
"fileMatch": [
"*/metadata//*.json"
],
"url": "./schema/metadata/.json"
},
{
"fileMatch": [
"*/metadata/entityAcl/*.json"
+12 -12
View File
@@ -21,8 +21,8 @@ You can try the CRM on the online [demo](https://www.espocrm.com/demo/).
### Requirements
* PHP 8.1 - 8.3;
* MySQL 5.7 (and later), or MariaDB 10.2 (and later).
* PostgreSQL 15 (and later) (yet experimental, officially supported soon)
* MySQL 5.7 (and later), or MariaDB 10.2 (and later);
* PostgreSQL 15 (and later) (yet experimental, officially supported soon).
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
@@ -49,16 +49,6 @@ See the [developer documentation](https://docs.espocrm.com/development/).
We highly recommend using IDE for development. The backend codebase follows SOLID principles, utilizes interfaces, static typing and generics. We recommend to start learning EspoCRM from the Dependency Injection article in the documentation.
### Contributing
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
Branches:
* *fix* upcoming maintenance release; minor fixes should be pushed to this branch;
* *master* develop branch; new features should be pushed to this branch;
* *stable* last stable release.
### Community & Support
If you have a question regarding some features, need help or customizations, want to get in touch with other EspoCRM users, or add a feature request, please use our [community forum](https://forum.espocrm.com/). We believe that using the forum to ask for help and share experience allows everyone in the community to contribute and use this knowledge later.
@@ -67,6 +57,16 @@ If you have a question regarding some features, need help or customizations, wan
EspoCRM is published under the GNU AGPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).
### Contributing
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). See [contributing guidelines](https://github.com/espocrm/espocrm/blob/master/.github/CONTRIBUTING.md).
Branches:
* *fix* upcoming maintenance release; minor fixes should be pushed to this branch;
* *master* develop branch; new features should be pushed to this branch;
* *stable* last stable release.
### Language
If you want to improve existing translation or add a language that is not available yet, you can contribute on our [POEditor](https://poeditor.com/join/project/gLDKZtUF4i) project. See instructions [here](https://www.espocrm.com/blog/how-to-use-poeditor-to-translate-espocrm/). It may be reasonable to let us know about your intention to join the POEditor project by posting on our forum or via the contact form on our website.
-10
View File
@@ -63,11 +63,6 @@ class Binding implements BindingProcessor
'container'
);
$binder->bindService(
'Espo\\Core\\Container',
'container'
);
$binder->bindService(
'Psr\\Container\\ContainerInterface',
'container'
@@ -148,11 +143,6 @@ class Binding implements BindingProcessor
'recordServiceContainer'
);
$binder->bindService(
'Espo\\Core\\Record\\HookManager',
'recordHookManager'
);
$binder->bindService(
'Espo\\Core\\HookManager',
'hookManager'
@@ -99,10 +99,7 @@ class AccessChecker implements AccessEntityCREDChecker
else if ($this->aclManager->checkEntity($user, $parent)) {
if (
$entity->getTargetField() &&
in_array(
$entity->getTargetField(),
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
)
!$this->aclManager->checkField($user, $parent->getEntityType(), $entity->getTargetField())
) {
return false;
}
@@ -29,6 +29,7 @@
namespace Espo\Classes\Acl\Note;
use Espo\Core\Acl\Table;
use Espo\Entities\Note;
use Espo\Entities\User;
use Espo\ORM\Entity;
@@ -141,6 +142,10 @@ class AccessChecker implements AccessEntityCREDChecker
return in_array($user->getId(), $entity->getLinkMultipleIdList('users'));
}
if ($entity->getTargetType() === Note::TARGET_PORTALS) {
return $this->aclManager->getPermissionLevel($user, 'portal') === Table::LEVEL_YES;
}
return false;
}
@@ -105,10 +105,7 @@ class AccessChecker implements AccessEntityCREDChecker
else if ($this->aclManager->checkEntity($user, $parent)) {
if (
$entity->getTargetField() &&
in_array(
$entity->getTargetField(),
$this->aclManager->getScopeForbiddenFieldList($user, $parent->getEntityType())
)
!$this->aclManager->checkField($user, $parent->getEntityType(), $entity->getTargetField())
) {
return false;
}
+104
View File
@@ -0,0 +1,104 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Cleanup;
use Espo\Core\Cleanup\Cleanup;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Note;
use Espo\ORM\EntityManager;
/**
* @noinspection PhpUnused
*/
class Audit implements Cleanup
{
private const PERIOD = '3 months';
public function __construct(
private Metadata $metadata,
private EntityManager $entityManager,
private Config $config
) {}
public function process(): void
{
if (!$this->config->get('cleanupAudit')) {
return;
}
$entityTypeList = $this->getEntityTypeList();
foreach ($entityTypeList as $scope) {
$this->processEntityType($scope);
}
}
private function processEntityType(string $entityType): void
{
$query = $this->entityManager
->getQueryBuilder()
->delete()
->from(Note::ENTITY_TYPE)
->where([
'parentType' => $entityType,
'createdAt<' => $this->getBefore()->toString(),
'type' => [Note::TYPE_UPDATE, Note::TYPE_STATUS],
])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
/**
* @return string[]
*/
private function getEntityTypeList(): array
{
/** @var string[] $scopeList */
$scopeList = array_keys($this->metadata->get(['scopes']) ?? []);
$scopeList = array_filter($scopeList, function ($item) {
return $this->metadata->get(['scopes', $item, 'entity']) &&
!$this->metadata->get(['scopes', $item, 'stream']);
});
return array_values($scopeList);
}
private function getBefore(): DateTime
{
/** @var string $period */
$period = $this->config->get('cleanupAuditPeriod') ?? self::PERIOD;
return DateTime::createNow()->modify('-' . $period);
}
}
@@ -0,0 +1,66 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\System;
use Espo\Core\Utils\Util;
/**
* @noinspection PhpUnused
*/
class CheckFilePermissions implements Command
{
public function __construct(
private FileManager $fileManager,
private System $system
) {}
public function run(Params $params, IO $io): void
{
$io->writeLine("\nNote: Run this command under the web server user.\n");
$io->writeLine('Writable:');
$io->writeLine('');
foreach ($this->fileManager->getPermissionUtils()->getWritableList() as $path) {
$fullPath = Util::concatPath($this->system->getRootDir(), $path);
$isWritable = $this->fileManager->isWritable($fullPath);
$msg = " " . ($isWritable ? "OK" : "FAIL") . " : $path";
$io->writeLine($msg);
}
}
}
@@ -27,46 +27,58 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\FieldProcessing\LeadCapture;
use Espo\Entities\LeadCapture as LeadCaptureEntity;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\Utils\Util;
use Espo\Entities\LeadCapture;
use Espo\Modules\Crm\Entities\Lead;
use Espo\ORM\Entity;
use Espo\Tools\LeadCapture\Service as LeadCaptureService;
use Espo\Core\Utils\Util;
use Espo\ORM\EntityManager;
/**
* @extends Record<LeadCaptureEntity>
* @implements Loader<LeadCapture>
*/
class LeadCapture extends Record
class ExampleLoader implements Loader
{
/** @var string[] */
protected $readOnlyAttributeList = ['apiKey'];
public function __construct(
private FieldUtil $fieldUtil,
private Config $config,
private EntityManager $entityManager
) {}
/**
* @param LeadCaptureEntity $entity
*/
public function prepareEntityForOutput(Entity $entity)
public function process(Entity $entity, Params $params): void
{
parent::prepareEntityForOutput($entity);
$entity->set('exampleRequestMethod', 'POST');
$entity->set('exampleRequestHeaders', [
'Content-Type: application/json',
]);
$this->processRequestUrl($entity);
$this->processRequestPayload($entity);
}
private function processRequestUrl(LeadCapture $entity): void
{
$apiKey = $entity->getApiKey();
$siteUrl = $this->config->get('siteUrl');
if ($apiKey) {
$requestUrl = $this->config->getSiteUrl() . '/api/v1/LeadCapture/' . $apiKey;
$entity->set('exampleRequestUrl', $requestUrl);
if (!$apiKey) {
return;
}
$fieldUtil = $this->fieldUtil;
$requestUrl = "$siteUrl/api/v1/LeadCapture/$apiKey";
$requestPayload = "```{\n";
$entity->set('exampleRequestUrl', $requestUrl);
}
private function processRequestPayload(LeadCapture $entity): void
{
$requestPayload = "```\n{\n";
$attributeList = [];
@@ -80,7 +92,7 @@ class LeadCapture extends Record
];
foreach ($entity->getFieldList() as $field) {
foreach ($fieldUtil->getActualAttributeList(Lead::ENTITY_TYPE, $field) as $attribute) {
foreach ($this->fieldUtil->getActualAttributeList(Lead::ENTITY_TYPE, $field) as $attribute) {
if (!in_array($attribute, $attributeIgnoreList)) {
$attributeList[] = $attribute;
}
@@ -105,20 +117,8 @@ class LeadCapture extends Record
$requestPayload .= "\n";
}
$requestPayload .= '}```';
$requestPayload .= "}\n```";
$entity->set('exampleRequestPayload', $requestPayload);
}
protected function beforeCreateEntity(Entity $entity, $data)
{
$apiKey = $this->createLeadCaptureService()->generateApiKey();
$entity->set('apiKey', $apiKey);
}
protected function createLeadCaptureService(): LeadCaptureService
{
return $this->injectableFactory->create(LeadCaptureService::class);
}
}
@@ -29,19 +29,18 @@
namespace Espo\Classes\FieldProcessing\Note;
use Espo\ORM\Entity;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Entities\Note;
use Espo\ORM\Entity;
/**
* @implements Loader<Note>
*/
class AttachmentsLoader implements Loader
class AdditionalFieldsLoader implements Loader
{
public function process(Entity $entity, Params $params): void
{
/** @var Note $entity */
$entity->loadAttachments();
$entity->loadAdditionalFields();
}
}
@@ -34,7 +34,6 @@ use Espo\Entities\AuthToken;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl;
use Espo\Core\Acl\Table;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
@@ -44,6 +43,7 @@ use Exception;
/**
* @implements Loader<User>
* @noinspection PhpUnused
*/
class LastAccessLoader implements Loader
{
@@ -58,10 +58,7 @@ class LastAccessLoader implements Loader
public function process(Entity $entity, Params $params): void
{
$forbiddenFieldList = $this->acl
->getScopeForbiddenFieldList($entity->getEntityType(), Table::ACTION_READ);
if (in_array('lastAccess', $forbiddenFieldList)) {
if (!$this->acl->checkField($entity->getEntityType(), 'lastAccess')) {
return;
}
@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class ArrayFromNull implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if ($value !== null) {
return;
}
$data->set($field, []);
}
}
@@ -0,0 +1,62 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class ArrayStringTrim implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if (!is_array($value)) {
return;
}
foreach ($value as $i => $item) {
if (!is_string($item)) {
continue;
}
$value[$i] = trim($item);
}
$data->set($field, $value);
}
}
@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use DateTimeImmutable;
use DateTimeInterface;
use Espo\Core\Field\Date as DateValue;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Exception;
/**
* @noinspection PhpUnused
*/
class Date implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
$value = $data->get($field);
if ($value === null) {
return;
}
try {
DateValue::fromString($value);
return;
}
catch (Exception) {}
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
if ($dateTime === false) {
return;
}
$value = $dateTime->format(DateTimeUtil::SYSTEM_DATE_FORMAT);
$data->set($field, $value);
}
}
@@ -0,0 +1,73 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Espo\Core\Field\DateTime as DateTimeValue;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Exception;
/**
* @noinspection PhpUnused
*/
class Datetime implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
$value = $data->get($field);
if ($value === null) {
return;
}
try {
DateTimeValue::fromString($value);
return;
}
catch (Exception) {}
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
if ($dateTime === false) {
return;
}
$value = $dateTime
->setTimezone(new DateTimeZone('UTC'))
->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
$data->set($field, $value);
}
}
@@ -0,0 +1,72 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use DateTimeImmutable;
use DateTimeInterface;
use Espo\Core\Field\Date;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Exception;
/**
* @noinspection PhpUnused
*/
class DatetimeOptionalDate implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
$attribute = $field . 'Date';
$value = $data->get($attribute);
if ($value === null) {
return;
}
try {
Date::fromString($value);
return;
}
catch (Exception) {}
$dateTime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $value);
if ($dateTime === false) {
return;
}
$value = $dateTime->format(DateTimeUtil::SYSTEM_DATE_FORMAT);
$data->set($attribute, $value);
}
}
@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class EmptyStringToNull implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if (!is_string($value)) {
return;
}
if ($value === '') {
$value = null;
}
$data->set($field, $value);
}
}
@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class StringTrim implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if (!is_string($value)) {
return;
}
$value = trim($value);
if ($value === '') {
$value = null;
}
$data->set($field, $value);
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldValidators\InboundEmail\FetchSince;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\Entities\EmailAccount;
use Espo\Entities\InboundEmail;
use Espo\ORM\Entity;
/**
* @implements Validator<InboundEmail|EmailAccount>
*/
class Required implements Validator
{
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
if (!$entity->isAvailableForFetching()) {
return null;
}
if (!$entity->get('fetchSince')) {
return Failure::create();
}
return null;
}
}
@@ -27,38 +27,36 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
use Espo\ORM\Entity;
use Espo\Core\Exceptions\BadRequest;
namespace Espo\Classes\FieldValidators\ScheduledJob\Scheduling;
use Cron\CronExpression;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\Entities\ScheduledJob;
use Espo\ORM\Entity;
use Exception;
/**
* @extends Record<\Espo\Entities\ScheduledJob>
* @implements Validator<ScheduledJob>
*/
class ScheduledJob extends Record
class Valid implements Validator
{
/** Should not be removed. */
protected bool $findLinkedLogCountQueryDisabled = true;
public function processValidation(Entity $entity, $data)
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
parent::processValidation($entity, $data);
$scheduling = $entity->getScheduling();
$scheduling = $entity->get('scheduling');
if ($scheduling === null) {
return null;
}
try {
$cronExpression = CronExpression::factory($scheduling);
new CronExpression($scheduling);
}
catch (Exception) {
return Failure::create();
}
/** @phpstan-ignore-next-line*/
$cronExpression->getNextRunDate()->format('Y-m-d H:i:s');
}
catch (Exception $e) {
throw new BadRequest("Not valid scheduling expression.");
}
return null;
}
}
@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldValidators\Settings\ThousandSeparator;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\ORM\Entity;
/**
* @implements Validator<Entity>
*/
class Valid implements Validator
{
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
$value = $entity->get($field);
if (!$value) {
return null;
}
if (!is_string($value)) {
return Failure::create();
}
if (preg_match('/^[0-9]$/', $value)) {
return Failure::create();
}
return null;
}
}
@@ -34,24 +34,24 @@ use Espo\Core\Job\JobDataLess;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use DateTime;
/**
* @noinspection PhpUnused
*/
class AuthTokenControl implements JobDataLess
{
private Config $config;
private EntityManager $entityManager;
private const LIMIT = 500;
public function __construct(Config $config, EntityManager $entityManager)
{
$this->config = $config;
$this->entityManager = $entityManager;
}
public function __construct(
private Config $config,
private EntityManager $entityManager
) {}
public function run(): void
{
$authTokenLifetime = $this->config->get('authTokenLifetime');
$authTokenMaxIdleTime = $this->config->get('authTokenMaxIdleTime');
$authTokenLifetime = (int) ($this->config->get('authTokenLifetime', 0) * 60);
$authTokenMaxIdleTime = (int) ($this->config->get('authTokenMaxIdleTime', 0) * 60);
if (!$authTokenLifetime && !$authTokenMaxIdleTime) {
return;
@@ -63,28 +63,23 @@ class AuthTokenControl implements JobDataLess
if ($authTokenLifetime) {
$dt = new DateTime();
$dt->modify("-$authTokenLifetime minutes");
$dt->modify('-' . $authTokenLifetime . ' hours');
$authTokenLifetimeThreshold = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
$whereClause['createdAt<'] = $authTokenLifetimeThreshold;
$whereClause['createdAt<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
}
if ($authTokenMaxIdleTime) {
$dt = new DateTime();
$dt->modify("-$authTokenMaxIdleTime minutes");
$dt->modify('-' . $authTokenMaxIdleTime . ' hours');
$authTokenMaxIdleTimeThreshold = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
$whereClause['lastAccess<'] = $authTokenMaxIdleTimeThreshold;
$whereClause['lastAccess<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
}
$tokenList = $this->entityManager
->getRDBRepository(AuthToken::ENTITY_TYPE)
->sth()
->where($whereClause)
->limit(0, 500)
->limit(0, self::LIMIT)
->find();
foreach ($tokenList as $token) {
@@ -0,0 +1,156 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Attachment;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
use Espo\Entities\Attachment;
use Espo\ORM\EntityManager;
use Espo\Tools\Attachment\AccessChecker;
use Espo\Tools\Attachment\DetailsObtainer;
use Espo\Tools\Attachment\FieldData;
/**
* @noinspection PhpUnused
*/
class CreateInputFilter implements Filter
{
public function __construct(
private EntityManager $entityManager,
private AccessChecker $accessChecker,
private DetailsObtainer $detailsObtainer
) {}
/**
* @throws BadRequest
* @throws Error
* @throws Forbidden
*/
public function filter(Data $data): void
{
$data->clear('parentId');
$data->clear('relatedId');
$contents = $this->handleContents($data);
$relatedEntityType = $this->getRelatedEntityType($data);
$field = $data->get('field');
$role = $data->get('role') ?? Attachment::ROLE_ATTACHMENT;
if (!$relatedEntityType || !$field) {
throw new BadRequest("No `field` and `parentType`.");
}
$fieldData = new FieldData($field, $data->get('parentType'), $data->get('relatedType'));
$this->accessChecker->check($fieldData, $role);
$this->checkMaxSize($contents, $data, $field, $role);
}
private function getRelatedEntityType(Data $data): ?string
{
if ($data->get('parentType') !== null) {
$data->clear('relatedType');
return $data->get('parentType');
}
if ($data->get('relatedType') !== null) {
return $data->get('relatedType');
}
return null;
}
/**
* @throws BadRequest
*/
private function handleContents(Data $data): string
{
$isBeingUploaded = $data->get('isBeingUploaded') ?? false;
$contents = '';
if (!$isBeingUploaded) {
if (!$data->has('file')) {
throw new BadRequest("No file contents.");
}
$file = $data->get('file');
if (!is_string($file)) {
throw new BadRequest("Non-string file contents.");
}
$arr = explode(',', $file);
if (count($arr) < 2) {
throw new BadRequest("Bad file contents.");
}
$contents = base64_decode($arr[1]);
if ($contents === false) {
throw new BadRequest("Could not decode file contents.");
}
}
$data->set('contents', $contents);
return $contents;
}
/**
* @throws BadRequest
*/
private function checkMaxSize(string $contents, Data $data, mixed $field, mixed $role): void
{
$size = mb_strlen($contents, '8bit');
$dummy = $this->entityManager->getRepositoryByClass(Attachment::class)->getNew();
$dummy->set([
'parentType' => $data->get('parentType'),
'relatedType' => $data->get('relatedType'),
'field' => $field,
'role' => $role,
]);
$maxSize = $this->detailsObtainer->getUploadMaxSize($dummy);
if ($maxSize && $size > $maxSize * 1024 * 1024) {
throw new BadRequest("File size should not exceed $maxSize Mb.");
}
}
}
@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Attachment;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
/**
* @noinspection PhpUnused
*/
class UpdateInputFilter implements Filter
{
public function filter(Data $data): void
{
$data->clear('parentId');
$data->clear('parentType');
$data->clear('relatedId');
$data->clear('relatedType');
$data->clear('isBeingUploaded');
$data->clear('storage');
}
}
@@ -27,33 +27,26 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\Record\AuthToken;
use stdClass;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
/**
* @extends Record<\Espo\Entities\AuthToken>
* @noinspection PhpUnused
*/
class AuthToken extends Record
class UpdateInputFilter implements Filter
{
protected $actionHistoryDisabled = true;
public function filterUpdateInput(stdClass $data): void
public function filter(Data $data): void
{
parent::filterUpdateInput($data);
$dataArray = get_object_vars($data);
foreach (array_keys($dataArray) as $attribute) {
foreach ($data->getAttributeList() as $attribute) {
if ($attribute !== 'isActive') {
unset($data->$attribute);
continue;
$data->clear($attribute);
}
}
if ($data->isActive ?? false) {
unset($data->isActive);
if ($data->get('isActive')) {
$data->clear('isActive');
}
}
}
@@ -0,0 +1,71 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\InboundEmail;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
use Espo\Core\Utils\Crypt;
/**
* @noinspection PhpUnused
*/
class PasswordsInputFilter implements Filter
{
public function __construct(
private Crypt $crypt
) {}
/**
* @throws BadRequest
*/
public function filter(Data $data): void
{
$password = $data->get('password');
if ($password !== null) {
if (!is_string($password)) {
throw new BadRequest();
}
$data->set('password', $this->crypt->encrypt($password));
}
$smtpPassword = $data->get('smtpPassword');
if ($smtpPassword !== null) {
if (!is_string($smtpPassword)) {
throw new BadRequest();
}
$data->set('smtpPassword', $this->crypt->encrypt($smtpPassword));
}
}
}
@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Note;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
/**
* @noinspection PhpUnused
*/
class UpdateInputFilter implements Filter
{
public function filter(Data $data): void
{
$data->clear('parentId');
$data->clear('parentType');
$data->clear('targetType');
$data->clear('usersIds');
$data->clear('teamsIds');
$data->clear('portalsIds');
$data->clear('isGlobal');
}
}
@@ -0,0 +1,59 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Portal;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
use Espo\Core\Utils\Config;
use Espo\Entities\User;
/**
* @noinspection PhpUnused
*/
class InputFilter implements Filter
{
public function __construct(
private User $user,
private Config $config
) {}
public function filter(Data $data): void
{
if (!$this->config->get('restrictedMode')) {
return;
}
if ($this->user->isSuperAdmin()) {
return;
}
$data->clear('customUrl');
}
}
@@ -27,47 +27,48 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\Record\User;
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
use Espo\Core\Authentication\Logins\Hmac;
use Espo\Core\Record\Output\Filter;
use Espo\Core\Utils\ApiKey;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Di;
/**
* @extends Record<\Espo\Entities\Role>
* @implements Filter<User>
*/
class Role extends Record implements
Di\DataManagerAware
class OutputFilter implements Filter
{
use Di\DataManagerSetter;
public function __construct(
private User $user,
private ApiKey $apiKey
) {}
protected $forceSelectAllAttributes = true;
public function afterCreateEntity(Entity $entity, $data)
public function filter(Entity $entity): void
{
parent::afterCreateEntity($entity, $data);
$entity->clear('sendAccessInfo');
$this->clearRolesCache();
$this->filterApiUser($entity);
}
public function afterUpdateEntity(Entity $entity, $data)
private function filterApiUser(User $entity): void
{
parent::afterUpdateEntity($entity, $data);
if (!$entity->isApi()) {
return;
}
$this->clearRolesCache();
}
if ($this->user->isAdmin()) {
if ($entity->getAuthMethod() === Hmac::NAME) {
$secretKey = $this->apiKey->getSecretKeyForUserId($entity->getId());
protected function clearRolesCache(): void
{
$this->createAclCacheClearer()->clearForAllInternalUsers();
$entity->set('secretKey', $secretKey);
}
$this->dataManager->updateCacheTimestamp();
}
return;
}
private function createAclCacheClearer(): AclCacheClearer
{
return $this->injectableFactory->create(AclCacheClearer::class);
$entity->clear('apiKey');
$entity->clear('secretKey');
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Webhook;
use Espo\Core\Record\Defaults\DefaultPopulator;
use Espo\Core\Record\Defaults\Populator;
use Espo\Entities\User;
use Espo\Entities\Webhook;
use Espo\ORM\Entity;
/**
* @implements Populator<Webhook>
*/
class DefaultsPopulator implements Populator
{
public function __construct(
private DefaultPopulator $defaultsDefaultsPopulator,
private User $user
) {}
public function populate(Entity $entity): void
{
$this->defaultsDefaultsPopulator->populate($entity);
if ($this->user->isApi()) {
$entity->set('userId', $this->user->getId());
}
}
}
@@ -0,0 +1,51 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\Webhook;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
use Espo\Entities\User;
/**
* @noinspection PhpUnused
*/
class UpdateInputFilter implements Filter
{
public function __construct(
private User $user
) {}
public function filter(Data $data): void
{
if (!$this->user->isAdmin()) {
$data->clear('event');
}
}
}
@@ -27,18 +27,19 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Modules\Crm\Services;
namespace Espo\Classes\RecordHooks\Attachment;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Attachment;
use Espo\ORM\Entity;
class Call extends Meeting
/**
* @implements SaveHook<Attachment>
*/
class AfterCreate implements SaveHook
{
protected function afterUpdateEntity(Entity $entity, $data)
public function process(Entity $entity): void
{
parent::afterUpdateEntity($entity, $data);
if (isset($data->contactsIds) || isset($data->leadsIds)) {
$this->loadAdditionalFields($entity);
}
$entity->clear('contents');
}
}
@@ -0,0 +1,100 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Attachment;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Attachment;
use Espo\ORM\Entity;
use Espo\Tools\Attachment\Checker;
use Espo\Tools\Attachment\DetailsObtainer;
/**
* @implements SaveHook<Attachment>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Config $config,
private Metadata $metadata,
private DetailsObtainer $detailsObtainer,
private Checker $checker
) {}
public function process(Entity $entity): void
{
$this->processStorage($entity);
$this->processRole($entity);
$this->processSize($entity);
$this->checker->checkType($entity);
}
private function processStorage(Attachment $entity): void
{
$storage = $entity->getStorage();
$availableStorageList = $this->config->get('attachmentAvailableStorageList') ?? [];
if (
$storage &&
(
!in_array($storage, $availableStorageList) ||
!$this->metadata->get(['app', 'fileStorage', 'implementationClassNameMap', $storage])
)
) {
$entity->clear('storage');
}
}
/**
* @throws Forbidden
*/
private function processSize(Attachment $entity): void
{
$size = $entity->getSize();
$maxSize = $this->detailsObtainer->getUploadMaxSize($entity);
// Checking not actual file size but a set value.
if ($size && $size > $maxSize) {
throw new Forbidden("Attachment size exceeds `attachmentUploadMaxSize`.");
}
}
private function processRole(Attachment $entity): void
{
if (!$entity->getRole()) {
$entity->setRole(Attachment::ROLE_ATTACHMENT);
}
}
}
@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Mail\Exceptions\NoSmtp;
use Espo\Core\Mail\Exceptions\SendingError;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Tools\Email\SendService;
/**
* @implements SaveHook<Email>
*/
class AfterUpdate implements SaveHook
{
public function __construct(
private User $user,
private SendService $sendService
) {}
/**
* @throws BadRequest
* @throws Error
* @throws NoSmtp
* @throws SendingError
*/
public function process(Entity $entity): void
{
if ($entity->getStatus() === Email::STATUS_SENDING) {
$this->sendService->send($entity, $this->user);
}
}
}
@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Mail\Sender;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Email>
*/
class BeforeCreate implements SaveHook
{
public function process(Entity $entity): void
{
if ($entity->getStatus() === Email::STATUS_SENDING) {
$messageId = Sender::generateMessageId($entity);
$entity->setMessageId('<' . $messageId . '>');
}
}
}
@@ -0,0 +1,151 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Mail\Sender;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\Utils\SystemUser;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Email>
*/
class BeforeUpdate implements SaveHook
{
/** @var string[] */
private $allowedForUpdateFieldList = [
'parent',
'teams',
'assignedUser',
];
public function __construct(
private User $user,
private EntityManager $entityManager,
private FieldUtil $fieldUtil
) {}
public function process(Entity $entity): void
{
$skipFilter = false;
if ($this->user->isAdmin()) {
$skipFilter = true;
}
if ($this->isEmailManuallyArchived($entity)) {
$skipFilter = true;
}
else if ($entity->isAttributeChanged('dateSent')) {
$entity->set('dateSent', $entity->getFetched('dateSent'));
}
if ($entity->getStatus() === Email::STATUS_DRAFT) {
$skipFilter = true;
}
if (
$entity->getStatus() === Email::STATUS_SENDING &&
$entity->getFetched('status') === Email::STATUS_DRAFT
) {
$skipFilter = true;
}
if (
$entity->isAttributeChanged('status') &&
$entity->getFetched('status') === Email::STATUS_ARCHIVED
) {
$entity->setStatus(Email::STATUS_ARCHIVED);
}
if (!$skipFilter) {
$this->clearEntityForUpdate($entity);
}
if ($entity->getStatus() == Email::STATUS_SENDING) {
$messageId = Sender::generateMessageId($entity);
$entity->setMessageId('<' . $messageId . '>');
}
}
private function isEmailManuallyArchived(Email $email): bool
{
if ($email->getStatus() !== Email::STATUS_ARCHIVED) {
return false;
}
$userId = $email->getCreatedBy()?->getId();
if (!$userId) {
return false;
}
$user = $this->entityManager
->getRDBRepositoryByClass(User::class)
->getById($userId);
if (!$user) {
return true;
}
return $user->getUserName() !== SystemUser::NAME;
}
private function clearEntityForUpdate(Email $email): void
{
$fieldDefsList = $this->entityManager
->getDefs()
->getEntity(Email::ENTITY_TYPE)
->getFieldList();
foreach ($fieldDefsList as $fieldDefs) {
$field = $fieldDefs->getName();
if ($fieldDefs->getParam('isCustom')) {
continue;
}
if (in_array($field, $this->allowedForUpdateFieldList)) {
continue;
}
$attributeList = $this->fieldUtil->getAttributeList(Email::ENTITY_TYPE, $field);
foreach ($attributeList as $attribute) {
$email->clear($attribute);
}
}
}
}
@@ -0,0 +1,55 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Record\Hook\ReadHook;
use Espo\Core\Record\ReadParams;
use Espo\Entities\Email;
use Espo\ORM\Entity;
use Espo\Tools\Email\InboxService;
/**
* @implements ReadHook<Email>
*/
class MarkAsRead implements ReadHook
{
public function __construct(
private InboxService $inboxService
) {}
public function process(Entity $entity, ReadParams $params): void
{
if ($entity->isRead()) {
return;
}
$this->inboxService->markAsRead($entity->getId());
}
}
@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\ORM\Entity;
use Espo\Tools\Email\InboxService;
/**
* @implements SaveHook<Email>
*/
class MarkAsReadBeforeUpdate implements SaveHook
{
public function __construct(
private InboxService $inboxService
) {}
public function process(Entity $entity): void
{
if ($entity->isRead()) {
return;
}
$this->inboxService->markAsRead($entity->getId());
}
}
@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\EmailAccount;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Entities\EmailAccount;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use const PHP_INT_MAX;
/**
* @implements SaveHook<EmailAccount>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private User $user,
private Config $config,
private EntityManager $entityManager
) {}
public function process(Entity $entity): void
{
if ($this->user->isAdmin()) {
return;
}
$entity->set('assignedUserId', $this->user->getId());
$count = $this->entityManager
->getRDBRepository(EmailAccount::ENTITY_TYPE)
->where(['assignedUserId' => $this->user->getId()])
->count();
if ($count >= $this->config->get('maxEmailAccountCount', PHP_INT_MAX)) {
throw new Forbidden("Email Account number for user limit exceeded.");
}
}
}
@@ -27,32 +27,33 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\RecordHooks\EmailFilter;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\EmailAccount as EmailAccountEntity;
use Espo\Entities\EmailFilter as EmailFilterEntity;
use Espo\Entities\EmailFilter;
use Espo\Entities\InboundEmail as InboundEmailEntity;
use Espo\Entities\User as UserEntity;
use Espo\ORM\Entity;
use Espo\Core\Exceptions\Forbidden;
use stdClass;
/**
* @extends Record<EmailFilterEntity>
* @implements SaveHook<EmailFilter>
*/
class EmailFilter extends Record
class BeforeSave implements SaveHook
{
/**
* @param EmailFilterEntity $entity
* @throws Forbidden
*/
protected function beforeCreateEntity(Entity $entity, $data)
{
parent::beforeCreateEntity($entity, $data);
public function __construct(
private Acl $acl
) {}
/**
* @inheritDoc
*/
public function process(Entity $entity): void
{
// Check if own.
if (!$this->acl->checkEntityEdit($entity)) {
if ($entity->isNew() && !$this->acl->checkEntityEdit($entity)) {
throw new Forbidden();
}
@@ -60,26 +61,17 @@ class EmailFilter extends Record
}
/**
* @param EmailFilterEntity $entity
* @throws Forbidden
*/
protected function beforeUpdateEntity(Entity $entity, $data)
{
parent::beforeUpdateEntity($entity, $data);
$this->controlEntityValues($entity);
}
/**
* @throws Forbidden
*/
private function controlEntityValues(EmailFilterEntity $entity): void
private function controlEntityValues(EmailFilter $entity): void
{
if ($entity->isGlobal()) {
$entity->set('parentId', null);
$entity->set('parentType', null);
$entity->setMultiple([
'parentType' => null,
'parentId' => null,
]);
if ($entity->getAction() !== EmailFilterEntity::ACTION_SKIP) {
if ($entity->getAction() !== EmailFilter::ACTION_SKIP) {
throw new Forbidden("Not allowed `action`.");
}
}
@@ -93,9 +85,9 @@ class EmailFilter extends Record
!in_array(
$entity->getAction(),
[
EmailFilterEntity::ACTION_NONE,
EmailFilterEntity::ACTION_SKIP,
EmailFilterEntity::ACTION_MOVE_TO_FOLDER,
EmailFilter::ACTION_NONE,
EmailFilter::ACTION_SKIP,
EmailFilter::ACTION_MOVE_TO_FOLDER,
]
)
) {
@@ -107,8 +99,8 @@ class EmailFilter extends Record
!in_array(
$entity->getAction(),
[
EmailFilterEntity::ACTION_SKIP,
EmailFilterEntity::ACTION_MOVE_TO_GROUP_FOLDER,
EmailFilter::ACTION_SKIP,
EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER,
]
)
) {
@@ -117,26 +109,19 @@ class EmailFilter extends Record
if (
$entity->getParentType() === EmailAccountEntity::ENTITY_TYPE &&
$entity->getAction() !== EmailFilterEntity::ACTION_SKIP
$entity->getAction() !== EmailFilter::ACTION_SKIP
) {
throw new Forbidden("Not allowed `action`.");
}
if ($entity->getAction() !== EmailFilterEntity::ACTION_MOVE_TO_FOLDER) {
if ($entity->getAction() !== EmailFilter::ACTION_MOVE_TO_FOLDER) {
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('emailFolderId', null);
}
if ($entity->getAction() !== EmailFilterEntity::ACTION_MOVE_TO_GROUP_FOLDER) {
if ($entity->getAction() !== EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER) {
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('groupEmailFolderId', null);
}
}
public function filterUpdateInput(stdClass $data): void
{
parent::filterUpdateInput($data);
unset($data->isGlobal);
unset($data->parentId);
unset($data->parentType);
}
}
@@ -27,20 +27,27 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\RecordHooks\EmailFolder;
use Espo\ORM\Entity;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\EmailFolder;
use Espo\Entities\User;
use Espo\ORM\Entity;
/**
* @extends Record<\Espo\Entities\EmailFolder>
* @implements SaveHook<EmailFolder>
*/
class EmailFolder extends Record
class BeforeCreate implements SaveHook
{
protected function beforeCreateEntity(Entity $entity, $data)
{
parent::beforeCreateEntity($entity, $data);
public function __construct(
private User $user,
private Acl $acl
) {}
public function process(Entity $entity): void
{
if (!$this->user->isAdmin() || !$entity->get('assignedUserId')) {
$entity->set('assignedUserId', $this->user->getId());
}
@@ -0,0 +1,53 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\LeadCapture;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\LeadCapture;
use Espo\ORM\Entity;
use Espo\Tools\LeadCapture\Service;
/**
* @noinspection PhpUnused
* @implements SaveHook<LeadCapture>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Service $service
) {}
public function process(Entity $entity): void
{
$apiKey = $this->service->generateApiKey();
$entity->setApiKey($apiKey);
}
}
@@ -0,0 +1,90 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Note;
use Espo\Entities\Note as NoteEntity;
use Espo\Entities\Preferences;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Tools\Stream\Service;
/**
* @implements SaveHook<Note>
* @noinspection PhpUnused
*/
class AfterCreate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private User $user,
private Metadata $metadata,
private Service $streamService
) {}
public function process(Entity $entity): void
{
$parentType = $entity->getParentType();
$parentId = $entity->getParentId();
if (
$entity->getType() !== NoteEntity::TYPE_POST ||
!$parentType ||
!$parentId
) {
return;
}
if (!$this->metadata->get(['scopes', $parentType, 'stream'])) {
return;
}
$preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $this->user->getId());
if (!$preferences) {
return;
}
if (!$preferences->get('followEntityOnStreamPost')) {
return;
}
$parent = $this->entityManager->getEntityById($parentType, $parentId);
if (!$parent || $this->user->isSystem() || $this->user->isApi()) {
return;
}
$this->streamService->followEntity($parent, $this->user->getId());
}
}
@@ -0,0 +1,195 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Acl;
use Espo\Core\Acl\Table as AclTable;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Note;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Repositories\User as UserRepository;
/**
* @implements SaveHook<Note>
*/
class AssignmentCheck implements SaveHook
{
public function __construct(
private User $user,
private Acl $acl,
private EntityManager $entityManager
) {}
public function process(Entity $entity): void
{
$targetType = $entity->getTargetType();
if (!$targetType) {
return;
}
$userTeamIdList = $this->user->getTeamIdList();
$userIdList = $entity->getLinkMultipleIdList('users');
$portalIdList = $entity->getLinkMultipleIdList('portals');
$teamIdList = $entity->getLinkMultipleIdList('teams');
/** @var iterable<User> $targetUserList */
$targetUserList = [];
if ($targetType === Note::TARGET_USERS) {
/** @var iterable<User> $targetUserList */
$targetUserList = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
->select(['id', 'type'])
->where(['id' => $userIdList])
->find();
}
$hasPortalTargetUser = false;
$allTargetUsersArePortal = true;
foreach ($targetUserList as $user) {
if (!$user->isPortal()) {
$allTargetUsersArePortal = false;
}
if ($user->isPortal()) {
$hasPortalTargetUser = true;
}
}
$messagePermission = $this->acl->getPermissionLevel('message');
if ($messagePermission === AclTable::LEVEL_NO) {
if (
$targetType !== Note::TARGET_SELF &&
$targetType !== Note::TARGET_PORTALS &&
!(
$targetType === Note::TARGET_USERS &&
count($userIdList) === 1 &&
$userIdList[0] === $this->user->getId()
) &&
!(
$targetType === Note::TARGET_USERS && $allTargetUsersArePortal
)
) {
throw new Forbidden('Not permitted to post to anybody except self.');
}
}
if ($targetType === Note::TARGET_TEAMS) {
if (empty($teamIdList)) {
throw new BadRequest("No team IDS.");
}
}
if ($targetType === Note::TARGET_USERS) {
if (empty($userIdList)) {
throw new BadRequest("No user IDs.");
}
}
if ($targetType === Note::TARGET_PORTALS) {
if (empty($portalIdList)) {
throw new BadRequest("No portal IDs.");
}
if ($this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES) {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if (
$targetType === Note::TARGET_USERS &&
$this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES
) {
if ($hasPortalTargetUser) {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if ($messagePermission === AclTable::LEVEL_TEAM) {
if ($targetType === Note::TARGET_ALL) {
throw new Forbidden('Not permitted to post to all.');
}
}
if (
$messagePermission === AclTable::LEVEL_TEAM &&
$targetType === Note::TARGET_TEAMS
) {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to foreign teams.');
}
foreach ($teamIdList as $teamId) {
if (!in_array($teamId, $userTeamIdList)) {
throw new Forbidden("Not permitted to post to foreign teams.");
}
}
}
if (
$messagePermission === AclTable::LEVEL_TEAM &&
$targetType === Note::TARGET_USERS
) {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
foreach ($targetUserList as $user) {
if ($user->getId() === $this->user->getId()) {
continue;
}
if ($user->isPortal()) {
continue;
}
$inTeam = $this->getUserRepository()->checkBelongsToAnyOfTeams($user->getId(), $userTeamIdList);
if (!$inTeam) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
}
}
}
private function getUserRepository(): UserRepository
{
/** @var UserRepository */
return $this->entityManager->getRepository(User::ENTITY_TYPE);
}
}
@@ -0,0 +1,135 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Acl;
use Espo\Core\Acl\Table as AclTable;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Note;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Tools\Stream\NoteUtil;
/**
* @implements SaveHook<Note>
* @noinspection PhpUnused
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private Acl $acl,
private User $user,
private NoteUtil $noteUtil
) {}
public function process(Entity $entity): void
{
$this->checkParent($entity);
if (!$entity->isPost() && !$this->user->isAdmin()) {
throw new Forbidden("Only 'Post' type allowed.");
}
if ($this->user->isPortal()) {
$entity->set('isInternal', false);
}
if ($entity->isPost()) {
$this->noteUtil->handlePostText($entity);
}
$targetType = $entity->getTargetType();
$entity->clear('isGlobal');
switch ($targetType) {
case Note::TARGET_ALL:
$entity->clear('usersIds');
$entity->clear('teamsIds');
$entity->clear('portalsIds');
$entity->set('isGlobal', true);
break;
case Note::TARGET_SELF:
$entity->clear('usersIds');
$entity->clear('teamsIds');
$entity->clear('portalsIds');
$entity->set('usersIds', [$this->user->getId()]);
$entity->set('isForSelf', true);
break;
case Note::TARGET_USERS:
$entity->clear('teamsIds');
$entity->clear('portalsIds');
break;
case Note::TARGET_TEAMS:
$entity->clear('usersIds');
$entity->clear('portalsIds');
break;
case Note::TARGET_PORTALS:
$entity->clear('usersIds');
$entity->clear('teamsIds');
break;
}
}
/**
* @throws Forbidden
*/
private function checkParent(Note $entity): void
{
if (!$entity->getParentType() || !$entity->getParentId()) {
return;
}
$parent = $this->entityManager->getEntityById($entity->getParentType(), $entity->getParentId());
if ($parent && $this->acl->check($parent, AclTable::ACTION_READ)) {
return;
}
throw new Forbidden("No access to parent.");
}
}
@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Note;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Tools\Stream\NoteUtil;
/**
* @implements SaveHook<Note>
* @noinspection PhpUnused
*/
class BeforeUpdate implements SaveHook
{
public function __construct(
private User $user,
private NoteUtil $noteUtil
) {}
public function process(Entity $entity): void
{
if ($entity->isPost()) {
$this->noteUtil->handlePostText($entity);
}
if (!$entity->isPost() && !$this->user->isAdmin()) {
throw new ForbiddenSilent("Only 'Post' type allowed.");
}
}
}
@@ -27,45 +27,42 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\RecordHooks\Portal;
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Portal;
use Espo\ORM\Entity;
use Espo\Core\Di;
use Espo\ORM\EntityManager;
use Espo\Repositories\Portal as PortalRepository;
/**
* @extends Record<\Espo\Entities\PortalRole>
* @implements SaveHook<Portal>
*/
class PortalRole extends Record implements
Di\DataManagerAware
class AfterUpdate implements SaveHook
{
use Di\DataManagerSetter;
public function __construct(
private Clearer $clearer,
private DataManager $dataManager,
private EntityManager $entityManager
) {}
protected $forceSelectAllAttributes = true;
public function afterCreateEntity(Entity $entity, $data)
public function process(Entity $entity): void
{
parent::afterCreateEntity($entity, $data);
$this->clearRolesCache();
}
$this->getPortalRepository()->loadUrlField($entity);
public function afterUpdateEntity(Entity $entity, $data)
{
parent::afterUpdateEntity($entity, $data);
$this->clearRolesCache();
}
protected function clearRolesCache(): void
{
$this->createAclCacheClearer()->clearForAllPortalUsers();
if (!$entity->isAttributeChanged('portalRolesIds')) {
return;
}
$this->clearer->clearForAllPortalUsers();
$this->dataManager->updateCacheTimestamp();
}
private function createAclCacheClearer(): AclCacheClearer
private function getPortalRepository(): PortalRepository
{
return $this->injectableFactory->create(AclCacheClearer::class);
/** @var PortalRepository */
return $this->entityManager->getRDBRepositoryByClass(Portal::class);
}
}
@@ -0,0 +1,53 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\PortalRole;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\PortalRole;
use Espo\ORM\Entity;
/**
* @implements SaveHook<PortalRole>
*/
class AfterSave implements SaveHook
{
public function __construct(
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity): void
{
$this->clearer->clearForAllInternalUsers();
$this->dataManager->updateCacheTimestamp();
}
}
@@ -0,0 +1,53 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Role;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Role;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Role>
*/
class AfterSave implements SaveHook
{
public function __construct(
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity): void
{
$this->clearer->clearForAllInternalUsers();
$this->dataManager->updateCacheTimestamp();
}
}
@@ -0,0 +1,232 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Role;
use Espo\Core\Acl\Table;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Portal\Acl\Table as TablePortal;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Metadata;
use Espo\Entities\PortalRole;
use Espo\Entities\Role;
use Espo\ORM\Entity;
use stdClass;
/**
* @noinspection PhpUnused
* @implements SaveHook<Role|PortalRole>
*/
class BeforeSaveValidate implements SaveHook
{
/** @var string[] */
private array $levelList = [
Table::LEVEL_YES,
Table::LEVEL_ALL,
Table::LEVEL_TEAM,
Table::LEVEL_OWN,
Table::LEVEL_NO,
];
/** @var string[] */
private array $portalLevelList = [
Table::LEVEL_YES,
Table::LEVEL_ALL,
TablePortal::LEVEL_ACCOUNT,
TablePortal::LEVEL_CONTACT,
Table::LEVEL_OWN,
Table::LEVEL_NO,
];
public function __construct(
private Metadata $metadata
) {}
public function process(Entity $entity): void
{
$this->validateData($entity);
$this->validateFieldData($entity);
}
/**
* @throws BadRequest
*/
private function validateData(Role|PortalRole $entity): void
{
if ($entity->get('data') === null) {
return;
}
/** @var array<string, mixed> $data */
$data = get_object_vars($entity->get('data'));
foreach ($data as $scope => $item) {
if (!is_bool($item) && !$item instanceof stdClass) {
throw new BadRequest("Bad data. Should be bool or object.");
}
$this->validateDataItem($scope, $entity, $item);
}
}
/**
* @throws BadRequest
*/
private function validateDataItem(string $scope, Role|PortalRole $entity, bool|stdClass $item): void
{
$key = $entity instanceof PortalRole ?
'aclPortal' : 'acl';
$type = $this->metadata->get("scopes.$scope.$key");
if ($type === 'boolean') {
if (!is_bool($item)) {
throw new BadRequest("Bad data. Value for *$scope* should be be bool.");
}
return;
}
if ($type === null) {
throw new BadRequest("Bad data. Scope *$scope* is not allowed.");
}
if ($item === false) {
return;
}
if (is_bool($item)) {
throw new BadRequest("Bad data. Value for *$scope* should be be false or object.");
}
$actions = [
Table::ACTION_CREATE,
Table::ACTION_READ,
Table::ACTION_EDIT,
Table::ACTION_DELETE,
Table::ACTION_STREAM,
];
$levels = $entity instanceof PortalRole ?
$this->portalLevelList : $this->levelList;
foreach ($actions as $action) {
if (!property_exists($item, $action)) {
continue;
}
$level = $item->$action;
if (!in_array($level, $levels)) {
throw new BadRequest("Level `$level` is not allowed for action *$action* for *$scope*.");
}
}
}
/**
* @throws BadRequest
*/
private function validateFieldData(Role|PortalRole $entity): void
{
if ($entity->get('fieldData') === null) {
return;
}
/** @var array<string, mixed> $data */
$data = get_object_vars($entity->get('fieldData'));
foreach ($data as $scope => $item) {
if (!$item instanceof stdClass) {
throw new BadRequest("Bad field-level data. Should be object.");
}
$this->validateFieldDataItem($scope, $entity, $item);
}
}
/**
* @throws BadRequest
*/
private function validateFieldDataItem(string $scope, PortalRole|Role $entity, stdClass $item): void
{
$disabledKey = $entity instanceof PortalRole ? 'aclPortalFieldLevelDisabled' : 'aclFieldLevelDisabled';
$key = $entity instanceof PortalRole ? 'aclPortal' : 'acl';
if (
!$this->metadata->get("scopes.$scope.entity") ||
!$this->metadata->get("scopes.$scope.$key") ||
$this->metadata->get("scopes.$scope.$disabledKey")
) {
throw new BadRequest("Bad field-level data. Scope *$scope* is not allowed.");
}
/** @var array<string, mixed> $data */
$data = get_object_vars($item);
foreach ($data as $field => $fieldItem) {
if (!$fieldItem instanceof stdClass) {
throw new BadRequest("Data for field *$field*, scope *$scope* should be object.");
}
$this->validateFieldDataItemItem($scope, $field, $fieldItem);
}
}
/**
* @throws BadRequest
*/
private function validateFieldDataItemItem(string $scope, string $field, stdClass $item): void
{
if (!$this->metadata->get("entityDefs.$scope.fields.$field")) {
throw new BadRequest("Field *$field* does not exist in *$scope*.");
}
$actions = [
Table::ACTION_READ,
Table::ACTION_EDIT,
];
$levels = [
Table::LEVEL_YES,
Table::LEVEL_NO,
];
foreach ($actions as $action) {
if (!property_exists($item, $action)) {
continue;
}
$level = $item->$action;
if (!in_array($level, $levels)) {
throw new BadRequest("Level `$level` is not allowed for *$scope*, field *$field*.");
}
}
}
}
@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Team;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Team;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Team>
* @noinspection PhpUnused
*/
class AfterUpdate implements SaveHook
{
public function __construct(
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity): void
{
if (!$entity->isAttributeChanged('rolesIds')) {
return;
}
$this->clearer->clearForAllInternalUsers();
$this->dataManager->updateCacheTimestamp();
}
}
@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Team;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\LinkHook;
use Espo\Entities\Team;
use Espo\Entities\User;
use Espo\ORM\Entity;
/**
* @implements LinkHook<Team>
*/
class ClearCacheAfterLink implements LinkHook
{
public function __construct(
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity, string $link, Entity $foreignEntity): void
{
if ($link !== 'users' || !$foreignEntity instanceof User) {
return;
}
$this->clearer->clearForUser($foreignEntity);
$this->dataManager->updateCacheTimestamp();
}
}
@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Team;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\UnlinkHook;
use Espo\Entities\Team;
use Espo\Entities\User;
use Espo\ORM\Entity;
/**
* @implements UnlinkHook<Team>
*/
class ClearCacheAfterUnlink implements UnlinkHook
{
public function __construct(
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity, string $link, Entity $foreignEntity): void
{
if ($link !== 'users' || !$foreignEntity instanceof User) {
return;
}
$this->clearer->clearForUser($foreignEntity);
$this->dataManager->updateCacheTimestamp();
}
}
@@ -0,0 +1,114 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\User;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\DataManager;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<User>
* @noinspection PhpUnused
*/
class AfterUpdate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private Clearer $clearer,
private DataManager $dataManager
) {}
public function process(Entity $entity): void
{
$this->processCache($entity);
$this->processContactName($entity);
}
private function processCache(User $entity): void
{
if (
$entity->isAttributeChanged('rolesIds') ||
$entity->isAttributeChanged('teamsIds') ||
$entity->isAttributeChanged('type') ||
$entity->isAttributeChanged('portalRolesIds') ||
$entity->isAttributeChanged('portalsIds')
) {
$this->clearer->clearForUser($entity);
$this->dataManager->updateCacheTimestamp();
}
if (
$entity->isAttributeChanged('portalRolesIds') ||
$entity->isAttributeChanged('portalsIds') ||
$entity->isAttributeChanged('contactId') ||
$entity->isAttributeChanged('accountsIds')
) {
$this->clearer->clearForAllPortalUsers();
$this->dataManager->updateCacheTimestamp();
}
}
private function processContactName(User $entity): void
{
if (
!$entity->isPortal() ||
!$entity->getContactId() ||
!$entity->isAttributeChanged('firstName') &&
!$entity->isAttributeChanged('lastName') &&
!$entity->isAttributeChanged('salutationName')
) {
return;
}
$contact = $this->entityManager->getEntityById(Contact::ENTITY_TYPE, $entity->getContactId());
if (!$contact) {
return;
}
if ($entity->isAttributeChanged('firstName')) {
$contact->set('firstName', $entity->get('firstName'));
}
if ($entity->isAttributeChanged('lastName')) {
$contact->set('lastName', $entity->get('lastName'));
}
if ($entity->isAttributeChanged('salutationName')) {
$contact->set('salutationName', $entity->get('salutationName'));
}
$this->entityManager->saveEntity($contact);
}
}
@@ -0,0 +1,135 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\User;
use Espo\Core\Authentication\Logins\Hmac;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Util;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Tools\User\UserUtil;
/**
* @implements SaveHook<User>
* @noinspection PhpUnused
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Config $config,
private User $user,
private UserUtil $util
) {}
public function process(Entity $entity): void
{
$this->processLimitChecking($entity);
$this->processUserExistsChecking($entity);
$this->processApi($entity);
$this->processTypeChecking($entity);
}
/**
* @throws Conflict
*/
private function processUserExistsChecking(User $entity): void
{
if ($this->util->checkExists($entity)) {
throw new Conflict('userNameExists');
}
}
/**
* @throws Forbidden
*/
private function processLimitChecking(User $entity): void
{
$userLimit = $this->config->get('userLimit');
$portalUserLimit = $this->config->get('portalUserLimit');
if (
$userLimit &&
!$this->user->isSuperAdmin() &&
!$entity->isPortal() && !$entity->isApi()
) {
$userCount = $this->util->getInternalCount();
if ($userCount >= $userLimit) {
throw new Forbidden("User limit $userLimit is reached.");
}
}
if (
$portalUserLimit &&
!$this->user->isSuperAdmin() &&
$entity->isPortal()
) {
$portalUserCount = $this->util->getPortalCount();
if ($portalUserCount >= $portalUserLimit) {
throw new Forbidden("Portal user limit $portalUserLimit is reached.");
}
}
}
private function processApi(User $entity): void
{
if (!$entity->isApi()) {
return;
}
$entity->set('apiKey', Util::generateApiKey());
if ($entity->getAuthMethod() === Hmac::NAME) {
$secretKey = Util::generateSecretKey();
$entity->set('secretKey', $secretKey);
}
}
/**
* @throws Forbidden
*/
private function processTypeChecking(User $entity): void
{
if (
$entity->isSuperAdmin() ||
!$entity->getType() ||
in_array($entity->getType(), $this->util->getAllowedUserTypeList())
) {
return;
}
throw new Forbidden("Not allowed 'type'.");
}
}
@@ -0,0 +1,171 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\User;
use Espo\Core\Authentication\Logins\Hmac;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Util;
use Espo\Entities\User as UserEntity;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Tools\User\UserUtil;
/**
* @implements SaveHook<User>
* @noinspection PhpUnused
*/
class BeforeUpdate implements SaveHook
{
public function __construct(
private Config $config,
private User $user,
private UserUtil $util
) {}
public function process(Entity $entity): void
{
$this->processLimitChecking($entity);
$this->processUserExistsChecking($entity);
$this->processApi($entity);
$this->processTypeChecking($entity);
}
/**
* @throws Conflict
*/
private function processUserExistsChecking(User $entity): void
{
if (!$entity->isAttributeChanged('userName')) {
return;
}
if ($this->util->checkExists($entity)) {
throw new Conflict('userNameExists');
}
}
/**
* @throws Forbidden
*/
private function processLimitChecking(User $entity): void
{
$userLimit = $this->config->get('userLimit');
$portalUserLimit = $this->config->get('portalUserLimit');
if (
$userLimit &&
!$this->user->isSuperAdmin() &&
(
(
$entity->isActive() &&
$entity->isAttributeChanged('isActive') &&
!$entity->isPortal() &&
!$entity->isApi()
) ||
(
!$entity->isPortal() &&
!$entity->isApi() &&
$entity->isAttributeChanged('type') &&
(
$entity->isRegular() ||
$entity->isAdmin()
) &&
(
$entity->getFetched('type') == UserEntity::TYPE_PORTAL ||
$entity->getFetched('type') == UserEntity::TYPE_API
)
)
)
) {
$userCount = $this->util->getInternalCount();
if ($userCount >= $userLimit) {
throw new Forbidden("User limit $userLimit is reached.");
}
}
if (
$portalUserLimit &&
!$this->user->isSuperAdmin() &&
(
(
$entity->isActive() &&
$entity->isAttributeChanged('isActive') &&
$entity->isPortal()
) ||
(
$entity->isPortal() &&
$entity->isAttributeChanged('type')
)
)
) {
$portalUserCount = $this->util->getPortalCount();
if ($portalUserCount >= $portalUserLimit) {
throw new Forbidden("Portal user limit $portalUserLimit is reached.");
}
}
}
private function processApi(User $entity): void
{
if (
!$entity->isApi() ||
!$entity->isAttributeChanged('authMethod') ||
$entity->getAuthMethod() !== Hmac::NAME
) {
return;
}
$secretKey = Util::generateSecretKey();
$entity->set('secretKey', $secretKey);
}
/**
* @throws Forbidden
*/
private function processTypeChecking(User $entity): void
{
if (
$entity->isSuperAdmin() ||
!$entity->isAttributeChanged('type') ||
!$entity->getType() ||
in_array($entity->getType(), $this->util->getAllowedUserTypeList())
) {
return;
}
throw new Forbidden("Can't change type.");
}
}
@@ -0,0 +1,62 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Webhook;
use Espo\Core\Record\DeleteParams;
use Espo\Core\Record\Hook\DeleteHook;
use Espo\Core\Webhook\Manager;
use Espo\Entities\Webhook;
use Espo\ORM\Entity;
/**
* @implements DeleteHook<Webhook>
* @noinspection PhpUnused
*/
class AfterDelete implements DeleteHook
{
public function __construct(
private Manager $webhookManager
) {}
public function process(Entity $entity, DeleteParams $params): void
{
$event = $entity->getEvent();
if (!$event) {
return;
}
if (!$entity->isActive()) {
return;
}
$this->webhookManager->removeEvent($event);
}
}
@@ -0,0 +1,75 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Webhook;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Webhook\Manager;
use Espo\Entities\Webhook;
use Espo\ORM\Entity;
use RuntimeException;
/**
* @implements SaveHook<Webhook>
*/
class AfterSave implements SaveHook
{
public function __construct(
private Manager $webhookManager
) {}
public function process(Entity $entity): void
{
$event = $entity->getEvent();
if (!$event) {
throw new RuntimeException("No 'event'.");
}
if ($entity->isNew()) {
if ($entity->isActive()) {
$this->webhookManager->addEvent($event);
}
return;
}
if (!$entity->isAttributeChanged('isActive')) {
return;
}
if ($entity->isActive()) {
$this->webhookManager->addEvent($event);
return;
}
$this->webhookManager->removeEvent($event);
}
}
@@ -27,124 +27,75 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
namespace Espo\Classes\RecordHooks\Webhook;
use Espo\Entities\Webhook as WebhookEntity;
use Espo\ORM\Entity;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Di;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use stdClass;
use Espo\Entities\Webhook;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @extends Record<WebhookEntity>
* @implements SaveHook<Webhook>
*/
class Webhook extends Record implements
Di\WebhookManagerAware
class BeforeSave implements SaveHook
{
use Di\WebhookManagerSetter;
private const WEBHOOK_MAX_COUNT_PER_USER = 50;
const WEBHOOK_MAX_COUNT_PER_USER = 50;
/**
* @var string[]
*/
protected $eventTypeList = [
/** @var string[] */
private $eventTypeList = [
'create',
'update',
'delete',
'fieldUpdate',
];
/**
* @var string[]
*/
protected $onlyAdminAttributeList = ['userId', 'userName'];
public function __construct(
private User $user,
private EntityManager $entityManager,
private Acl $acl,
private Metadata $metadata,
private Config $config
) {}
/**
* @var string[]
*/
protected $readOnlyAttributeList = ['secretKey'];
public function populateDefaults(Entity $entity, stdClass $data): void
{
parent::populateDefaults($entity, $data);
if ($this->user->isApi()) {
$entity->set('userId', $this->user->getId());
}
}
protected function filterInput($data)
{
parent::filterInput($data);
unset($data->entityType);
unset($data->field);
unset($data->type);
}
public function filterUpdateInput(stdClass $data): void
{
if (!$this->user->isAdmin()) {
unset($data->event);
}
parent::filterUpdateInput($data);
}
protected function beforeCreateEntity(Entity $entity, $data)
public function process(Entity $entity): void
{
$this->checkEntityUserIsApi($entity);
$this->processEntityEventData($entity);
if (!$this->user->isAdmin()) {
if ($entity->isNew() && !$this->user->isAdmin()) {
$this->checkMaxCount();
}
}
protected function checkMaxCount(): void
/**
* @throws Forbidden
*/
private function checkEntityUserIsApi(Webhook $entity): void
{
$maxCount = $this->config->get('webhookMaxCountPerUser', self::WEBHOOK_MAX_COUNT_PER_USER);
$count = $this->entityManager
->getRDBRepository(WebhookEntity::ENTITY_TYPE)
->where([
'userId' => $this->user->getId(),
])
->count();
if ($maxCount && $count >= $maxCount) {
throw new Forbidden("Webhook number per user exceeded the limit.");
}
}
protected function beforeUpdateEntity(Entity $entity, $data)
{
$this->checkEntityUserIsApi($entity);
$this->processEntityEventData($entity);
}
protected function checkEntityUserIsApi(Entity $entity): void
{
$userId = $entity->get('userId');
$userId = $entity->getUserId();
if (!$userId) {
return;
}
/** @var User|null $user */
$user = $this->entityManager->getEntity(User::ENTITY_TYPE, $userId);
$user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId);
if (!$user || !$user->isApi()) {
throw new Forbidden("User must be an API User.");
if ($user && $user->isApi()) {
return;
}
throw new Forbidden("User must be an API User.");
}
protected function processEntityEventData(Entity $entity): void
/**
* @throws Forbidden
*/
private function processEntityEventData(Webhook $entity): void
{
$event = $entity->get('event');
@@ -152,10 +103,8 @@ class Webhook extends Record implements
throw new Forbidden("Event is empty.");
}
if (!$entity->isNew()) {
if ($entity->isAttributeChanged('event')) {
throw new Forbidden("Event can't be changed.");
}
if (!$entity->isNew() && $entity->isAttributeChanged('event')) {
throw new Forbidden("Event can't be changed.");
}
$arr = explode('.', $event);
@@ -164,7 +113,6 @@ class Webhook extends Record implements
throw new Forbidden("Not supported event.");
}
$arr = explode('.', $event);
$entityType = $arr[0];
$type = $arr[1];
@@ -185,7 +133,7 @@ class Webhook extends Record implements
throw new Forbidden("Not existing Entity Type.");
}
if (!$this->acl->checkScope($entityType, 'read')) {
if (!$this->acl->checkScope($entityType, Acl\Table::ACTION_READ)) {
throw new Forbidden("Entity type is forbidden.");
}
@@ -204,43 +152,35 @@ class Webhook extends Record implements
throw new Forbidden("Field is empty.");
}
$forbiddenFieldList = $this->getAcl()->getScopeForbiddenFieldList($entityType);
if (in_array($field, $forbiddenFieldList)) {
if (!$this->acl->checkField($entityType, $field)) {
throw new Forbidden("Field is forbidden.");
}
if (!$this->metadata->get(['entityDefs', $entityType, 'fields', $field])) {
throw new Forbidden("Field does not exist.");
}
} else {
$entity->set('field', null);
return;
}
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('field', null);
}
protected function afterCreateEntity(Entity $entity, $data)
/**
* @throws Forbidden
*/
private function checkMaxCount(): void
{
if ($entity->get('isActive')) {
$this->webhookManager->addEvent($entity->get('event'));
}
}
$maxCount = $this->config->get('webhookMaxCountPerUser', self::WEBHOOK_MAX_COUNT_PER_USER);
protected function afterDeleteEntity(Entity $entity)
{
if ($entity->get('isActive')) {
$this->webhookManager->removeEvent($entity->get('event'));
}
}
$count = $this->entityManager
->getRDBRepositoryByClass(Webhook::class)
->where(['userId' => $this->user->getId()])
->count();
protected function afterUpdateEntity(Entity $entity, $data)
{
if (isset($data->isActive)) {
if ($entity->get('isActive')) {
$this->webhookManager->addEvent($entity->get('event'));
}
else {
$this->webhookManager->removeEvent($entity->get('event'));
}
if ($maxCount && $count >= $maxCount) {
throw new Forbidden("Webhook number per user exceeded the limit.");
}
}
}
@@ -30,6 +30,8 @@
namespace Espo\Classes\Select\Attachment\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\Entities\Attachment;
use Espo\Entities\Settings;
use Espo\ORM\Query\SelectBuilder;
class Orphan implements Filter
@@ -37,32 +39,35 @@ class Orphan implements Filter
public function apply(SelectBuilder $queryBuilder): void
{
$queryBuilder->where([
'role' => ['Attachment', 'Inline Attachment'],
'role' => [
Attachment::ROLE_ATTACHMENT,
Attachment::ROLE_INLINE_ATTACHMENT,
],
[
'OR' => [
[
'parentId' => null,
'parentType!=' => null,
'relatedType=' => null,
'parentId' => null,
'relatedType' => null,
],
[
'parentType' => null,
'relatedId' => null,
'relatedType!=' => null,
'relatedId' => null,
'parentType' => null,
],
],
],
[
'OR' => [
'relatedType!=' => 'Settings',
'relatedType=' => null,
'relatedType!=' => Settings::ENTITY_TYPE,
'relatedType' => null,
],
],
'attachmentChild.id' => null,
]);
$queryBuilder->leftJoin(
'Attachment',
Attachment::ENTITY_TYPE,
'attachmentChild',
[
'attachmentChild.sourceId:' => 'attachment.id',
@@ -30,6 +30,7 @@
namespace Espo\Classes\Select\Email\AdditionalAppliers;
use Espo\Core\Select\Applier\AdditionalApplier;
use Espo\Core\Select\Primary\Filters\One;
use Espo\Core\Select\SearchParams;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
@@ -57,6 +58,10 @@ class Main implements AdditionalApplier
private function applyIndexes(?string $folder, SelectBuilder $queryBuilder, SearchParams $searchParams): void
{
if ($searchParams->getPrimaryFilter() === One::NAME) {
return;
}
if ($searchParams->getTextFilter()) {
return;
}
@@ -215,7 +215,6 @@ class GoogleMaps implements Helper
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($c, \CURLOPT_BINARYTRANSFER, 1);
$raw = curl_exec($c);
+60 -45
View File
@@ -43,8 +43,6 @@ use Espo\Tools\ExportCustom\Service as ExportCustomService;
use Espo\Tools\LinkManager\LinkManager;
use stdClass;
use const FILTER_SANITIZE_STRING;
class EntityManager
{
/**
@@ -66,7 +64,7 @@ class EntityManager
* @throws Error
* @throws Conflict
*/
public function postActionCreateEntity(Request $request): bool
public function postActionCreateEntity(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -79,9 +77,6 @@ class EntityManager
$name = $data['name'];
$type = $data['type'];
$name = filter_var($name, FILTER_SANITIZE_STRING);
$type = filter_var($type, FILTER_SANITIZE_STRING);
if (!is_string($name) || !is_string($type)) {
throw new BadRequest();
}
@@ -142,9 +137,9 @@ class EntityManager
$params['kanbanStatusIgnoreList'] = $data['kanbanStatusIgnoreList'];
}
$this->entityManagerTool->create($name, $type, $params);
$name = $this->entityManagerTool->create($name, $type, $params);
return true;
return (object) ['name' => $name];
}
/**
@@ -163,8 +158,6 @@ class EntityManager
$name = $data['name'];
$name = filter_var($name, FILTER_SANITIZE_STRING);
if (!is_string($name)) {
throw new BadRequest();
}
@@ -191,8 +184,6 @@ class EntityManager
$name = $data['name'];
$name = filter_var($name, FILTER_SANITIZE_STRING);
if (!is_string($name)) {
throw new BadRequest();
}
@@ -234,11 +225,15 @@ class EntityManager
throw new BadRequest();
}
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
$params[$item] = htmlspecialchars($data[$item]);
}
foreach ($additionalParamList as $item) {
$params[$item] = filter_var($data[$item] ?? null, FILTER_SANITIZE_STRING);
$params[$item] = $data[$item];
if (is_string($params[$item])) {
$params[$item] = htmlspecialchars($params[$item]);
}
}
$params['labelForeign'] = $params['labelForeign'] ?? $params['linkForeign'];
@@ -275,21 +270,31 @@ class EntityManager
$params['layoutForeign'] = $data['layoutForeign'];
}
if (array_key_exists('selectFilter', $data)) {
$params['selectFilter'] = $data['selectFilter'];
}
if (array_key_exists('selectFilterForeign', $data)) {
$params['selectFilterForeign'] = $data['selectFilterForeign'];
}
/** @var array{
* linkType: string,
* entity: string,
* link: string,
* entityForeign: string,
* linkForeign: string,
* label: string,
* labelForeign: string,
* relationName?: ?string,
* linkMultipleField?: bool,
* linkMultipleFieldForeign?: bool,
* audited?: bool,
* auditedForeign?: bool,
* layout?: string,
* layoutForeign?: string,
* linkType: string,
* entity: string,
* link: string,
* entityForeign: string,
* linkForeign: string,
* label: string,
* labelForeign: string,
* relationName?: ?string,
* linkMultipleField?: bool,
* linkMultipleFieldForeign?: bool,
* audited?: bool,
* auditedForeign?: bool,
* layout?: string,
* layoutForeign?: string,
* selectFilter?: string,
* selectFilterForeign?: string,
* } $params
*/
@@ -321,7 +326,7 @@ class EntityManager
foreach ($paramList as $item) {
if (array_key_exists($item, $data)) {
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
$params[$item] = htmlspecialchars($data[$item]);
}
}
@@ -352,26 +357,36 @@ class EntityManager
$params['layout'] = $data['layout'];
}
if (array_key_exists('auditedForeign', $data)) {
if (array_key_exists('layoutForeign', $data)) {
$params['layoutForeign'] = $data['layoutForeign'];
}
if (array_key_exists('selectFilter', $data)) {
$params['selectFilter'] = $data['selectFilter'];
}
if (array_key_exists('selectFilterForeign', $data)) {
$params['selectFilterForeign'] = $data['selectFilterForeign'];
}
/**
* @var array{
* entity: string,
* link: string,
* entityForeign?: ?string,
* linkForeign?: ?string,
* label?: string,
* labelForeign?: string,
* linkMultipleField?: bool,
* linkMultipleFieldForeign?: bool,
* audited?: bool,
* auditedForeign?: bool,
* parentEntityTypeList?: string[],
* foreignLinkEntityTypeList?: string[],
* layout?: string,
* layoutForeign?: string,
* entity: string,
* link: string,
* entityForeign?: ?string,
* linkForeign?: ?string,
* label?: string,
* labelForeign?: string,
* linkMultipleField?: bool,
* linkMultipleFieldForeign?: bool,
* audited?: bool,
* auditedForeign?: bool,
* parentEntityTypeList?: string[],
* foreignLinkEntityTypeList?: string[],
* layout?: string,
* layoutForeign?: string,
* selectFilter?: string,
* selectFilterForeign?: string,
* } $params
*/
@@ -398,7 +413,7 @@ class EntityManager
$params = [];
foreach ($paramList as $item) {
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
$params[$item] = htmlspecialchars($data[$item]);
}
/**
+24 -9
View File
@@ -29,6 +29,7 @@
namespace Espo\Controllers;
use Espo\Core\Utils\Config;
use Espo\Entities\User;
use Espo\Tools\FieldManager\FieldManager as FieldManagerTool;
use Espo\Core\Api\Request;
@@ -38,6 +39,9 @@ use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
/**
* @noinspection PhpUnused
*/
class FieldManager
{
/**
@@ -46,7 +50,8 @@ class FieldManager
public function __construct(
private User $user,
private DataManager $dataManager,
private FieldManagerTool $fieldManagerTool
private FieldManagerTool $fieldManagerTool,
private Config $config
) {
$this->checkControllerAccess();
}
@@ -97,18 +102,18 @@ class FieldManager
$fieldManagerTool = $this->fieldManagerTool;
$fieldManagerTool->create($scope, $name, get_object_vars($data));
$name = $fieldManagerTool->create($scope, $name, get_object_vars($data));
try {
$this->dataManager->rebuild([$scope]);
$this->rebuild($scope);
}
catch (Error $e) {
$fieldManagerTool->delete($scope, $data->name);
$fieldManagerTool->delete($scope, $name);
throw new Error($e->getMessage());
}
return $fieldManagerTool->read($scope, $data->name);
return $fieldManagerTool->read($scope, $name);
}
/**
@@ -142,7 +147,7 @@ class FieldManager
$fieldManagerTool->update($scope, $name, get_object_vars($data));
if ($fieldManagerTool->isChanged()) {
$this->dataManager->rebuild([$scope]);
$this->rebuild($scope);
} else {
$this->dataManager->clearCache();
}
@@ -163,12 +168,12 @@ class FieldManager
throw new BadRequest();
}
$result = $this->fieldManagerTool->delete($scope, $name);
$this->fieldManagerTool->delete($scope, $name);
$this->dataManager->clearCache();
$this->dataManager->rebuildMetadata();
return $result;
return true;
}
/**
@@ -192,8 +197,18 @@ class FieldManager
$this->fieldManagerTool->resetToDefault($scope, $name);
$this->dataManager->rebuild([$scope]);
$this->rebuild($scope);
return true;
}
/**
* @throws Error
*/
private function rebuild(string $scope): void
{
$argument = $this->config->get('database.platform') === 'Postgresql' ? null : [$scope];
$this->dataManager->rebuild($argument);
}
}
+1 -3
View File
@@ -29,8 +29,6 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\NotFound;
@@ -97,7 +95,7 @@ class LeadCapture extends Record
/**
* @throws BadRequest
* @throws NotFound
* @throws ForbiddenSilent
* @throws Forbidden
*/
public function postActionGenerateNewApiKey(Request $request): stdClass
{
+13 -4
View File
@@ -29,8 +29,10 @@
namespace Espo\Controllers;
use Espo\Core\Api\Response;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Api\Request;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Metadata as MetadataUtil;
use Espo\Entities\User as UserEntity;
use Espo\Tools\App\MetadataService as Service;
@@ -53,16 +55,21 @@ class Metadata
$this->user = $user;
}
public function getActionRead(): stdClass
public function getActionRead(Request $request): mixed
{
$key = $request->getQueryParam('key');
if (is_string($key)) {
return $this->service->getDataForFrontendByKey($key);
}
return $this->service->getDataForFrontend();
}
/**
* @return mixed
* @throws Forbidden
*/
public function getActionGet(Request $request)
public function getActionGet(Request $request, Response $response): void
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
@@ -70,6 +77,8 @@ class Metadata
$key = $request->getQueryParam('key');
return $this->metadata->get($key, false);
$value = $this->metadata->get($key);
$response->writeBody(Json::encode($value));
}
}
+45 -11
View File
@@ -41,22 +41,18 @@ use Espo\Core\Select\Where\Item as WhereItem;
use Espo\Entities\User as UserEntity;
use Espo\Tools\Stream\RecordService;
use Espo\Tools\Stream\UserRecordService;
use stdClass;
class Stream
{
public static string $defaultAction = 'list';
private RecordService $service;
private SearchParamsFetcher $searchParamsFetcher;
public function __construct(
RecordService $service,
SearchParamsFetcher $searchParamsFetcher
) {
$this->service = $service;
$this->searchParamsFetcher = $searchParamsFetcher;
}
private RecordService $service,
private UserRecordService $userRecordService,
private SearchParamsFetcher $searchParamsFetcher
) {}
/**
* @throws BadRequest
@@ -79,7 +75,7 @@ class Stream
$searchParams = $this->fetchSearchParams($request);
$result = $scope === UserEntity::ENTITY_TYPE ?
$this->service->findUser($id, $searchParams) :
$this->userRecordService->find($id, $searchParams) :
$this->service->find($scope, $id ?? '', $searchParams);
return (object) [
@@ -110,7 +106,7 @@ class Stream
->withPrimaryFilter('posts');
$result = $scope === UserEntity::ENTITY_TYPE ?
$this->service->findUser($id, $searchParams) :
$this->userRecordService->find($id, $searchParams) :
$this->service->find($scope, $id ?? '', $searchParams);
return (object) [
@@ -119,6 +115,30 @@ class Stream
];
}
/**
* @throws BadRequest
* @throws Forbidden
* @throws NotFound
*/
public function getActionListUpdates(Request $request): stdClass
{
$id = $request->getRouteParam('id');
$scope = $request->getRouteParam('scope');
if ($scope === null || $id === null) {
throw new BadRequest();
}
$searchParams = $this->fetchSearchParams($request);
$result = $this->service->findUpdates($scope, $id, $searchParams);
return (object) [
'total' => $result->getTotal(),
'list' => $result->getValueMapList(),
];
}
/**
* @throws BadRequest
* @throws Forbidden
@@ -150,6 +170,20 @@ class Stream
$searchParams = $searchParams->withBoolFilterAdded('skipOwn');
}
$beforeNumber = $request->getQueryParam('beforeNumber');
if ($beforeNumber) {
$searchParams = $searchParams
->withWhereAdded(
WhereItem
::createBuilder()
->setAttribute('number')
->setType(WhereItem\Type::LESS_THAN)
->setValue($beforeNumber)
->build()
);
}
return $searchParams;
}
}
@@ -0,0 +1,61 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use Espo\Core\Exceptions\Forbidden;
use stdClass;
/**
* @noinspection PhpUnused
*/
class WebhookQueueItem extends RecordBase
{
protected function checkAccess(): bool
{
if (!$this->user->isAdmin()) {
return false;
}
return true;
}
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
}
+7
View File
@@ -60,6 +60,7 @@ class Acl
* Get an access level for a specific scope and action.
*
* @param Table::ACTION_* $action
* @noinspection PhpDocSignatureInspection
*/
public function getLevel(string $scope, string $action): string
{
@@ -158,6 +159,7 @@ class Acl
*/
public function checkEntityRead(Entity $entity): bool
{
/** @noinspection PhpRedundantOptionalArgumentInspection */
return $this->checkEntity($entity, Table::ACTION_READ);
}
@@ -215,6 +217,7 @@ class Acl
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenAttributeList(
string $scope,
@@ -232,6 +235,7 @@ class Acl
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenFieldList(
string $scope,
@@ -250,6 +254,7 @@ class Acl
* @param string $field A field to check.
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @return bool
* @noinspection PhpDocSignatureInspection
*/
public function checkField(string $scope, string $field, string $action = Table::ACTION_READ): bool
{
@@ -262,6 +267,7 @@ class Acl
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenLinkList(
string $scope,
@@ -354,6 +360,7 @@ class Acl
*/
public function checkUser(string $permission, User $entity): bool
{
/** @noinspection PhpDeprecationInspection */
return $this->aclManager->checkUser($this->user, $permission, $entity);
}
}
+39 -48
View File
@@ -31,9 +31,7 @@ namespace Espo\Core;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
use Espo\Core\Acl\AccessChecker;
use Espo\Core\Acl\AccessChecker\AccessCheckerFactory;
use Espo\Core\Acl\AccessCreateChecker;
@@ -184,6 +182,7 @@ class AclManager
* Get an access level for a specific scope and action.
*
* @param Table::ACTION_* $action
* @noinspection PhpDocSignatureInspection
*/
public function getLevel(User $user, string $scope, string $action): string
{
@@ -201,7 +200,7 @@ class AclManager
*/
public function getPermissionLevel(User $user, string $permission): string
{
if (substr($permission, -10) === 'Permission') {
if (str_ends_with($permission, 'Permission')) {
$permission = substr($permission, 0, -10);
}
@@ -280,7 +279,7 @@ class AclManager
try {
return $this->check($user, $subject, $action);
}
catch (NotImplemented $e) {
catch (NotImplemented) {
return false;
}
}
@@ -324,7 +323,7 @@ class AclManager
return $checker->checkEntity($user, $entity, $data, $action);
}
throw new NotImplemented("No entity access checker for '{$scope}' action '{$action}'.");
throw new NotImplemented("No entity access checker for '$scope' action '$action'.");
}
/**
@@ -334,6 +333,7 @@ class AclManager
*/
public function checkEntityRead(User $user, Entity $entity): bool
{
/** @noinspection PhpRedundantOptionalArgumentInspection */
return $this->checkEntity($user, $entity, Table::ACTION_READ);
}
@@ -436,7 +436,7 @@ class AclManager
$methodName = 'checkScope';
if (!method_exists($checker, $methodName)) {
throw new NotImplemented("No access checker for '{$scope}' action '{$action}'.");
throw new NotImplemented("No access checker for '$scope' action '$action'.");
}
return $checker->$methodName($user, $data, $action);
@@ -476,6 +476,7 @@ class AclManager
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenAttributeList(
User $user,
@@ -505,6 +506,7 @@ class AclManager
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenFieldList(
User $user,
@@ -534,6 +536,8 @@ class AclManager
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @param string $thresholdLevel Should not be used. Stands for possible future enhancements.
* @return string[]
* @noinspection PhpUnusedParameterInspection
* @noinspection PhpDocSignatureInspection
*/
public function getScopeForbiddenLinkList(
User $user,
@@ -556,6 +560,7 @@ class AclManager
* @param string $field A field to check.
* @param Table::ACTION_READ|Table::ACTION_EDIT $action An action.
* @return bool
* @noinspection PhpDocSignatureInspection
*/
public function checkField(User $user, string $scope, string $field, string $action = Table::ACTION_READ): bool
{
@@ -591,7 +596,6 @@ class AclManager
}
if ($permission === Table::LEVEL_TEAM) {
/** @var string[] $teamIdList */
$teamIdList = $user->getLinkMultipleIdList('teams');
/** @var \Espo\Repositories\User $userRepository */
@@ -622,10 +626,8 @@ class AclManager
{
$className = $this->userAclClassName;
$acl = new $className($this, $user);
/** @var Acl */
return $acl;
return new $className($this, $user);
}
/**
@@ -636,48 +638,40 @@ class AclManager
*/
public function getScopeRestrictedFieldList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
$typeList = !is_array($type) ? [$type] : $type;
$list = [];
$list = [];
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedFieldList($scope, $type)
);
}
return array_unique($list);
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedFieldList($scope, $type)
);
}
return $this->globalRestriction->getScopeRestrictedFieldList($scope, $type);
return array_unique($list);
}
/**
* Get a restricted attribute list for a specific scope by a restriction type.
*
* @param GlobalRestriction::TYPE_*|array<int,GlobalRestriction::TYPE_*> $type
* @param GlobalRestriction::TYPE_*|array<int, GlobalRestriction::TYPE_*> $type
* @return string[]
*/
public function getScopeRestrictedAttributeList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
$typeList = !is_array($type) ? [$type] : $type;
$list = [];
$list = [];
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedAttributeList($scope, $type)
);
}
return array_unique($list);
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedAttributeList($scope, $type)
);
}
return $this->globalRestriction->getScopeRestrictedAttributeList($scope, $type);
return array_unique($list);
}
/**
@@ -688,22 +682,18 @@ class AclManager
*/
public function getScopeRestrictedLinkList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
$typeList = !is_array($type) ? [$type] : $type;
$list = [];
$list = [];
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedLinkList($scope, $type)
);
}
return array_unique($list);
foreach ($typeList as $type) {
$list = array_merge(
$list,
$this->globalRestriction->getScopeRestrictedLinkList($scope, $type)
);
}
return $this->globalRestriction->getScopeRestrictedLinkList($scope, $type);
return array_unique($list);
}
/**
@@ -733,6 +723,7 @@ class AclManager
/**
* @deprecated As of v7.0. Access checkers not to be exposed.
* @noinspection PhpUnused
*/
public function getImplementation(string $scope): object
{
@@ -764,7 +755,7 @@ class AclManager
return false;
}
if ($this->get($user, $permission) === Table::LEVEL_TEAM) {
if ($this->getPermissionLevel($user, $permission) === Table::LEVEL_TEAM) {
if ($target->getId() === $user->getId()) {
return true;
}
@@ -30,6 +30,7 @@
namespace Espo\Core\Api;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
@@ -59,6 +60,7 @@ class ActionHandler implements RequestHandlerInterface
* @throws Forbidden
* @throws Error
* @throws NotFound
* @throws Conflict
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
@@ -93,6 +95,7 @@ class ActionHandler implements RequestHandlerInterface
$response->setHeader('X-App-Timestamp', (string) ($this->config->get('appTimestamp') ?? '0'));
/** @noinspection PhpConditionAlreadyCheckedInspection */
return $response instanceof ResponseWrapper ?
$response->toPsr7() :
self::responseToPsr7($response);
@@ -33,7 +33,6 @@ use Espo\Core\InjectableFactory;
class AuthBuilderFactory
{
public function __construct(private InjectableFactory $injectableFactory)
{}
@@ -156,7 +156,6 @@ class ControllerActionProcessor
$type = $params[0]->getType();
if (
!$type ||
!$type instanceof ReflectionNamedType ||
$type->isBuiltin()
) {
+2 -2
View File
@@ -90,7 +90,7 @@ class ErrorOutput
?string $route = null
): void {
$this->processInternal($request, $response, $exception, $route, false);
$this->processInternal($request, $response, $exception, $route);
}
public function processWithBodyPrinting(
@@ -133,7 +133,7 @@ class ErrorOutput
$logMessageItemList = [];
if ($message) {
$logMessageItemList[] = "{$message}";
$logMessageItemList[] = "$message";
}
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
+1 -1
View File
@@ -32,7 +32,6 @@ namespace Espo\Core\Api;
use Espo\Core\Api\Request as ApiRequest;
use Psr\Http\Message\UriInterface;
use Slim\Psr7\Factory\UriFactory;
use stdClass;
@@ -49,6 +48,7 @@ class RequestNull implements ApiRequest
/**
* @return null
* @noinspection PhpDocSignatureInspection
*/
public function getQueryParam(string $name): ?string
{
@@ -275,6 +275,7 @@ class RequestWrapper implements ApiRequest
return $this->getMethod() === 'PUT';
}
/** @noinspection PhpUnused */
public function isUpdate(): bool
{
return $this->getMethod() === 'UPDATE';
+1 -1
View File
@@ -202,7 +202,7 @@ class RouteProcessor
$action = $crudMethodActionMap[strtolower($method)] ?? null;
if (!$action) {
throw new BadRequest("No action for method `{$method}`.");
throw new BadRequest("No action for method `$method`.");
}
}
@@ -55,7 +55,7 @@ class RunnerRunner
public function run(string $className, ?Params $params = null): void
{
if (!class_exists($className)) {
$this->log->error("Application runner '{$className}' does not exist.");
$this->log->error("Application runner '$className' does not exist.");
throw new RunnerException();
}
@@ -38,14 +38,8 @@ use Espo\Core\Utils\Config;
*/
class Client implements Runner
{
private ClientManager $clientManager;
private Config $config;
public function __construct(ClientManager $clientManager, Config $config)
{
$this->clientManager = $clientManager;
$this->config = $config;
}
public function __construct(private ClientManager $clientManager, private Config $config)
{}
public function run(): void
{
@@ -60,6 +60,9 @@ class PortalClient implements RunnerParameterized
private ErrorOutput $errorOutput
) {}
/**
* @throws BadRequest
*/
public function run(Params $params): void
{
$id = $params->get('id') ??
@@ -43,6 +43,9 @@ class Preload implements Runner
{
use Cli;
/**
* @throws Throwable
*/
public function run(): void
{
$preload = new PreloadUtil();
@@ -59,7 +62,7 @@ class Preload implements Runner
$count = $preload->getCount();
echo "Success." . PHP_EOL;
echo "Files loaded: " . (string) $count . "." . PHP_EOL;
echo "Files loaded: " . $count . "." . PHP_EOL;
}
protected function processException(Throwable $e): void
@@ -69,13 +72,13 @@ class Preload implements Runner
$msg = $e->getMessage();
if ($msg) {
echo "Message: {$msg}" . PHP_EOL;
echo "Message: $msg" . PHP_EOL;
}
$file = $e->getFile();
if ($file) {
echo "File: {$file}" . PHP_EOL;
echo "File: $file" . PHP_EOL;
}
echo "Line: " . $e->getLine() . PHP_EOL;
@@ -41,6 +41,8 @@ use RuntimeException;
* in another storage. E.g. a single Redis data store can be utilized with
* multiple Espo replicas (for scalability purposes).
* Defined at metadata > app > containerServices > authTokenManager.
*
* @noinspection PhpUnused
*/
class EspoManager implements Manager
{
@@ -56,8 +58,7 @@ class EspoManager implements Manager
public function get(string $token): ?AuthToken
{
/** @var ?AuthTokenEntity $authToken */
$authToken = $this->repository
return $this->repository
->select([
'id',
'isActive',
@@ -72,13 +73,10 @@ class EspoManager implements Manager
])
->where(['token' => $token])
->findOne();
return $authToken;
}
public function create(Data $data): AuthToken
{
/** @var AuthTokenEntity $authToken */
$authToken = $this->repository->getNew();
$authToken
@@ -102,6 +100,7 @@ class EspoManager implements Manager
public function inactivate(AuthToken $authToken): void
{
/** @noinspection PhpConditionAlreadyCheckedInspection */
if (!$authToken instanceof AuthTokenEntity) {
throw new RuntimeException();
}
@@ -115,6 +114,7 @@ class EspoManager implements Manager
public function renew(AuthToken $authToken): void
{
/** @noinspection PhpConditionAlreadyCheckedInspection */
if (!$authToken instanceof AuthTokenEntity) {
throw new RuntimeException();
}
@@ -159,11 +159,11 @@ class EspoManager implements Manager
$length = self::TOKEN_RANDOM_LENGTH;
if (function_exists('random_bytes')) {
/** @noinspection PhpUnhandledExceptionInspection */
return bin2hex(random_bytes($length));
}
if (function_exists('openssl_random_pseudo_bytes')) {
/** @var string $randomValue */
$randomValue = openssl_random_pseudo_bytes($length);
return bin2hex($randomValue);
@@ -35,7 +35,7 @@ namespace Espo\Core\Authentication\AuthToken;
interface Manager
{
/**
* Get an auth token. If does not exist then returns NULL.
* Get an auth token. If it does not exist, then returns NULL.
*/
public function get(string $token): ?AuthToken;
@@ -34,20 +34,13 @@ namespace Espo\Core\Authentication;
*/
class AuthenticationData
{
private ?string $username;
private ?string $password;
private ?string $method;
private bool $byTokenOnly = false;
public function __construct(
?string $username = null,
?string $password = null,
?string $method = null
) {
$this->username = $username;
$this->password = $password;
$this->method = $method;
}
private ?string $username = null,
private ?string $password = null,
private ?string $method = null
) {}
public static function create(): self
{
@@ -34,21 +34,13 @@ use Espo\Core\Authentication\Logins\Espo;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use RuntimeException;
class ConfigDataProvider
{
private const FAILED_ATTEMPTS_PERIOD = '60 seconds';
private const MAX_FAILED_ATTEMPT_NUMBER = 10;
private Config $config;
private Metadata $metadata;
public function __construct(Config $config, Metadata $metadata)
{
$this->config = $config;
$this->metadata = $metadata;
}
public function __construct(private Config $config, private Metadata $metadata)
{}
/**
* A period for max failed attempts checking.
@@ -155,16 +147,4 @@ class ConfigDataProvider
return $list;
}
public function getMethodLoginMetadataParams(string $method): MetadataParams
{
/** @var ?array<string, mixed> $data */
$data = $this->metadata->get(['authenticationMethods', $method]);
if ($data === null) {
throw new RuntimeException();
}
return MetadataParams::fromRaw($method, $data);
}
}
@@ -32,60 +32,46 @@ namespace Espo\Core\Authentication\Helper;
use Espo\Core\Authentication\Logins\ApiKey;
use Espo\Core\Authentication\Logins\Hmac;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
class UserFinder
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function __construct(private EntityManager $entityManager)
{}
public function find(string $username, string $hash): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'userName' => $username,
'password' => $hash,
'type!=' => [User::TYPE_API, User::TYPE_SYSTEM],
])
->findOne();
return $user;
}
public function findApiHmac(string $apiKey): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'type' => User::TYPE_API,
'apiKey' => $apiKey,
'authMethod' => Hmac::NAME,
])
->findOne();
return $user;
}
public function findApiApiKey(string $apiKey): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'type' => User::TYPE_API,
'apiKey' => $apiKey,
'authMethod' => ApiKey::NAME,
])
->findOne();
return $user;
}
}
@@ -40,7 +40,12 @@ use Espo\ORM\EntityManager;
use Espo\Entities\AuthLogRecord;
use DateTime;
use Exception;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class FailedAttemptsLimit implements BeforeLogin
{
public function __construct(
@@ -70,7 +75,12 @@ class FailedAttemptsLimit implements BeforeLogin
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
try {
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
}
catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
$ip = $this->util->obtainIpFromRequest($request);
@@ -99,7 +109,7 @@ class FailedAttemptsLimit implements BeforeLogin
return;
}
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '{$ip}'.");
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '$ip'.");
throw new Forbidden("Max failed login attempts exceeded.");
}
@@ -31,21 +31,15 @@ namespace Espo\Core\Authentication\Hook;
use Espo\Core\Utils\Metadata;
use Espo\Core\InjectableFactory;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
use Espo\Core\Authentication\Result;
class Manager
{
private Metadata $metadata;
private InjectableFactory $injectableFactory;
public function __construct(Metadata $metadata, InjectableFactory $injectableFactory)
{
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
public function __construct(private Metadata $metadata, private InjectableFactory $injectableFactory)
{}
public function processBeforeLogin(AuthenticationData $data, Request $request): void
{
@@ -63,7 +63,7 @@ class Hmac implements SignatureVerifier
$this->key = $key;
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
throw new RuntimeException("Unsupported algorithm $algorithm.");
}
}
@@ -70,7 +70,7 @@ class Rsa implements SignatureVerifier
$this->keys = $keys;
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
throw new RuntimeException("Unsupported algorithm $algorithm.");
}
}
@@ -120,6 +120,12 @@ class Rsa implements SignatureVerifier
'e' => new BigInteger('0x' . bin2hex(Util::base64UrlDecode($key->getE())), 16),
]);
return $publicKey->toString('PKCS8');
$pem = $publicKey->toString('PKCS8');
if (!is_string($pem)) {
throw new RuntimeException();
}
return $pem;
}
}
@@ -72,7 +72,7 @@ class Header
try {
$parsed = Json::decode($raw);
}
catch (JsonException $e) {}
catch (JsonException) {}
if (!$parsed instanceof stdClass) {
throw new RuntimeException();
@@ -88,23 +88,25 @@ class Header
);
}
/** @noinspection PhpSameParameterValueInspection */
private static function obtainFromParsedString(stdClass $parsed, string $name): string
{
$value = $parsed->$name ?? null;
if (!is_string($value)) {
throw new RuntimeException("No or bad `{$name}` in JWT header.");
throw new RuntimeException("No or bad `$name` in JWT header.");
}
return $value;
}
/** @noinspection PhpSameParameterValueInspection */
private static function obtainFromParsedStringNull(stdClass $parsed, string $name): ?string
{
$value = $parsed->$name ?? null;
if ($value !== null && !is_string($value)) {
throw new RuntimeException("Bad `{$name}` in JWT header.");
throw new RuntimeException("Bad `$name` in JWT header.");
}
return $value;
@@ -123,6 +123,7 @@ class Payload
return $this->authTime;
}
/** @noinspection PhpUnused */
public function getSid(): ?string
{
return $this->sid;
@@ -143,7 +144,7 @@ class Payload
try {
$parsed = Json::decode($raw);
}
catch (JsonException $e) {}
catch (JsonException) {}
if (!$parsed instanceof stdClass) {
throw new RuntimeException();
@@ -29,4 +29,6 @@
namespace Espo\Core\Authentication\Ldap;
class Client extends \Laminas\Ldap\Ldap {}
use Laminas\Ldap\Ldap;
class Client extends Ldap {}
@@ -29,11 +29,13 @@
namespace Espo\Core\Authentication\Ldap;
use Laminas\Ldap\Exception\LdapException;
class ClientFactory
{
/**
* @param array<string, mixed> $options
* @throws \Laminas\Ldap\Exception\LdapException
* @throws LdapException
*/
public function create(array $options): Client
{
@@ -52,7 +52,12 @@ use Espo\Core\Utils\Log;
use Espo\Core\Utils\PasswordHash;
use Espo\Entities\User;
use Exception;
use Laminas\Ldap\Exception\LdapException;
use Laminas\Ldap\Ldap;
/**
* @noinspection PhpUnused
*/
class LdapLogin implements Login
{
private LDAPUtils $utils;
@@ -81,6 +86,7 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $ldapFieldMap = [
'userName' => 'userNameAttribute',
@@ -93,6 +99,7 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $userFieldMap = [
'teamsIds' => 'userTeamsIds',
@@ -101,12 +108,16 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $portalUserFieldMap = [
'portalsIds' => 'portalUserPortalsIds',
'portalRolesIds' => 'portalUserRolesIds',
];
/**
* @throws LdapException
*/
public function login(Data $data, Request $request): Result
{
$username = $data->getUsername();
@@ -219,7 +230,7 @@ class LdapLogin implements Login
if (!isset($user)) {
if (!$this->utils->getOption('createEspoUser')) {
$this->log->warning(
"LDAP: Authentication success for user {$username}, but user is not created in EspoCRM."
"LDAP: Authentication success for user $username, but user is not created in EspoCRM."
);
return Result::fail(FailReason::USER_NOT_FOUND);
@@ -369,13 +380,14 @@ class LdapLogin implements Login
$user->setAsNotNew();
$user->updateFetchedValues();
/** @var ?User */
return $this->entityManager->getEntityById(User::ENTITY_TYPE, $user->getId());
}
/**
* Find LDAP user DN by his username.
*
* @throws \Laminas\Ldap\Exception\LdapException
* @throws LdapException
*/
private function findLdapUserDnByUsername(string $username): ?string
{
@@ -395,7 +407,8 @@ class LdapLogin implements Login
$loginFilterString . ')';
/** @var array<int, array{dn: string}> $result */
$result = $ldapClient->search($searchString, null, Client::SEARCH_SCOPE_SUB);
/** @noinspection PhpRedundantOptionalArgumentInspection */
$result = $ldapClient->search($searchString, null, Ldap::SEARCH_SCOPE_SUB);
$this->log->debug('LDAP: user search string: "' . $searchString . '"');
@@ -413,11 +426,11 @@ class LdapLogin implements Login
{
$filter = trim($filter);
if (substr($filter, 0, 1) != '(') {
if (!str_starts_with($filter, '(')) {
$filter = '(' . $filter;
}
if (substr($filter, -1) != ')') {
if (!str_ends_with($filter, ')')) {
$filter = $filter . ')';
}
@@ -188,8 +188,6 @@ class Utils
{
$options = $this->getOptions();
$zendOptions = array_diff_key($options, array_flip($this->permittedEspoOptions));
return $zendOptions;
return array_diff_key($options, array_flip($this->permittedEspoOptions));
}
}

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