Compare commits

...

750 Commits

Author SHA1 Message Date
Yuri Kuznetsov 7d0925de85 9.1.8 2025-07-25 14:02:42 +03:00
Yuri Kuznetsov 5d0350ef64 bool field web form 2025-07-25 11:33:15 +03:00
Yuri Kuznetsov d649181964 field loaders in the end 2025-07-25 09:47:10 +03:00
Yuri Kuznetsov 1c207e4445 fix avatar color 2025-07-24 11:51:06 +03:00
Yuri Kuznetsov dd8c584f6f jp_JP language 2025-07-24 08:49:20 +03:00
Yuri Kuznetsov 782f5e6b20 fix campaign stats query 2025-07-23 11:26:32 +03:00
Yuri Kuznetsov 4a890e777c campaign stats sql fix 2025-07-23 10:37:33 +03:00
Yuri Kuznetsov 370909de96 default side panel layout in layout set 2025-07-21 10:37:48 +03:00
Yuri Kuznetsov 7f8b299015 bg lang 2025-07-14 11:30:44 +03:00
Yuri Kuznetsov 9c1e52f3bf fix test 2025-07-10 16:41:06 +03:00
Yuri Kuznetsov 5b7a4ec8f0 email: skip html to markdown if no html 2025-07-10 16:05:21 +03:00
Yuri Kuznetsov 19a09cc6d1 sendbox ui impr 2025-07-10 15:53:50 +03:00
Yuri Kuznetsov 889a8bfc90 forbid access to system requirements 2025-07-10 15:37:00 +03:00
Yuri Kuznetsov 9e4e461fd0 fix css 2025-07-10 14:49:48 +03:00
Yuri Kuznetsov e3075abe4f selectize small fix 2025-07-10 13:37:40 +03:00
Yuri Kuznetsov 81ef42118a small multiselect css 2025-07-10 13:21:16 +03:00
Yuri Kuznetsov 72a069a1db ws reconnect impr 2025-07-05 12:09:28 +03:00
Yuri Kuznetsov 492d9abb25 document accounts allow direct access 2025-07-01 10:48:50 +03:00
Yuri Kuznetsov 4baab4dff4 9.1.7 2025-06-24 14:39:53 +03:00
Yuri Kuznetsov 9c9f35b33a fix type 2025-06-24 12:51:45 +03:00
Yuri Kuznetsov b31b71756b fix time zone list in import 2025-06-24 12:44:04 +03:00
Yuri Kuznetsov 285f0bc011 cleanup 2025-06-24 12:41:20 +03:00
Yuri Kuznetsov 4e08872f68 ref 2025-06-24 12:40:44 +03:00
Yuri Kuznetsov 929611f317 throw bad request on uri starting with double slash 2025-06-24 12:34:23 +03:00
Yuri Kuznetsov a2fce2d425 fr lang additions 2025-06-24 10:01:46 +03:00
Yuri Kuznetsov ca98fb39af fix import error access filter 2025-06-24 09:22:46 +03:00
Yuri Kuznetsov 97e5aa8f90 ref 2025-06-23 19:46:42 +03:00
Yuri Kuznetsov 8649f1ac0c fix ldap query 2025-06-23 19:41:34 +03:00
Yuri Kuznetsov c2f4afd520 entity factory create with additional attributes 2025-06-23 18:13:59 +03:00
Yuri Kuznetsov 4a38109d10 update cz 2025-06-22 14:12:11 +03:00
Yuri Kuznetsov cb6d806d89 update fr 2025-06-22 14:10:05 +03:00
Yuri Kuznetsov 709e5b7e62 fix list view with filter 2025-06-21 18:15:03 +03:00
Yuri Kuznetsov d00af77206 fix default currency 2025-06-20 09:32:47 +03:00
Yuri Kuznetsov d60a49a1bf css fix 2025-06-16 15:59:16 +03:00
Yuri Kuznetsov bd33d70365 fix list model removal recovery 2025-06-16 11:22:40 +03:00
Yuri Kuznetsov 26c2234ead docs 2025-06-16 11:22:10 +03:00
Yuri Kuznetsov 8cfc2321d1 composer email modal collapse not leave out 2025-06-16 10:44:16 +03:00
Yuri Kuznetsov 133d6144ad 9.1.6 2025-06-16 10:08:04 +03:00
Yuri Kuznetsov fe8bbb0d2e fix record grid shrink 2025-06-15 15:31:47 +03:00
Yuri Kuznetsov d406a7d78c collaborators own autocomplete 2025-06-15 13:34:18 +03:00
Yuri Kuznetsov fabbacdcc1 comment 2025-06-14 11:07:23 +03:00
Yuri Kuznetsov 809defb24a fix reduce usage 2025-06-14 10:16:27 +03:00
Yuri Kuznetsov 72f9ff1a4a fix multi-enum n^2 2025-06-13 21:13:23 +03:00
Yuri Kuznetsov 923bb27a93 fix pagination lost after full edit view 2025-06-13 13:38:14 +03:00
Yuri Kuznetsov 38f8fedec8 confirm cancel callback 2025-06-13 13:29:25 +03:00
Eymen Elkum a94fb01ceb fix: remove usage of non-existent 'hash' field (#3399) 2025-06-12 12:40:53 +03:00
Yuri Kuznetsov a90cd8a718 fix any of varchar 2025-06-11 16:55:38 +03:00
Yuri Kuznetsov e091db9063 reactions style change 2025-06-10 19:36:44 +03:00
Yuri Kuznetsov 4f9cfb487f cleanup 2025-06-10 19:26:42 +03:00
Yuri Kuznetsov 5f1a0f4f75 css likes fix 2025-06-10 19:26:05 +03:00
Yuri Kuznetsov dc18d5166f user default color 2025-06-10 18:53:54 +03:00
Yuri Kuznetsov 859f0c970b fix markdown list regexp 2025-06-10 11:18:05 +03:00
Yuri Kuznetsov 4f574abfe7 improve show password ui 2025-06-09 16:26:30 +03:00
Yuri Kuznetsov 042b15ae45 focus on toggle 2025-06-09 16:16:08 +03:00
Yuri Kuznetsov ccccc13662 fix id 2025-06-09 16:08:32 +03:00
Yuri Kuznetsov 3008f47620 login form view password 2025-06-09 16:07:11 +03:00
Yuri Kuznetsov 34df8c5f63 orm: support subquery in join on, ref 2025-06-09 13:46:00 +03:00
Yuri Kuznetsov 8feaadc162 calendar reRender date fix 2025-06-08 20:07:09 +03:00
Yuri Kuznetsov bf95f79775 distinct usage removal 2025-06-08 19:40:11 +03:00
Yuri Kuznetsov f2c4e0931d ref 2025-06-08 19:35:37 +03:00
Yuri Kuznetsov 9982ed0bef distinct usage rmoval 2025-06-08 19:21:27 +03:00
Yuri Kuznetsov cd860cb7d0 ref, getting rid of distinct 2025-06-08 19:09:00 +03:00
Yuri Kuznetsov 22f7d2a8b0 comment 2025-06-08 19:08:22 +03:00
Yuri Kuznetsov e37d35bc9b ref, comments 2025-06-08 10:58:44 +03:00
Yuri Kuznetsov 4ef5f24212 fix calendar link 2025-06-07 23:54:53 +03:00
Yuri Kuznetsov 6aebcaed97 lateral join 2025-06-07 20:34:31 +03:00
Yuri Kuznetsov d35f00b2cb orm: ANY_VALUE function 2025-06-07 13:33:05 +03:00
Yuri Kuznetsov 999f6f5593 status style 2025-06-06 20:44:36 +03:00
Yuri Kuznetsov f92e261ea8 fix inline attachment UUID compatibility 2025-06-05 22:09:08 +03:00
Yuri Kuznetsov ba03bfa6da schema 2025-06-04 15:17:21 +03:00
Yuri Kuznetsov bd3e06d403 syckBackWithModel param 2025-06-04 10:16:25 +03:00
Yuri Kuznetsov bf43ab05ff fix test 2025-06-02 19:04:18 +03:00
Yuri Kuznetsov 8d81ac3acb fix or helper 2025-06-02 18:49:02 +03:00
Yuri Kuznetsov 2d0255909e disable activities dashlet in portal 2025-06-02 10:08:25 +03:00
Yuri Kuznetsov 2634bb7208 add columnIsNull filter 2025-06-02 09:47:13 +03:00
Yuri Kuznetsov a4c80bf498 type 2025-06-01 12:19:10 +03:00
Yuri Kuznetsov eb19cd14b5 bundled extension support in developer mode 2025-05-31 11:37:34 +03:00
Yuri Kuznetsov c227fe7c34 pdf template default style 2025-05-29 18:54:58 +03:00
Yuri Kuznetsov 1fc92333dd autocomplete scroll fix 2025-05-29 13:51:03 +03:00
Yuri Kuznetsov 47c1a6fd05 dynamic logic field ref 2025-05-29 13:07:29 +03:00
Yuri Kuznetsov acf5c2984d 9.1.5 2025-05-29 10:00:56 +03:00
Yuri Kuznetsov 98eb304903 fix pgsql interval 2025-05-29 09:38:16 +03:00
Yuri Kuznetsov 1b385f987b js compress to support ios 2025-05-28 17:19:01 +03:00
Yuri Kuznetsov 34c1cdbe86 less js compress 2025-05-28 16:40:36 +03:00
Yuri Kuznetsov 8563945199 modal list without buttons sticky header fix 2025-05-28 12:26:17 +03:00
Yuri Kuznetsov 162e05449c field view: fromField handling 2025-05-28 12:00:59 +03:00
Yuri Kuznetsov 0b6fd83b14 9.1.4 2025-05-27 09:21:27 +03:00
Yuri Kuznetsov 3631f17a77 fix ff dd shadows 2025-05-26 20:51:32 +03:00
Yuri Kuznetsov 4100af5ad3 fix sa 2025-05-26 15:32:39 +03:00
Yuri Kuznetsov b16024f205 error ref 2025-05-26 15:22:13 +03:00
Yuri Kuznetsov a0dbef3b78 cleanup 2025-05-26 12:12:04 +03:00
Yuri Kuznetsov 12c24098c3 update frontend build tools 2025-05-26 12:06:00 +03:00
Yuri Kuznetsov 8d03944e68 schema docs fix 2025-05-25 11:35:28 +03:00
Yuri Kuznetsov 3b5207871b link field fromField usage 2025-05-24 17:04:17 +03:00
Yuri Kuznetsov 0f2689deee fix kb article move 2025-05-24 13:53:01 +03:00
Yuri Kuznetsov 37d437dbae ref 2025-05-24 11:42:07 +03:00
Yuri Kuznetsov bfe109e9e4 stream user link color 2025-05-24 09:20:54 +03:00
Yuri Kuznetsov d8f7127528 comment 2025-05-23 20:06:43 +03:00
Yuri Kuznetsov 986af05002 fix user detail layout 2025-05-23 16:17:58 +03:00
Yuri Kuznetsov bdbfbdabf1 complex text table css fix 2025-05-23 11:24:30 +03:00
Yuri Kuznetsov 04f74150ac complex text table style 2025-05-23 11:20:18 +03:00
Yuri Kuznetsov 74ce903cb7 element check 2025-05-23 09:19:14 +03:00
Yuri Kuznetsov 666dd116bd link one empty search fix 2025-05-20 23:15:09 +03:00
Yuri Kuznetsov e0d262e733 portal user same account select 2025-05-20 23:05:57 +03:00
Yuri Kuznetsov 128d7ec94d 9.1.3 2025-05-20 14:58:12 +03:00
Yuri Kuznetsov e0277038cd fix email store 2025-05-20 13:57:55 +03:00
Yuri Kuznetsov 845a50a99f fix tree list view open link 2025-05-18 13:07:34 +03:00
Yuri Kuznetsov a30a353156 markdown match fix 2025-05-17 11:33:15 +03:00
Yuri Kuznetsov 27a7e2f6f5 mass remove form web hook queue 2025-05-16 20:48:49 +03:00
Yuri Kuznetsov febf78dab6 webhook in menu 2025-05-16 15:49:09 +03:00
Yuri Kuznetsov fab0efa5e5 webhook event queue item list view 2025-05-16 15:47:48 +03:00
Yuri Kuznetsov 72794768af webhook queue items panel 2025-05-16 15:33:27 +03:00
Yuri Kuznetsov 75c71d4b28 skip starred and followers in webhook 2025-05-16 15:20:32 +03:00
Yuri Kuznetsov 81364eefb3 layout change 2025-05-16 15:17:01 +03:00
Yuri Kuznetsov 07a09dd08e email sender: reply-to after addresses 2025-05-15 19:56:36 +03:00
SuchAFuriousDeath 673370b9a5 fixes TIMESTAMPDIFF_MONTH error for postgresql (#3369) 2025-05-15 08:54:12 +03:00
Yuri Kuznetsov c398ab0a40 text field: prevent scroll chrome woe with other solution 2025-05-14 19:24:12 +03:00
Yuri Kuznetsov 86f3801513 use setRangeText 2025-05-14 19:08:06 +03:00
Yuri Kuznetsov 678be304c5 cleanup 2025-05-14 18:32:01 +03:00
Yuri Kuznetsov 0243a2a5a0 9.1.2 2025-05-14 17:04:33 +03:00
Yuri Kuznetsov 15d95ddac9 select single category fix 2025-05-14 15:48:39 +03:00
Yuri Kuznetsov 33a15bdbd6 attachment file name fix 2025-05-14 14:09:51 +03:00
Yuri Kuznetsov b71a237cad postgresql delete alias fix 2025-05-14 13:58:35 +03:00
Yuri Kuznetsov 11d9d2aee4 autocomplete max height 2025-05-13 22:12:31 +03:00
Yuri Kuznetsov 6a1a3b67c8 fix lead catpture list layout 2025-05-13 15:19:37 +03:00
Yuri Kuznetsov d1bb6be354 fix record panels container multiple 2025-05-13 13:26:48 +03:00
Yuri Kuznetsov 55a387c785 fix 2025-05-13 12:49:23 +03:00
Yuri Kuznetsov 6efa058293 skip re-render email body on quick save 2025-05-13 12:00:49 +03:00
Yuri Kuznetsov 8f054a475c compose initial fields ref 2025-05-13 12:00:29 +03:00
Yuri Kuznetsov 5b1919b760 google map id 2025-05-12 17:36:42 +03:00
Yuri Kuznetsov 50ded4b933 new google maps api 2025-05-12 17:22:45 +03:00
Yuri Kuznetsov c4b0629e4f google maps warning fix 2025-05-12 17:15:35 +03:00
Yuri Kuznetsov 3d5991495b 9.1.1 2025-05-12 10:13:54 +03:00
Yuri Kuznetsov 1ee853890c pgsql TZ fix 2025-05-11 19:00:35 +03:00
Yuri Kuznetsov 400347740d comment 2025-05-11 13:45:47 +03:00
Yuri Kuznetsov eae84b9d8f foreign-field params helper 2025-05-11 13:45:02 +03:00
Yuri Kuznetsov 1ba4937f32 phone foreign disable numeric search 2025-05-11 13:44:51 +03:00
Yuri Kuznetsov 205091dc6f change email template order 2025-05-11 12:41:45 +03:00
Yuri Kuznetsov d704d089a3 lang fix 2025-05-11 12:41:30 +03:00
Yuri Kuznetsov 4cf6e4f9d3 email template text search only name 2025-05-11 12:32:29 +03:00
Yuri Kuznetsov 88565e2e19 address field metadata independent 2025-05-10 17:45:46 +03:00
Yuri Kuznetsov 3a295bcbad fix select record tree item 2025-05-10 10:00:18 +03:00
Yuri Kuznetsov 3cf87f8bd9 uploaded image preview in full size 2025-05-09 18:16:21 +03:00
Yuri Kuznetsov 469f0e1913 assigned users avatars in list mode 2025-05-08 10:12:38 +03:00
Yuri Kuznetsov 6e8e2abd5f type guard 2025-05-07 11:45:16 +03:00
Yuri Kuznetsov a7016ca153 kanban starred 2025-05-07 10:08:18 +03:00
Yuri Kuznetsov 7359367569 websocket reconnect subscribe 2025-05-07 09:48:47 +03:00
Yuri Kuznetsov e394779737 markdown use custom tokenizer to escape html tags 2025-05-07 08:58:14 +03:00
Yuri Kuznetsov 9bb1b1a36c cleanup 2025-05-06 23:04:30 +03:00
Yuri Kuznetsov 7ac977ef6c change layout order 2025-05-06 21:12:02 +03:00
Yuri Kuznetsov aabe966bcf Netowrk error message 2025-05-06 20:15:53 +03:00
Yuri Kuznetsov ffbe6f5efa email list detail sync 2025-05-06 18:11:08 +03:00
Yuri Kuznetsov 68f568949e fix list remove sync 2025-05-06 17:45:38 +03:00
Yuri Kuznetsov 3fbaeab264 fix convert lead leav out confirm 2025-05-06 17:04:14 +03:00
Yuri Kuznetsov bc0bcf3594 fix notify 2025-05-06 16:58:07 +03:00
Yuri Kuznetsov 0258f5e8b1 fix email to lead/contact js error 2025-05-06 10:50:38 +03:00
Yuri Kuznetsov 5e2ba0d28e type fix 2025-05-06 10:47:19 +03:00
Yuri Kuznetsov ed03e6637f search on mobile fix 2025-05-05 20:30:09 +03:00
Yuri Kuznetsov ccb17b1170 fix 2025-05-05 13:18:28 +03:00
Yuri Kuznetsov 8ce3f4dcc1 ref 2025-05-05 13:17:11 +03:00
Yuri Kuznetsov dbf8f15333 attribute details css fix 2025-05-05 12:35:30 +03:00
Yuri Kuznetsov 51d838e1b2 enum field details type 2025-05-05 12:26:22 +03:00
Yuri Kuznetsov c5381488ce lang 2025-05-05 12:02:09 +03:00
Yuri Kuznetsov 662b63e47b fix 2025-05-05 11:29:27 +03:00
Yuri Kuznetsov 3e8459d5c2 version 2025-05-05 10:09:01 +03:00
Yuri Kuznetsov dcf0d4265b comments 2025-05-03 17:16:22 +03:00
Yuri Kuznetsov ef3330d0fb ref 2025-05-03 17:14:32 +03:00
Yuri Kuznetsov def0bbd55e ref 2025-05-03 17:11:37 +03:00
Yuri Kuznetsov 44c3aa0f19 dd small max height fix 2025-05-03 10:40:03 +03:00
Yuri Kuznetsov 96c8382143 dropdown with checkbox css fix 2025-05-02 16:40:14 +03:00
Yuri Kuznetsov 6e980eaea9 role table fix 2025-05-02 09:22:05 +03:00
Yuri Kuznetsov 42b703204a email template placeholders for fields with loaders 2025-05-01 18:56:42 +03:00
Yuri Kuznetsov 5ebad1c816 data ref 2025-05-01 18:26:52 +03:00
Yuri Kuznetsov 4e230845f9 template placeholder variables loader 2025-05-01 18:26:10 +03:00
Yuri Kuznetsov 8855161518 inline edit css fix 2025-05-01 17:41:48 +03:00
Yuri Kuznetsov 4418133ec0 entity defs field loaders 2025-05-01 17:22:13 +03:00
Yuri Kuznetsov 5b9ac837dd mass email information send opt out ignore 2025-05-01 14:11:29 +03:00
Yuri Kuznetsov 2a570dd573 mass email invalid check 2025-05-01 14:01:59 +03:00
Yuri Kuznetsov a2e34ff6ce inline edit css fix 2025-05-01 13:12:05 +03:00
Yuri Kuznetsov d75ff87824 datepicker fix 2025-04-30 19:38:27 +03:00
Yuri Kuznetsov 9fd4d78485 after render fix 2025-04-30 18:52:05 +03:00
Yuri Kuznetsov b9a5f1cbff email subject fix 2025-04-30 17:07:01 +03:00
Yuri Kuznetsov 123605c442 add field to layout 2025-04-30 13:14:50 +03:00
Yuri Kuznetsov 0c539faaee layout manager hidden visual cue 2025-04-30 10:03:02 +03:00
Yuri Kuznetsov a571e283d3 kanban: revert record after move error 2025-04-30 09:52:24 +03:00
Yuri Kuznetsov 0a17b974c9 dropdown overflow fix 2025-04-30 09:43:08 +03:00
Yuri Kuznetsov cf3497c817 email listview attachment icon fix 2025-04-28 09:58:47 +03:00
Yuri Kuznetsov 22665ee951 label 2025-04-28 09:14:58 +03:00
Yuri Kuznetsov 416adafaa5 email: disable text search for fields 2025-04-28 09:12:24 +03:00
Yuri Kuznetsov 96bf57fea7 dropdown fix 2025-04-25 19:44:56 +03:00
SuchAFuriousDeath a41374ef74 Remove double semicolons (#3345)
* Remove double semicolons

* fix formatting

* fix formatting 2
2025-04-23 14:08:39 +03:00
Yuri Kuznetsov e2800c7dbf additional icon classes in metadata 2025-04-23 10:03:57 +03:00
Yuri Kuznetsov 8a8b800302 group folder do not list sent from group accounts 2025-04-22 18:50:04 +03:00
Yuri Kuznetsov 48ed42c29c form label CSS fix 2025-04-22 13:13:48 +03:00
Yuri Kuznetsov d8722a4087 attachment search by ID 2025-04-22 09:27:18 +03:00
Yuri Kuznetsov ac7eb466fc Merge branch 'fix' 2025-04-21 11:19:39 +03:00
Yuri Kuznetsov 839c31dbb8 before-upgrade version check 2025-04-21 11:19:30 +03:00
Yuri Kuznetsov dbaa7adf20 update frontend timezones 2025-04-21 10:20:47 +03:00
Yuri Kuznetsov 98628a273b css fix 2025-04-19 19:31:07 +03:00
Yuri Kuznetsov 90bcd57d2c dropdown out of screen fix 2025-04-19 19:29:12 +03:00
Yuri Kuznetsov e1acf4a2b8 create save context on save 2025-04-19 10:52:56 +03:00
Yuri Kuznetsov 449a8fcbb4 fix multi-enum bc 2025-04-18 16:51:03 +03:00
Yuri Kuznetsov 268615d8fe oidc userinfo support 2025-04-17 14:13:50 +03:00
Yuri Kuznetsov 2b98eef915 remove resource endpoint field 2025-04-17 10:45:42 +03:00
Yuri Kuznetsov 39e7b397e9 hide field 2025-04-17 10:36:15 +03:00
Yuri Kuznetsov 16576623f6 document accounts field metadata change 2025-04-17 10:12:11 +03:00
Yuri Kuznetsov cc5c979af6 update bullbone 2025-04-16 21:00:55 +03:00
Yuri Kuznetsov 3fd33d3269 view stream attachments 2025-04-16 20:46:17 +03:00
Yuri Kuznetsov 39cefe99e6 cs 2025-04-16 18:52:15 +03:00
Yuri Kuznetsov d8d9c3eb66 use cleanupAppLog 2025-04-16 17:34:45 +03:00
Yuri Kuznetsov 1502361fa3 cleanup improvements 2025-04-16 13:15:50 +03:00
Yuri Kuznetsov 61b50b5288 fix typo 2025-04-15 19:38:01 +03:00
Yuri Kuznetsov 2303a81751 attachments field search fix 2025-04-15 19:17:40 +03:00
Yuri Kuznetsov 0e7c405862 empty working range 2025-04-15 18:09:42 +03:00
Yuri Kuznetsov 23eb78d5c3 action sync listener fix 2025-04-15 14:12:02 +03:00
Yuri Kuznetsov 5e2c134194 Merge branch 'fix' 2025-04-15 10:05:42 +03:00
Yuri Kuznetsov 3dcbff1d08 9.0.8 2025-04-15 09:53:20 +03:00
Yuri Kuznetsov 4559f2746e record collection getApiOutput method 2025-04-15 09:45:46 +03:00
Yuri Kuznetsov 963fb40b2b fix save email settings 2025-04-14 11:13:15 +03:00
Yuri Kuznetsov 1682075267 ui fix 2025-04-14 11:06:23 +03:00
Yuri Kuznetsov 3032b2be87 select js error 2025-04-14 10:55:35 +03:00
Yuri Kuznetsov 2624c15763 fix 2025-04-14 10:24:23 +03:00
Yuri Kuznetsov 981be27151 email account dynamic logic changes 2025-04-14 09:18:44 +03:00
Yuri Kuznetsov 79b06df170 todo change 2025-04-13 16:27:14 +03:00
Yuri Kuznetsov 0eafe2ba35 system account color 2025-04-12 13:19:39 +03:00
Yuri Kuznetsov 11c6b79a4d remove system email account 2025-04-12 13:06:18 +03:00
Yuri Kuznetsov b2ce89754f install changes 2025-04-12 12:56:31 +03:00
Yuri Kuznetsov 1c1aac38c2 email account quick search for admin 2025-04-12 11:14:23 +03:00
Yuri Kuznetsov 587ab9a492 cleanup 2025-04-12 11:01:18 +03:00
Yuri Kuznetsov 3a38c8245b change insert text 2025-04-11 20:02:03 +03:00
Yuri Kuznetsov 99cb0ed8ce ref 2025-04-11 14:17:40 +03:00
Yuri Kuznetsov 1d685dc772 fix email send test no password 2025-04-11 09:11:23 +03:00
Yuri Kuznetsov cea34c95b5 skip audited 2025-04-10 19:53:38 +03:00
Yuri Kuznetsov 17bfe2d969 integration password type 2025-04-10 15:06:39 +03:00
Yuri Kuznetsov 7e4254ab42 ref 2025-04-10 14:48:06 +03:00
Yuri Kuznetsov f7d0e54f7a stylefix 2025-04-10 14:33:31 +03:00
Yuri Kuznetsov 41ea5e22ca formula insert change 2025-04-10 08:52:44 +03:00
Yuri Kuznetsov 85907320b4 cleanup 2025-04-09 23:42:17 +03:00
Yuri Kuznetsov a52a5e31e5 tracking url copy url 2025-04-09 23:23:31 +03:00
Yuri Kuznetsov 21bf43e33d Merge branch 'fix' 2025-04-09 23:14:07 +03:00
Yuri Kuznetsov 61c5ad9802 email show body plain impr 2025-04-09 21:54:03 +03:00
Yuri Kuznetsov 6b58d30eec improve html sanitize 2025-04-09 21:19:39 +03:00
Yuri Kuznetsov 84f7fc562c CSP form-action self 2025-04-09 21:11:27 +03:00
Yuri Kuznetsov 56f66976c7 add group index 2025-04-09 15:34:18 +03:00
Yuri Kuznetsov 16cc10bca0 style fixes 2025-04-09 10:38:30 +03:00
Yuri Kuznetsov b18ccfcb7b dashlet changes 2025-04-09 10:30:53 +03:00
Yuri Kuznetsov 3c3ea2135b style fix 2025-04-09 09:15:34 +03:00
Yuri Kuznetsov f860a8ecba role table ui impr 2025-04-09 09:12:25 +03:00
Yuri Kuznetsov 8fa5240dcd select style 2025-04-09 08:58:20 +03:00
Yuri Kuznetsov 9935c86538 native select 2025-04-08 22:46:02 +03:00
Yuri Kuznetsov 8ec1d5353f css fix 2025-04-08 22:24:22 +03:00
Yuri Kuznetsov 85ccb0da15 Merge branch 'fix' 2025-04-08 19:43:21 +03:00
Yuri Kuznetsov b431f40f9f update ubuntu 2025-04-08 19:43:01 +03:00
Yuri Kuznetsov 85bb013a0e 9.0.7 2025-04-08 19:30:16 +03:00
Yuri Kuznetsov aeeb779ab0 fix upload button 2025-04-08 19:22:22 +03:00
Yuri Kuznetsov ed1f07d872 comment 2025-04-08 19:02:10 +03:00
Yuri Kuznetsov 97938b8fcd comment 2025-04-08 17:46:36 +03:00
Yuri Kuznetsov 54d73fa073 show action button rerender if no button 2025-04-08 17:28:52 +03:00
Yuri Kuznetsov 2404dfff75 schema 2025-04-08 14:55:02 +03:00
Yuri Kuznetsov e401f3aef2 read-only saved 2025-04-08 14:22:50 +03:00
Yuri Kuznetsov 33914a9b61 ref 2025-04-08 11:28:45 +03:00
Yuri Kuznetsov 46c23cec26 types 2025-04-08 10:56:36 +03:00
Yuri Kuznetsov 71cb29c507 ref 2025-04-08 10:49:54 +03:00
Yuri Kuznetsov 034a0b8ff1 dynamic logic required backend 2025-04-08 10:33:57 +03:00
Yuri Kuznetsov f4c6c16df7 condition checker fix and has support 2025-04-07 18:32:41 +03:00
Yuri Kuznetsov 1bfc8a2b38 dynamic logic move 2025-04-07 17:14:11 +03:00
Yuri Kuznetsov 601cf8fdfd ConditionCheckerFactory 2025-04-07 11:27:11 +03:00
Yuri Kuznetsov 4be5829805 tests 2025-04-07 11:20:27 +03:00
Yuri Kuznetsov 66ae50b129 dynamic logic tool 2025-04-07 11:12:28 +03:00
Yuri Kuznetsov daa94f53d0 jsdoc 2025-04-06 19:56:46 +03:00
Yuri Kuznetsov 42c54f4c84 X-Record-Link-Updated 2025-04-06 11:29:06 +03:00
Yuri Kuznetsov 633ac6e63f save context 2025-04-06 11:28:51 +03:00
Yuri Kuznetsov 8cfa9bd8af comments 2025-04-06 10:52:03 +03:00
Yuri Kuznetsov 7c63eabd76 remove legacy params 2025-04-06 10:37:41 +03:00
Yuri Kuznetsov 1dafe99fa9 debounce helper arguments 2025-04-05 17:58:05 +03:00
Yuri Kuznetsov 35431ede0d cs, jsdocs 2025-04-05 17:33:10 +03:00
Yuri Kuznetsov 869d73511c colorpicker alpha background stretch 2025-04-05 09:46:55 +03:00
Yuri Kuznetsov 3fab26158f cs 2025-04-04 19:07:47 +03:00
Yuri Kuznetsov b1b0df004e compose email fix error handling 2025-04-04 18:24:44 +03:00
Yuri Kuznetsov 53bbfde2b2 ref 2025-04-04 18:23:31 +03:00
Yuri Kuznetsov 6c32b94376 email header refresh 2025-04-04 16:29:57 +03:00
Yuri Kuznetsov 8dd35ec9d3 email body to plain inprovement 2025-04-04 15:58:45 +03:00
Yuri Kuznetsov 55c7cff79d rename 2025-04-04 13:01:07 +03:00
Yuri Kuznetsov 06bd8ef175 ref 2025-04-04 13:00:15 +03:00
Yuri Kuznetsov 1894c5cc9a blank avatar cache 2025-04-04 12:34:41 +03:00
Yuri Kuznetsov 927fd7a589 avatar caching improvement 2025-04-04 12:27:07 +03:00
Yuri Kuznetsov fe70eba7d6 attachment cache header change 2025-04-04 11:22:49 +03:00
Yuri Kuznetsov 4a4618ef56 html to plain link fix 2025-04-04 10:27:14 +03:00
Yuri Kuznetsov 3af94e3545 fix activities hasAttachment null => false 2025-04-03 22:22:48 +03:00
Yuri Kuznetsov 80ad254cf6 Merge branch 'master' of https://github.com/espocrm/espocrm 2025-04-03 22:21:41 +03:00
SuchAFuriousDeath e4154a70ff Fix getActivitiesUserMeetingQueryin Activities service for Postgres (#3330)
* Fix getActivitiesUserMeetingQueryin Activities service

* change false to 'false'
2025-04-03 22:21:10 +03:00
Yuri Kuznetsov 0fe44b0885 fix 2025-04-03 11:15:42 +03:00
Yuri Kuznetsov ad00a8cff8 expanded layout item params 2025-04-03 11:03:22 +03:00
Yuri Kuznetsov 8a71c651ae ref 2025-04-02 19:35:53 +03:00
Yuri Kuznetsov c09c1a8298 imap security label fix, port autofill fix 2025-04-02 15:26:52 +03:00
Yuri Kuznetsov c950559038 fix docs 2025-04-02 15:19:10 +03:00
Yuri Kuznetsov 251700eb50 external account languageIsGlobal 2025-04-02 12:18:48 +03:00
Yuri Kuznetsov 7bf3406735 fix labels 2025-04-01 15:22:07 +03:00
Yuri Kuznetsov 30e123ccbb fix smtp params transport preparator 2025-04-01 13:51:13 +03:00
Yuri Kuznetsov d993f99ad1 add sensitive parameter annotation 2025-04-01 11:26:17 +03:00
Yuri Kuznetsov e9fbb1d4d4 dynamic logic builder varchar matches fix 2025-04-01 10:50:28 +03:00
Yuri Kuznetsov 6f9ba6c7f6 smtp custom transport preparator 2025-03-31 23:36:33 +03:00
Yuri Kuznetsov 08f5bbeaba sender ref 2025-03-31 23:19:21 +03:00
Yuri Kuznetsov 520d55b98d sender ref 2025-03-31 23:12:12 +03:00
Yuri Kuznetsov d57cea662d transport preparator 2025-03-31 22:49:23 +03:00
Yuri Kuznetsov 2f5b78f887 email sending library change 2025-03-31 19:15:30 +03:00
Yuri Kuznetsov ef9795f6e4 send test notify 2025-03-31 15:27:59 +03:00
Yuri Kuznetsov f8166a7109 fix 2025-03-31 15:27:09 +03:00
Yuri Kuznetsov bcfd735844 fix send test 2025-03-31 15:24:34 +03:00
Yuri Kuznetsov 54f91df984 fix select 2025-03-30 22:33:14 +03:00
Yuri Kuznetsov 19843c19d1 sender replyTo set 2025-03-30 15:01:33 +03:00
Yuri Kuznetsov e620317966 update libs 2025-03-30 13:45:43 +03:00
Yuri Kuznetsov 224187ba11 ref, suppress 2025-03-30 12:57:54 +03:00
Yuri Kuznetsov 9e9af5769a suppress inspection 2025-03-30 12:51:22 +03:00
Yuri Kuznetsov 1224e778d4 ref, deprecate 2025-03-29 19:55:42 +02:00
Yuri Kuznetsov 0ee1cbeaaf comment 2025-03-29 19:21:58 +02:00
Yuri Kuznetsov d0c6400644 mail sender ref 2025-03-29 19:20:06 +02:00
Yuri Kuznetsov 19ae8f2499 Merge branch 'master' of https://github.com/espocrm/espocrm 2025-03-29 16:36:43 +02:00
Yuri Kuznetsov 0aae87a248 role edit view performance improvement 2025-03-29 16:36:21 +02:00
Yuri Kuznetsov fa1bd30ac3 cleanup 2025-03-29 13:54:50 +02:00
SuchAFuriousDeath 73d0203b8f Fix doc comment (#3326) 2025-03-28 13:56:35 +02:00
Yuri Kuznetsov caf3a729d1 formula: key access null coalescing 2025-03-27 17:13:04 +02:00
Yuri Kuznetsov 7e49e6491d formula: nested key access 2025-03-27 16:34:19 +02:00
Yuri Kuznetsov ff045692d0 cs 2025-03-27 15:21:49 +02:00
Yuri Kuznetsov 2e0f0fb7db ref 2025-03-27 14:35:01 +02:00
Yuri Kuznetsov 7763ddb802 formula: key value access 2025-03-27 14:06:50 +02:00
Yuri Kuznetsov eafd465ba2 formula: increment decrement 2025-03-27 12:01:04 +02:00
Yuri Kuznetsov 512ddc0d76 change insert 2025-03-27 11:36:02 +02:00
Yuri Kuznetsov b2156f36b9 formula: array append 2025-03-27 11:13:07 +02:00
Yuri Kuznetsov a56ccbd1f8 fix phpstan weird error 2025-03-26 15:34:47 +02:00
Yuri Kuznetsov e91a98d0d5 fix 2025-03-26 15:27:35 +02:00
Yuri Kuznetsov 3d914d0591 fix 2025-03-26 15:18:13 +02:00
Yuri Kuznetsov b95c1c8878 ref 2025-03-26 14:54:26 +02:00
Yuri Kuznetsov f18ed3a531 ref 2025-03-26 13:57:53 +02:00
Yuri Kuznetsov d2feca38d8 ref 2025-03-26 13:50:29 +02:00
Yuri Kuznetsov 41b0dd4e78 ref 2025-03-26 13:34:21 +02:00
Yuri Kuznetsov e92b200eb3 update phpstan, ref 2025-03-26 12:24:39 +02:00
Yuri Kuznetsov c1ae7dff95 select ref, docs 2025-03-26 11:10:24 +02:00
Yuri Kuznetsov 6cbf6cea73 access checker factory bind entityType 2025-03-26 10:59:47 +02:00
Yuri Kuznetsov b8e1515015 ref 2025-03-25 18:19:16 +02:00
Yuri Kuznetsov 399bc60525 array field item max length param 2025-03-25 15:38:56 +02:00
Yuri Kuznetsov 913dc48bfe sensitive params 2025-03-25 14:32:48 +02:00
Yuri Kuznetsov 2af34da28f oauth providers 2025-03-25 14:15:39 +02:00
Yuri Kuznetsov 9043d37a08 fix 2025-03-25 13:45:12 +02:00
Yuri Kuznetsov 72cd6583d5 formula sandbox log errors 2025-03-25 13:42:23 +02:00
Yuri Kuznetsov 03969a20fe select for share mysql fix 2025-03-25 13:36:14 +02:00
Yuri Kuznetsov f2a5e103dd fix 2025-03-25 09:50:48 +02:00
Yuri Kuznetsov 45d66461e8 wysiwyf mailto 2025-03-24 18:03:28 +02:00
Yuri Kuznetsov 79fb3eaa29 schema 2025-03-24 10:22:44 +02:00
Yuri Kuznetsov 946ad47765 jsdoc fix 2025-03-24 10:17:03 +02:00
Yuri Kuznetsov 488d5ab6ae added forbidden field name list 2025-03-24 09:15:58 +02:00
Yuri Kuznetsov 9c8d7d2062 user data ref 2025-03-24 09:13:07 +02:00
Yuri Kuznetsov ce6abcd3f0 phone/email fields details 2025-03-23 12:01:07 +02:00
Yuri Kuznetsov 8f2ccb45a2 rename 2025-03-21 17:03:21 +02:00
Yuri Kuznetsov 415e9503f7 wysiwyg no none in list view 2025-03-21 12:42:16 +02:00
Yuri Kuznetsov aec3e459b5 Merge branch 'fix' 2025-03-21 10:58:33 +02:00
Yuri Kuznetsov 48ce79811f fix domain 2025-03-21 10:58:24 +02:00
Yuri Kuznetsov 0d2c30bcd9 Merge branch 'fix' 2025-03-21 10:52:21 +02:00
Yuri Kuznetsov c939deb589 iframeSandboxExcludeDomainList 2025-03-21 10:46:26 +02:00
Yuri Kuznetsov 5cdcb8bd74 collapsable edit modal 2025-03-21 10:26:16 +02:00
Yuri Kuznetsov e57018732f array field validate item not entered 2025-03-21 09:19:00 +02:00
Yuri Kuznetsov 4869d5f5b6 cleanup 2025-03-20 21:30:21 +02:00
Yuri Kuznetsov 7ffcb9031b ref 2025-03-20 21:22:36 +02:00
Yuri Kuznetsov 474fd25d3c ref 2025-03-20 21:15:18 +02:00
Yuri Kuznetsov 22d50aee21 modal bar ref 2025-03-20 20:59:04 +02:00
Yuri Kuznetsov 9fec3a57b2 ref 2025-03-20 20:44:49 +02:00
Yuri Kuznetsov d3c480d481 ref 2025-03-20 20:38:28 +02:00
Yuri Kuznetsov 58e091b45e ref 2025-03-20 18:41:15 +02:00
Yuri Kuznetsov c4303483b7 modal ref 2025-03-20 18:03:33 +02:00
Yuri Kuznetsov c742c0859b impr confirm leave out, collapse modal ref 2025-03-20 16:59:02 +02:00
Yuri Kuznetsov 45ab1653a4 lead form ui changes 2025-03-20 14:23:42 +02:00
Yuri Kuznetsov bc10efa640 today task danger style 2025-03-20 14:03:20 +02:00
Yuri Kuznetsov 757e2fbda1 text preview style impr 2025-03-20 12:34:10 +02:00
Yuri Kuznetsov a09959a969 kb order do not restrict in portal 2025-03-20 11:15:02 +02:00
Yuri Kuznetsov c39052a0d3 select applier factory ref 2025-03-20 11:12:29 +02:00
Yuri Kuznetsov e321d61237 dynamlic logic builder not fix 2025-03-20 10:04:30 +02:00
Yuri Kuznetsov 678b3bb47d comment 2025-03-20 09:39:00 +02:00
Yuri Kuznetsov 489a5befde ref 2025-03-20 08:40:55 +02:00
Yuri Kuznetsov c0a16d8b7c cleanup 2025-03-19 19:01:00 +02:00
Yuri Kuznetsov 2ce99b3702 order applier acl manager 2025-03-19 15:11:36 +02:00
Yuri Kuznetsov 8008741e35 order field access check 2025-03-19 14:54:45 +02:00
Yuri Kuznetsov 47edd2215f Merge branch 'fix' 2025-03-19 14:32:41 +02:00
Yuri Kuznetsov 91740192d2 order disabled api key 2025-03-19 14:30:49 +02:00
Yuri Kuznetsov bd900d0b48 disable order 2025-03-19 14:29:22 +02:00
Yuri Kuznetsov ca5aa8edf0 remove no random_bytes fallback 2025-03-19 13:55:15 +02:00
Yuri Kuznetsov 81937ca606 secure froendend password generation 2 2025-03-19 13:25:16 +02:00
Yuri Kuznetsov ba15bf8d7e secure frontend password generation 2025-03-19 13:19:52 +02:00
Yuri Kuznetsov e529a72a00 test fix 2025-03-19 13:05:39 +02:00
Yuri Kuznetsov 080a7864b5 kanban createDisabled 2025-03-19 12:33:28 +02:00
Yuri Kuznetsov 3db7261c0e tooltips 2025-03-19 12:01:19 +02:00
Yuri Kuznetsov bb47ab3261 Merge branch 'fix' 2025-03-19 11:21:59 +02:00
Yuri Kuznetsov 0ae0365ee5 9.0.6 2025-03-19 11:08:06 +02:00
Yuri Kuznetsov cbcc560bd3 sanitize navbar color 2025-03-19 09:33:35 +02:00
Yuri Kuznetsov ee819fb58a lead form: use panel text 2025-03-18 18:51:07 +02:00
Yuri Kuznetsov 69eb1a1948 update package 2025-03-18 16:48:43 +02:00
Yuri Kuznetsov a71bf16c51 element check cleanup 2025-03-18 16:42:05 +02:00
Yuri Kuznetsov 2a2e0cc15d update bullbone 2025-03-18 16:36:58 +02:00
Yuri Kuznetsov fe99cf8db1 regression fix 2025-03-18 16:28:50 +02:00
Yuri Kuznetsov 5e7a089cb0 do not set person name unless all fields are present 2025-03-18 15:15:13 +02:00
Yuri Kuznetsov a198cf8c65 cs 2025-03-18 14:19:19 +02:00
Yuri Kuznetsov 57e5202a28 schema 2025-03-18 14:17:01 +02:00
Yuri Kuznetsov cf3cc2c4a5 ref 2025-03-18 14:14:53 +02:00
Yuri Kuznetsov 9eda4c48e9 style impr 2025-03-18 13:58:17 +02:00
Yuri Kuznetsov 06153536c1 cleanup 2025-03-18 13:45:25 +02:00
Yuri Kuznetsov 77fdd9e194 kb article full text search 2025-03-18 13:01:13 +02:00
Yuri Kuznetsov 0e383acfe3 jsdoc 2025-03-18 11:39:13 +02:00
Yuri Kuznetsov 9e65549eb3 select additional appliers in selectDefs 2025-03-18 11:08:12 +02:00
Yuri Kuznetsov a6c0e2aa0e ref 2025-03-18 10:53:23 +02:00
Yuri Kuznetsov 67702f5a55 orm entity: prepare decimal 2025-03-18 10:04:56 +02:00
Yuri Kuznetsov 4270a452a7 ref 2025-03-18 09:25:04 +02:00
Yuri Kuznetsov 5178d72850 currency field: fix empty decimal part when 0 decimals 2025-03-18 09:09:43 +02:00
Yuri Kuznetsov 832cfb8915 record websocket debounce 2025-03-17 18:49:22 +02:00
Yuri Kuznetsov 79cd6ad7b9 user field select primary filter 2025-03-17 18:02:34 +02:00
Yuri Kuznetsov a445c4d2a9 tab quick search order by length 2025-03-17 17:42:33 +02:00
Yuri Kuznetsov 4a1cb6a9d5 portal account contact backend default populate 2025-03-17 16:22:58 +02:00
Yuri Kuznetsov 5069547464 ref 2025-03-17 15:43:46 +02:00
Yuri Kuznetsov 335a62cb10 currency customizationOptionsReferenceDisabled 2025-03-17 15:26:40 +02:00
Yuri Kuznetsov e74e9e519b setStatus methods 2025-03-17 15:25:45 +02:00
Yuri Kuznetsov 3f8fcbcb74 clnp 2025-03-17 15:20:05 +02:00
Yuri Kuznetsov 90db4b29c4 fix 2025-03-17 13:49:36 +02:00
dependabot[bot] 8fce28df3b Bump @babel/helpers from 7.26.0 to 7.26.10 (#3314)
Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.26.0 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 12:06:16 +02:00
dependabot[bot] 6ac4e7ef18 Bump axios from 1.7.5 to 1.8.3 (#3313)
Bumps [axios](https://github.com/axios/axios) from 1.7.5 to 1.8.3.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.5...v1.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 12:04:00 +02:00
Yuri Kuznetsov 9945080bec navbar item access data list 2025-03-17 11:44:53 +02:00
Yuri Kuznetsov 7dac68b6c0 user access checker check is active 2025-03-14 17:08:17 +02:00
Yuri Kuznetsov 209e828a49 Merge branch 'fix' 2025-03-14 16:58:48 +02:00
Yuri Kuznetsov 398743fe63 remove icon class 2025-03-14 12:43:08 +02:00
Yuri Kuznetsov 92f6759591 fix task reminders handler 2025-03-14 12:42:08 +02:00
Yuri Kuznetsov 368c2fb866 9.0.5 2025-03-13 17:08:24 +02:00
Yuri Kuznetsov 985c6fb64b fix free busy service 2025-03-13 17:08:16 +02:00
Yuri Kuznetsov 9b9ec31f0f lead form control save, error fix 2025-03-13 14:38:05 +02:00
Yuri Kuznetsov 946d0137af Merge branch 'fix' 2025-03-12 15:52:01 +02:00
Yuri Kuznetsov 37d2d8cf4f navbar dropdown active bg 2025-03-12 15:51:50 +02:00
Yuri Kuznetsov 3072c2a236 fix module name usage 2025-03-11 19:35:17 +02:00
Yuri Kuznetsov a38803389c ref 2025-03-11 19:31:46 +02:00
Yuri Kuznetsov 4708cf00c0 ref 2025-03-11 19:30:19 +02:00
Yuri Kuznetsov 4d525ae8ef additional applier in kanban 2025-03-11 19:17:03 +02:00
Yuri Kuznetsov 57b69fe5a9 base field type label 2025-03-11 19:05:17 +02:00
Yuri Kuznetsov 6ee1387fcf Merge branch 'fix' 2025-03-11 19:01:26 +02:00
Yuri Kuznetsov 5cfbdb21e9 style fix 2025-03-11 19:01:15 +02:00
Yuri Kuznetsov 2abf1ccb73 base type change 2025-03-11 18:59:28 +02:00
Yuri Kuznetsov 09b977a798 custom field color 2025-03-11 14:47:10 +02:00
Yuri Kuznetsov 80fac162af field details 2025-03-11 14:19:55 +02:00
Yuri Kuznetsov c5e43e6e49 field: set read only support not ready 2025-03-11 13:10:40 +02:00
Yuri Kuznetsov 08571e805a blockquote complex text margin 2025-03-10 20:52:28 +02:00
Yuri Kuznetsov dd5691ef7f ref 2025-03-10 18:30:30 +02:00
Yuri Kuznetsov 6e43dc87f2 formula: return last expression 2025-03-10 16:17:27 +02:00
Yuri Kuznetsov 06dc8ef281 change event action 2025-03-10 13:35:16 +02:00
Yuri Kuznetsov b05b4c78e8 schema desc 2025-03-08 11:58:36 +02:00
Yuri Kuznetsov 9726d63d0b ref 2025-03-08 11:56:48 +02:00
Yuri Kuznetsov 4ca7d27d39 optimistic concurrency control: skip 2025-03-08 11:56:41 +02:00
Yuri Kuznetsov 29b2304b25 bottom panels first tab select impr 2025-03-07 19:38:54 +02:00
Yuri Kuznetsov 8bca3b2113 jsdoc 2025-03-07 18:07:42 +02:00
Yuri Kuznetsov 7181d7196f ref 2025-03-07 13:28:31 +02:00
Yuri Kuznetsov 28ebc6ea81 ref 2025-03-07 12:36:18 +02:00
Yuri Kuznetsov f3c4e631cd ref 2025-03-07 11:46:21 +02:00
Yuri Kuznetsov da14a41387 duration focus after update fix 2025-03-06 14:12:50 +02:00
Yuri Kuznetsov cc14c69a5e duration focus after update fix 2025-03-06 13:56:00 +02:00
Yuri Kuznetsov 5ae385527b default preparator: use account contact links 2025-03-06 10:19:26 +02:00
Yuri Kuznetsov b6aec6360b dynamic logic render deleted 2025-03-05 11:33:24 +02:00
Yuri Kuznetsov 5a5528f78f command.php add DIR 2025-03-05 11:09:35 +02:00
Yuri Kuznetsov a3f11919c9 control label no select 2025-03-05 09:51:58 +02:00
Yuri Kuznetsov 3847afc50f view user access for acl entities 2025-03-05 09:50:17 +02:00
Yuri Kuznetsov a3e7f5c5fa websocket ref 2025-03-03 10:03:02 +02:00
Yuri Kuznetsov 1fe2c74691 dropdown fix 2025-03-02 13:13:48 +02:00
Yuri Kuznetsov 7b5789e44c parent field get select layout method 2025-03-02 12:36:10 +02:00
Yuri Kuznetsov 61e03fd12a field editCancel set option & skipReRenderInEditMode 2025-03-01 17:38:39 +02:00
Yuri Kuznetsov 5f6bd7b748 currency converter forbidden exception 2025-03-01 11:55:39 +02:00
Yuri Kuznetsov 1e9a59f084 starred style change 2025-03-01 10:36:05 +02:00
Yuri Kuznetsov 9e11cecfa5 text field list enter impr 2025-03-01 09:42:02 +02:00
Yuri Kuznetsov 8a76e70fcb add exit code 2025-02-28 19:24:54 +02:00
Yuri Kuznetsov 92e4d03495 1 exit code on upgrade failure 2025-02-28 18:55:29 +02:00
Yuri Kuznetsov a9d059c0df comment 2025-02-27 20:44:41 +02:00
Yuri Kuznetsov 2b14e11259 fix text height 2025-02-27 20:41:18 +02:00
Yuri Kuznetsov 50cde83341 note post to label 2025-02-27 20:37:40 +02:00
Yuri Kuznetsov 0283f781d7 cs 2025-02-27 20:31:23 +02:00
Yuri Kuznetsov ddd54c321e Merge branch 'fix' 2025-02-27 20:21:41 +02:00
Yuri Kuznetsov 01a40e311d link parent getSelectFilters for autocomplete 2025-02-27 17:46:11 +02:00
Yuri Kuznetsov 6209733497 wysiwyg iframe sandbox 2025-02-26 21:51:59 +02:00
Yuri Kuznetsov 5e37bc2d62 Merge branch 'fix' 2025-02-26 21:24:45 +02:00
Yuri Kuznetsov 8477416063 sandbox=allow-scripts 2025-02-26 21:03:29 +02:00
Yuri Kuznetsov 5020c132b5 cal getAdditionalActionList for user 2025-02-26 14:06:09 +02:00
Yuri Kuznetsov b70a354556 label changes 2025-02-25 10:33:35 +02:00
Yuri Kuznetsov 92f5afef3c options layout changes 2025-02-25 10:31:10 +02:00
Yuri Kuznetsov 9a407eef6d include shared dashlet parameter 2025-02-25 10:24:17 +02:00
Yuri Kuznetsov e635a28f16 layout mass update filter fields 2025-02-24 21:57:17 +02:00
Yuri Kuznetsov adbed86617 cleanup 2025-02-24 18:40:30 +02:00
Yuri Kuznetsov eb556b978b campaign informational email 2025-02-24 18:08:48 +02:00
Yuri Kuznetsov 5b1647a42f campaign ref 2025-02-24 15:46:25 +02:00
Yuri Kuznetsov 153c09f282 mass email: skip for inactive campaign 2025-02-24 15:05:53 +02:00
Yuri Kuznetsov 8350bebb50 opportunity probability allow empty 2025-02-24 13:43:08 +02:00
Yuri Kuznetsov 75e5701a3b ref 2025-02-24 13:40:47 +02:00
Yuri Kuznetsov eb7ca6188c ref 2025-02-24 13:22:56 +02:00
Yuri Kuznetsov e80a885208 ref 2025-02-24 13:19:19 +02:00
Yuri Kuznetsov b1076ba1bd full text search for cases 2025-02-24 13:10:35 +02:00
Yuri Kuznetsov ed281abd92 cs 2025-02-24 13:06:45 +02:00
Yuri Kuznetsov e63111c93f list column adjust width if greater than 100 2025-02-24 12:54:28 +02:00
Yuri Kuznetsov e87ba0c4e1 ref 2025-02-23 18:40:36 +02:00
Yuri Kuznetsov 79f7243e70 ref 2025-02-23 18:21:22 +02:00
Yuri Kuznetsov b19f148ee8 ref 2025-02-23 17:56:51 +02:00
Yuri Kuznetsov 4ee79811bc ref 2025-02-23 17:08:31 +02:00
Yuri Kuznetsov e79e9d6ce1 ref 2025-02-23 15:05:29 +02:00
Yuri Kuznetsov ddae4383dd jsdoc 2025-02-23 10:47:23 +02:00
Yuri Kuznetsov 683751ac0c cleanup 2025-02-23 10:11:03 +02:00
Yuri Kuznetsov 8407d27ea0 entity param 2025-02-23 10:10:31 +02:00
Yuri Kuznetsov 4126b99a7b ref 2025-02-23 10:10:23 +02:00
Yuri Kuznetsov 69003eae7f ref 2025-02-22 21:57:27 +02:00
Yuri Kuznetsov ec5e173e40 ref 2025-02-22 20:11:38 +02:00
Yuri Kuznetsov 06603bd2b3 chart no user select 2025-02-22 13:26:31 +02:00
Yuri Kuznetsov 11263671b2 fix 2025-02-22 12:21:18 +02:00
Yuri Kuznetsov e9f0880f01 dropdown up fix 2025-02-22 11:46:41 +02:00
Yuri Kuznetsov 2fac2328d0 ref 2025-02-22 11:25:55 +02:00
Yuri Kuznetsov 3ef7382ab0 jsdoc 2025-02-22 10:28:24 +02:00
Yuri Kuznetsov fb4379ea34 search manager ref 2025-02-22 10:27:24 +02:00
Yuri Kuznetsov e7bea7e7fe ref, types 2025-02-22 09:28:03 +02:00
Yuri Kuznetsov 20ea95238b onMassSelect 2025-02-22 09:15:28 +02:00
Yuri Kuznetsov 1098540ae4 ref 2025-02-22 09:03:34 +02:00
Yuri Kuznetsov 3ae31f6341 ref 2025-02-22 08:42:58 +02:00
Yuri Kuznetsov 014c6a3b73 ref 2025-02-21 20:31:06 +02:00
Yuri Kuznetsov 65f9cd35c9 ref 2025-02-21 19:02:23 +02:00
Yuri Kuznetsov 324bc26e1e ref 2025-02-21 18:42:26 +02:00
Yuri Kuznetsov 234ff55376 ref 2025-02-21 17:58:19 +02:00
Yuri Kuznetsov e13f79134e ref 2025-02-21 17:50:23 +02:00
Yuri Kuznetsov f78ca003d2 ref 2025-02-21 17:40:23 +02:00
Yuri Kuznetsov d7460a9e0a ref 2025-02-21 17:29:38 +02:00
Yuri Kuznetsov 85a98326fe ref 2025-02-21 17:07:39 +02:00
Yuri Kuznetsov 5d1584ce83 timeline animate refresh 2025-02-21 16:46:24 +02:00
Yuri Kuznetsov 5f02605aca ref 2025-02-21 16:43:48 +02:00
Yuri Kuznetsov b3b1441510 jsdoc fix 2025-02-21 16:40:13 +02:00
Yuri Kuznetsov 84ba790ef9 fix collection trigger sync 2025-02-21 16:37:29 +02:00
Yuri Kuznetsov de93e83abe ref 2025-02-21 16:33:43 +02:00
Yuri Kuznetsov 1bcb8349de ref 2025-02-21 16:22:06 +02:00
Yuri Kuznetsov dc527c2cf6 ref 2025-02-21 16:15:12 +02:00
Yuri Kuznetsov 71daa4ce2f ref 2025-02-21 16:06:36 +02:00
Yuri Kuznetsov a13710e9d2 ref 2025-02-21 15:37:31 +02:00
Yuri Kuznetsov bf2e7ac236 ref 2025-02-21 15:34:30 +02:00
Yuri Kuznetsov 72120f262d ref 2025-02-21 15:31:40 +02:00
Yuri Kuznetsov 6cde07a75b ref 2025-02-21 14:43:39 +02:00
Yuri Kuznetsov 42213789ba ref 2025-02-21 14:41:00 +02:00
Yuri Kuznetsov 598655ef33 ref 2025-02-21 14:20:32 +02:00
Yuri Kuznetsov b247b00bd7 list sync keep modal 2025-02-21 14:02:58 +02:00
Yuri Kuznetsov d39a732d17 ref 2025-02-21 13:40:50 +02:00
Yuri Kuznetsov 86e2e39772 cleanup 2025-02-21 13:16:20 +02:00
Yuri Kuznetsov 6405ab2b17 ref 2025-02-21 12:58:59 +02:00
Yuri Kuznetsov cbbde8af9b ref 2025-02-21 12:29:14 +02:00
Yuri Kuznetsov 2c4779a98c notifyWait usage 2025-02-21 12:07:36 +02:00
Yuri Kuznetsov b465faf936 remove related from list related 2025-02-21 12:04:13 +02:00
Yuri Kuznetsov a0a801a7c7 list related full refresh 2025-02-21 12:01:09 +02:00
Yuri Kuznetsov 2863654f8f edit modal helper ref 2025-02-21 11:50:46 +02:00
Yuri Kuznetsov 717869d0da npm run commands 2025-02-20 21:28:13 +02:00
Yuri Kuznetsov 115465f017 schema 2025-02-20 21:17:54 +02:00
Yuri Kuznetsov 738f483aef integration populate defaults on rebuild 2025-02-20 21:12:11 +02:00
Yuri Kuznetsov f7e782f081 ref Integration entity 2025-02-20 21:08:29 +02:00
Yuri Kuznetsov 2214c70b2b jsdoc, ref, modal afterExpand 2025-02-20 18:46:01 +02:00
Yuri Kuznetsov 7f4bc6f952 setInContainerNotWritten 2025-02-20 17:39:20 +02:00
Yuri Kuznetsov bf28abc4ca email: do not re-add users on save 2025-02-20 16:56:47 +02:00
Yuri Kuznetsov 963b9e9e59 entity get fetched link multiple id list 2025-02-20 16:49:37 +02:00
Yuri Kuznetsov 8a8de30949 ref 2025-02-20 16:08:54 +02:00
Yuri Kuznetsov cee67cbd97 ref 2025-02-20 15:24:31 +02:00
Yuri Kuznetsov d60d834a65 cleanup 2025-02-20 14:09:20 +02:00
Yuri Kuznetsov c1a10bf926 nameAttribute param 2025-02-20 13:20:26 +02:00
Yuri Kuznetsov a1dd707315 dropdown up 2025-02-20 12:47:47 +02:00
Yuri Kuznetsov 60d778bcb1 ref 2025-02-20 11:38:43 +02:00
Yuri Kuznetsov d6291b03e3 ref 2025-02-20 11:10:19 +02:00
Yuri Kuznetsov 24b5b94d63 ref 2025-02-20 11:07:07 +02:00
Yuri Kuznetsov 85d4f6f304 ref 2025-02-20 10:59:12 +02:00
Yuri Kuznetsov da2c80b4fc jsdocs 2025-02-20 10:48:50 +02:00
Yuri Kuznetsov 18a91d633d ref 2025-02-20 10:47:13 +02:00
Yuri Kuznetsov 65ec35f4dd cleanup 2025-02-20 10:41:14 +02:00
Yuri Kuznetsov 515e388521 see more after stream post 2025-02-19 19:43:05 +02:00
Yuri Kuznetsov b9037b9a52 ref 2025-02-19 19:29:13 +02:00
Yuri Kuznetsov 0bf7bcb204 whitespace fix 2025-02-19 18:50:14 +02:00
Yuri Kuznetsov bec4a188a3 css fix 2025-02-19 18:47:35 +02:00
Yuri Kuznetsov df6b5ae043 model-sync set 2025-02-19 18:04:46 +02:00
Yuri Kuznetsov e29d0959c4 page content width param 2025-02-19 13:27:03 +02:00
Yuri Kuznetsov cce73a8c32 ref 2025-02-19 12:13:48 +02:00
Yuri Kuznetsov eee9d37c97 Merge branch 'master' of https://github.com/espocrm/espocrm 2025-02-19 12:00:34 +02:00
Yuri Kuznetsov 4b983afc5c middle dot usage 2025-02-19 12:00:24 +02:00
Yuri Kuznetsov 3a1dd252e1 Merge branch 'fix' 2025-02-19 11:55:01 +02:00
Yuri Kuznetsov c2d7bc818e do no show last dropdown divider 2025-02-19 11:41:35 +02:00
Andrew Fontana 732a4a24e6 Fix typo AccessChecker.php (#3289)
Was using capital I in getLinkMultipleIdList.
2025-02-19 08:29:30 +02:00
Yuri Kuznetsov 64e3cd23ad Merge branch 'fix' 2025-02-18 09:31:39 +02:00
Yuri Kuznetsov aca48f024d fix dynamic logic string ui 2025-02-18 09:23:59 +02:00
Yuri Kuznetsov 9b0549a762 foreign fields setup fix 2025-02-17 21:32:29 +02:00
Yuri Kuznetsov b0681b89ea multi enum ui impr 2025-02-17 17:35:48 +02:00
Yuri Kuznetsov 2182cf6a89 multi-select dropdown items style 2025-02-17 17:00:15 +02:00
Yuri Kuznetsov 2e13d48518 multi-enum option style 2025-02-17 16:32:10 +02:00
Yuri Kuznetsov 841e570e57 multi select do not increase height on drag 2025-02-17 15:14:39 +02:00
Yuri Kuznetsov 1a08df7824 Merge branch 'fix' 2025-02-17 15:02:21 +02:00
Yuri Kuznetsov 3f0140c716 dashlet auto refresh no notify 2025-02-17 14:53:02 +02:00
Yuri Kuznetsov 38a9c69bde Merge branch 'fix' 2025-02-17 11:17:02 +02:00
Yuri Kuznetsov 3366a27575 metadata additional fields not customizable fix 2025-02-17 11:08:24 +02:00
Yuri Kuznetsov a93ba33e92 cleanup 2025-02-17 10:42:00 +02:00
Yuri Kuznetsov 80c5decee8 list to detail collection cloning and sync 2025-02-16 20:00:18 +02:00
Yuri Kuznetsov 21b84c9f36 ref, jsdoc 2025-02-16 18:59:45 +02:00
Yuri Kuznetsov 970966e4c5 edit header ref 2025-02-16 18:04:51 +02:00
Yuri Kuznetsov 4336344d40 full refresh on header title click 2025-02-16 17:57:03 +02:00
Yuri Kuznetsov fe9740e3eb ref 2025-02-16 17:37:12 +02:00
Yuri Kuznetsov 7581942b0c ref 2025-02-16 16:59:32 +02:00
Yuri Kuznetsov 5dfab7e325 ref 2025-02-16 16:48:59 +02:00
Yuri Kuznetsov e93f338a9e jsdocs 2025-02-16 16:47:00 +02:00
Yuri Kuznetsov d1b3afdd01 clone model on list to detail 2025-02-16 16:45:22 +02:00
Yuri Kuznetsov 054fde7288 suppress inspection 2025-02-16 16:33:06 +02:00
Yuri Kuznetsov f887d05d5e Merge branch 'fix' 2025-02-16 16:31:00 +02:00
Yuri Kuznetsov 09dea0be01 refresh panel and dashlet notify 2025-02-16 14:39:32 +02:00
Yuri Kuznetsov 40157bcb8c strip html in notification message 2025-02-16 14:08:56 +02:00
Yuri Kuznetsov e0e80c5a56 trigger change on control+enter save 2025-02-15 16:55:44 +02:00
Yuri Kuznetsov 926a052e99 update dependencies 2025-02-15 15:11:54 +02:00
Yuri Kuznetsov a7d10ae9d6 update summernote 2025-02-15 15:09:40 +02:00
Yuri Kuznetsov 0757ebf520 update dom purify 2025-02-15 14:59:05 +02:00
Yuri Kuznetsov 20479cdcab Merge branch 'fix' 2025-02-15 14:53:57 +02:00
Yuri Kuznetsov 1954efd7e0 tab search improve 2025-02-14 22:52:33 +02:00
Yuri Kuznetsov cb34377363 formula field style impr 2025-02-14 16:18:13 +02:00
Yuri Kuznetsov 2e9437572d fix list tree where 2025-02-14 13:15:26 +02:00
Yuri Kuznetsov 0e639ef6a8 fix docs 2025-02-14 12:03:57 +02:00
Yuri Kuznetsov 5c7467e4bf select related create fix 2025-02-14 12:00:35 +02:00
Yuri Kuznetsov b9b71b2015 calendar month title fix 2025-02-13 21:21:20 +02:00
Yuri Kuznetsov 50b7f2cf30 rename 2025-02-12 13:41:30 +02:00
Yuri Kuznetsov cf20fe65cc fix test 2025-02-12 13:40:29 +02:00
Yuri Kuznetsov a676f26c36 mail config data provider 2025-02-12 13:35:19 +02:00
Yuri Kuznetsov 8ba7fdfa85 ref 2025-02-12 13:23:27 +02:00
Yuri Kuznetsov f81861df19 update ubuntu 2025-02-12 13:08:20 +02:00
Yuri Kuznetsov 1d6f654c2f collection methods 2025-02-12 12:43:31 +02:00
Yuri Kuznetsov 67df53dda8 throw not found, not error 2025-02-12 11:32:08 +02:00
Yuri Kuznetsov 11bc32ab8a cleanup 2025-02-12 11:24:27 +02:00
Yuri Kuznetsov f5610bbdb4 modal maximize focus 2025-02-12 11:13:36 +02:00
Yuri Kuznetsov 24cd1b3c5d image number muted text 2025-02-12 11:03:26 +02:00
Yuri Kuznetsov 48c2bda539 cleanup 2025-02-12 10:57:20 +02:00
Yuri Kuznetsov 7888f5ca74 image number 2025-02-12 10:57:10 +02:00
Yuri Kuznetsov 1dfdb261e1 image preview ref 2025-02-12 10:25:45 +02:00
Yuri Kuznetsov 7b0217be6a cleanup 2025-02-12 09:34:37 +02:00
Yuri Kuznetsov bab9140ef2 fit image on maximize 2025-02-11 21:18:50 +02:00
Yuri Kuznetsov ddee6815af full screen image modal 2025-02-11 20:31:01 +02:00
Yuri Kuznetsov 4e9dbc98e3 image/avif 2025-02-11 19:06:34 +02:00
Yuri Kuznetsov c313e72f75 target list categories 2025-02-11 18:45:46 +02:00
Yuri Kuznetsov e0c84e007c client page theme, lead form theme 2025-02-11 15:35:00 +02:00
Yuri Kuznetsov fe2f15c576 webhook: skip version number 2025-02-11 14:27:27 +02:00
Yuri Kuznetsov 87ef57eb29 client page custom title, lead form custom title 2025-02-11 14:23:00 +02:00
Yuri Kuznetsov 6f75dd3dbe cleanup 2025-02-11 13:11:19 +02:00
Yuri Kuznetsov 1d45e6d839 Merge branch 'fix' 2025-02-11 13:08:41 +02:00
Yuri Kuznetsov 87875c1a7f task reminder re-appearing fix 2025-02-11 13:08:32 +02:00
Yuri Kuznetsov 8d6e8f2610 optimistic concurrency control improvement and ref 2025-02-11 12:54:33 +02:00
Yuri Kuznetsov fe81fd3549 ref 2025-02-11 11:44:46 +02:00
Yuri Kuznetsov fbfa219615 resolve conflict: improve ui 2025-02-11 11:39:41 +02:00
Yuri Kuznetsov 1e1ee6ba8c enable concurrency writing control for some entity types 2025-02-11 11:30:33 +02:00
Yuri Kuznetsov c51bb22822 ref 2025-02-11 11:28:13 +02:00
Yuri Kuznetsov 7c32b525a3 entity manager: rebuild on reset to default 2025-02-11 11:21:56 +02:00
Yuri Kuznetsov b242119e00 varcha field: any of, none of filters 2025-02-10 18:48:20 +02:00
Yuri Kuznetsov ecf90a3941 navbar tabs: remove last dividers 2025-02-10 17:16:46 +02:00
Yuri Kuznetsov c97ba58dcb Merge branch 'fix' 2025-02-10 16:59:27 +02:00
Yuri Kuznetsov e802ceea86 markdown: disable header ids 2025-02-10 16:59:13 +02:00
Yuri Kuznetsov 280accd9ad complex text style changes 2025-02-10 16:57:31 +02:00
Yuri Kuznetsov f59122254d case & task description height 2025-02-10 16:36:48 +02:00
Yuri Kuznetsov 137b6fcff7 text field: show more after editing 2025-02-10 16:31:51 +02:00
Yuri Kuznetsov 44b9f60478 text field: prevent unwanted scroll on enter 2025-02-10 16:25:10 +02:00
Yuri Kuznetsov a34c0ff776 Merge branch 'fix' 2025-02-10 16:14:14 +02:00
Yuri Kuznetsov 4dfce57bd1 css fix 2025-02-10 10:25:44 +02:00
Yuri Kuznetsov 9c45642d79 Merge branch 'stable' 2025-02-09 23:00:16 +02:00
Yuri Kuznetsov bfd1ff5fa7 9.0.4 2025-02-09 22:51:15 +02:00
Yuri Kuznetsov 2322b2d1da fix empty group tab causing js error 2025-02-09 22:51:04 +02:00
Yuri Kuznetsov b8dc4e2dcd collection maxSize option 2025-02-09 12:17:41 +02:00
Yuri Kuznetsov e4959e0c38 cleanup 2025-02-08 15:18:50 +02:00
Yuri Kuznetsov dbafd6ba4d ORM: Mapper relate return false if no row affected 2025-02-08 15:16:43 +02:00
Yuri Kuznetsov 0f1057a6cf Merge branch 'fix' 2025-02-08 14:55:23 +02:00
Yuri Kuznetsov 88b0479366 field: fromView set option 2025-02-08 09:35:11 +02:00
Yuri Kuznetsov 4ce40dd85c markdown list items erase empty 2025-02-07 22:59:06 +02:00
Yuri Kuznetsov bf2813c241 Merge branch 'fix' 2025-02-07 22:43:03 +02:00
Yuri Kuznetsov cf4045faec disable code ligatures 2025-02-07 22:42:51 +02:00
Yuri Kuznetsov 6349db3122 text field markdown line syntax helper 2025-02-07 22:05:46 +02:00
Yuri Kuznetsov 62c7af7b38 style from options reference 2025-02-07 20:14:46 +02:00
Yuri Kuznetsov 6ab55fcd22 return static 2025-02-07 19:56:42 +02:00
Yuri Kuznetsov 947f049893 init systemConfig 2025-02-07 19:53:50 +02:00
Yuri Kuznetsov 71cbbbe556 Merge branch 'fix' 2025-02-07 19:40:59 +02:00
Yuri Kuznetsov bfc5e8054d cleanup 2025-02-07 15:26:58 +02:00
Yuri Kuznetsov 62a290c197 frontend portal acl: use accountLink and contactLink 2025-02-07 15:21:14 +02:00
Yuri Kuznetsov b2ee92d606 fix test 2025-02-07 14:30:08 +02:00
Yuri Kuznetsov b68100df38 skip version check if @@version 2025-02-07 14:12:48 +02:00
Yuri Kuznetsov 41b8bb0acc config services 2025-02-07 14:05:08 +02:00
Yuri Kuznetsov 1b52f3ca3e ref 2025-02-07 13:55:28 +02:00
Yuri Kuznetsov 8ff6c8bd03 Merge branch 'fix' 2025-02-07 13:53:31 +02:00
Yuri Kuznetsov ed249366f9 bundles changes 2025-02-06 20:47:08 +02:00
Yuri Kuznetsov ed9cb77bba use bfcache 2025-02-06 12:56:23 +02:00
Yuri Kuznetsov 6ad1d917bd Merge branch 'master' of https://github.com/espocrm/espocrm 2025-02-06 11:05:07 +02:00
Yuri Kuznetsov e5c24a5fdc Merge branch 'stable' 2025-02-06 11:04:54 +02:00
Yuri Kuznetsov 15d771e2a8 9.0.3 2025-02-06 10:54:11 +02:00
Yuri Kuznetsov b38bd4bc1c style fix 2025-02-06 10:54:00 +02:00
Yuri Kuznetsov b35efdaec5 merge relationship columns 2025-02-06 10:40:49 +02:00
Yuri Kuznetsov bdf81780f1 schema 2025-02-06 10:10:14 +02:00
Yuri Kuznetsov db030173e2 ref 2025-02-06 09:52:12 +02:00
Yuri Kuznetsov aced5f2d5d htmlizer: fix fallback 2025-02-05 13:28:59 +02:00
Yuri Kuznetsov 59c06302a1 htmlizer: apply encoding before manipulating dom 2025-02-05 13:10:13 +02:00
Yuri Kuznetsov ba64637e6f disable dynamic logic visible for users field 2025-02-05 12:52:31 +02:00
Yuri Kuznetsov 574a1f8d6a top navbar resize fix 2025-02-05 12:43:02 +02:00
Yuri Kuznetsov d93bc73f13 userdata provider 2025-02-04 16:00:03 +02:00
Yuri Kuznetsov 293cc6d775 fix jsdoc 2025-02-04 15:26:11 +02:00
Yuri Kuznetsov e7539d4930 no error text when error label 2025-02-04 11:21:47 +02:00
Yuri Kuznetsov 53ade3bd23 panel with empty list-container no padding 2025-02-04 10:37:45 +02:00
Yuri Kuznetsov 1991adc48e mail parser: fallback to message-$i file name 2025-02-03 21:38:13 +02:00
dependabot[bot] a6dd1d9db0 Bump phpoffice/phpspreadsheet from 1.29.8 to 1.29.9 (#3262)
Bumps [phpoffice/phpspreadsheet](https://github.com/PHPOffice/PhpSpreadsheet) from 1.29.8 to 1.29.9.
- [Release notes](https://github.com/PHPOffice/PhpSpreadsheet/releases)
- [Changelog](https://github.com/PHPOffice/PhpSpreadsheet/blob/1.29.9/CHANGELOG.md)
- [Commits](https://github.com/PHPOffice/PhpSpreadsheet/compare/1.29.8...1.29.9)

---
updated-dependencies:
- dependency-name: phpoffice/phpspreadsheet
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 18:44:39 +02:00
Yuri Kuznetsov 28694059c2 records dashlet: row actions in metadata 2025-02-03 16:57:13 +02:00
Yuri Kuznetsov bd13d45207 fix record tree entity removal when no real subject used 2025-02-02 13:59:05 +02:00
Yuri Kuznetsov abd9fb9985 free email domain checker inerface and implementation 2025-02-01 17:52:23 +02:00
Yuri Kuznetsov b334da55ed fix comment 2025-02-01 13:44:01 +02:00
Yuri Kuznetsov c6c2d5a0d9 ref 2025-02-01 12:56:13 +02:00
Yuri Kuznetsov fc89a1bdf6 Merge branch 'fix' 2025-02-01 12:47:20 +02:00
Yuri Kuznetsov 4ab929dc93 deleteFromDb not depreacted 2025-02-01 12:46:12 +02:00
Yuri Kuznetsov c7a56b5c38 rename label 2025-01-31 21:27:00 +02:00
Yuri Kuznetsov 80c7c12361 lead form autocompletion 2025-01-31 21:19:58 +02:00
Yuri Kuznetsov 489f71ea47 getVersion method 2025-01-31 21:09:21 +02:00
Yuri Kuznetsov 029331b3b7 Merge branch 'fix' 2025-01-31 21:01:39 +02:00
Yuri Kuznetsov 70aba2ec52 system config 2025-01-31 21:00:55 +02:00
Yuri Kuznetsov ed0b8b4758 fix schema 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov 251fb2cd49 phone number international validator class 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov 0dc5b8282a larger gap above bottom panels if tabs in recird middle 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov d5eb165ea4 no user select on control label 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov 9d4b39c1f7 one time code autocomplete param 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov c3dda027bf form 500px width, sticky button centered 2025-01-31 21:00:54 +02:00
Yuri Kuznetsov 5e2b841ae2 fix schema 2025-01-30 22:16:19 +02:00
Yuri Kuznetsov 324dab780d phone number international validator class 2025-01-30 17:04:25 +02:00
Yuri Kuznetsov ba166c86c4 larger gap above bottom panels if tabs in recird middle 2025-01-30 12:09:53 +02:00
Yuri Kuznetsov 3e08e8f166 no user select on control label 2025-01-30 11:56:09 +02:00
Yuri Kuznetsov 685069a761 one time code autocomplete param 2025-01-30 08:56:34 +02:00
Yuri Kuznetsov c152c399bd form 500px width, sticky button centered 2025-01-27 22:19:06 +02:00
dependabot[bot] 0307e9752d Bump phpoffice/phpspreadsheet from 1.29.7 to 1.29.8 (#3256)
Bumps [phpoffice/phpspreadsheet](https://github.com/PHPOffice/PhpSpreadsheet) from 1.29.7 to 1.29.8.
- [Release notes](https://github.com/PHPOffice/PhpSpreadsheet/releases)
- [Changelog](https://github.com/PHPOffice/PhpSpreadsheet/blob/1.29.8/CHANGELOG.md)
- [Commits](https://github.com/PHPOffice/PhpSpreadsheet/compare/1.29.7...1.29.8)

---
updated-dependencies:
- dependency-name: phpoffice/phpspreadsheet
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 18:24:53 +02:00
Yuri Kuznetsov c5ff0197bb 9.0.2 2025-01-27 17:43:51 +02:00
Yuri Kuznetsov 2417dbf7de fix role translation 2025-01-27 14:48:05 +02:00
Yuri Kuznetsov 4f97358b2f notNull schema 2025-01-27 13:30:58 +02:00
Yuri Kuznetsov 3244347b71 schema 2025-01-27 10:22:27 +02:00
Yuri Kuznetsov 54be896942 fix meeting users avatar 2025-01-27 09:15:01 +02:00
Yuri Kuznetsov 8f690966fc color fixes 2025-01-25 19:16:35 +02:00
Yuri Kuznetsov bccf348b7e color fix 2025-01-25 12:31:52 +02:00
Yuri Kuznetsov b02c3d8d9b color fix 2025-01-25 12:26:31 +02:00
Yuri Kuznetsov 39e29a9d02 before script ignore custom vendor 2025-01-25 09:24:06 +02:00
Yuri Kuznetsov 26b1be00e8 record grid adjust percentage 2025-01-24 21:18:56 +02:00
Yuri Kuznetsov aff531e62b schema docs 2025-01-24 13:50:40 +02:00
Yuri Kuznetsov e8de15e548 fix label 2025-01-24 10:57:41 +02:00
Yuri Kuznetsov 03cd76fff5 base controller error actions 2025-01-24 10:56:46 +02:00
Yuri Kuznetsov 4750313d37 task handler show hide reminders 2025-01-23 13:23:40 +02:00
Yuri Kuznetsov 0c1a310329 btn icon style fix 2025-01-23 12:46:45 +02:00
Yuri Kuznetsov 78e2e8b927 9.0.1 2025-01-22 19:36:59 +02:00
Yuri Kuznetsov bfcbbc4388 fix datepicker min date 2025-01-22 16:51:47 +02:00
Yuri Kuznetsov 52af8e27dd web form: pre-set required 2025-01-22 16:42:54 +02:00
Yuri Kuznetsov eb948ea53e dompdf add meta tag 2025-01-22 16:19:34 +02:00
Yuri Kuznetsov ee314b4374 checkboxesAreDisabled check 2025-01-21 16:32:30 +02:00
Yuri Kuznetsov 6b1a844fea jsdoc fix 2025-01-21 14:06:06 +02:00
Yuri Kuznetsov c9c09aab77 total count no select 2025-01-21 12:35:00 +02:00
Yuri Kuznetsov 285f5191c2 margin classes 2025-01-21 12:11:36 +02:00
Yuri Kuznetsov e5a39cd134 fix path 2025-01-20 19:59:50 +02:00
Yuri Kuznetsov 33460b42a7 ignore Sales module in before upgrade script 2025-01-20 19:58:28 +02:00
Yuri Kuznetsov 0bd0ce057d cs 2025-01-20 19:49:33 +02:00
Yuri Kuznetsov e63ec875db jsdoc fix 2025-01-20 17:06:58 +02:00
1537 changed files with 38818 additions and 14590 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
test:
name: Test on PHP ${{ matrix.php-versions }}
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
env:
TEST_DATABASE_HOST: '127.0.0.1'
TEST_DATABASE_PLATFORM: 'Postgresql'
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
test:
name: Test on PHP ${{ matrix.php-versions }}
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
env:
TEST_DATABASE_HOST: '127.0.0.1'
TEST_DATABASE_PORT: '8888'
+1 -1
View File
@@ -23,7 +23,7 @@ on:
jobs:
test:
name: Test on PHP ${{ matrix.php-versions }}
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
strategy:
matrix:
php-versions: ['8.2', '8.3', '8.4']
+38
View File
@@ -341,6 +341,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/clientIcons">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/app/clientIcons" />
<option name="relativePathToSchema" value="schema/metadata/app/clientIcons.json" />
<option name="schemaVersion" value="JSON Schema version 7" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/Resources/metadata/app/clientIcons.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/clientRecord">
<value>
<SchemaInfo>
@@ -1386,6 +1405,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/logicDefs">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/logicDefs" />
<option name="relativePathToSchema" value="schema/metadata/logicDefs.json" />
<option name="schemaVersion" value="JSON Schema version 7" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/metadata/logicDefs/*.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/notificationDefs">
<value>
<SchemaInfo>
+12
View File
@@ -82,6 +82,12 @@
],
"url": "./schema/metadata/integrations.json"
},
{
"fileMatch": [
"*/metadata/logicDefs/*.json"
],
"url": "./schema/metadata/logicDefs.json"
},
{
"fileMatch": [
"*/metadata/notificationDefs/*.json"
@@ -184,6 +190,12 @@
],
"url": "./schema/metadata/app/client.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/clientIcons.json"
],
"url": "./schema/metadata/app/clientIcons.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/clientRecord.json"
+6 -2
View File
@@ -184,8 +184,12 @@ module.exports = grunt => {
comments: /^!/,
},
beautify: false,
mangle: true,
compress: true
compress: {
sequences: false,
collapse_vars: false,
keep_fargs: true,
webkit: true,
},
},
bundle: {
options: {
+10
View File
@@ -232,6 +232,16 @@ class Binding implements BindingProcessor
'Espo\\Tools\\Stream\\Service',
'streamService'
);
$binder->bindService(
'Espo\\Core\\Utils\\Config\\SystemConfig',
'systemConfig'
);
$binder->bindService(
'Espo\\Core\\Utils\\Config\\ApplicationConfig',
'applicationConfig'
);
}
private function bindCore(Binder $binder): void
@@ -46,14 +46,10 @@ class AccessChecker implements AccessEntityCREDSChecker
{
use DefaultAccessCheckerDependency;
private DefaultAccessChecker $defaultAccessChecker;
private AclManager $aclManager;
public function __construct(DefaultAccessChecker $defaultAccessChecker, AclManager $aclManager)
{
$this->defaultAccessChecker = $defaultAccessChecker;
$this->aclManager = $aclManager;
}
public function __construct(
private DefaultAccessChecker $defaultAccessChecker,
private AclManager $aclManager,
) {}
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
{
@@ -70,6 +66,10 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
if (!$user->isAdmin() && !$entity->isActive()) {
return false;
}
if ($entity->isPortal()) {
if ($this->aclManager->getPermissionLevel($user, Permission::PORTAL) === Table::LEVEL_YES) {
return true;
@@ -68,9 +68,9 @@ class AccessChecker implements AccessEntityCREDSChecker
assert($entity instanceof CoreEntity);
$userIdList = $entity->getLinkMultipleIdLIst('users');
$userIdList = $entity->getLinkMultipleIdList('users');
if (is_array($userIdList) && in_array($user->getId(), $userIdList)) {
if (in_array($user->getId(), $userIdList)) {
return true;
}
@@ -47,6 +47,10 @@ class AppLog implements Cleanup
public function process(): void
{
if (!$this->config->get('cleanupAppLog')) {
return;
}
$query = DeleteBuilder::create()
->from(AppLogRecord::ENTITY_TYPE)
->where(['createdAt<' => $this->getBefore()->toString()])
@@ -31,42 +31,40 @@ namespace Espo\Classes\Cleanup;
use Espo\Core\Cleanup\Cleanup;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Espo\Entities\WebhookEventQueueItem;
use Espo\Entities\WebhookQueueItem;
use Espo\ORM\EntityManager;
use DateTime;
use Espo\ORM\Name\Attribute;
/**
* @noinspection PhpUnused
*/
class WebhookQueue implements Cleanup
{
private string $cleanupWebhookQueuePeriod = '10 days';
private $config;
private $entityManager;
public function __construct(Config $config, EntityManager $entityManager)
{
$this->config = $config;
$this->entityManager = $entityManager;
}
public function __construct(private Config $config, private EntityManager $entityManager)
{}
public function process(): void
{
$period = '-' . $this->config->get('cleanupWebhookQueuePeriod', $this->cleanupWebhookQueuePeriod);
$datetime = new DateTime();
$datetime->modify($period);
$from = $datetime->format('Y-m-d H:i:s');
$from = $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
$query1 = $this->entityManager
->getQueryBuilder()
->delete()
->from('WebhookQueueItem')
->from(WebhookQueueItem::ENTITY_TYPE)
->where([
'DATE:(createdAt)<' => $from,
'OR' => [
'status!=' => 'Pending',
'deleted' => true,
'status!=' => WebhookQueueItem::STATUS_PENDING,
Attribute::DELETED => true,
],
])
->build();
@@ -76,12 +74,12 @@ class WebhookQueue implements Cleanup
$query2 = $this->entityManager
->getQueryBuilder()
->delete()
->from('WebhookEventQueueItem')
->from(WebhookEventQueueItem::ENTITY_TYPE)
->where([
'DATE:(createdAt)<' => $from,
'OR' => [
'isProcessed' => true,
'deleted' => true,
Attribute::DELETED => true,
],
])
->build();
@@ -85,7 +85,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
$column => '{value}',
],
],
@@ -97,7 +97,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
$column => '{value}',
],
],
@@ -109,7 +109,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
$column => '{value}',
],
],
@@ -121,7 +121,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
$column => '{value}',
],
],
@@ -133,7 +133,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
"$column*" => '{value}',
],
],
@@ -145,7 +145,7 @@ class RelationshipRole implements FieldConverter
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
Attribute::DELETED => false,
"$column*" => '{value}',
],
],
@@ -52,11 +52,6 @@ class AddressDataLoader implements Loader
/** @var EmailRepository $repository */
$repository = $this->entityManager->getRepository(Email::ENTITY_TYPE);
$repository->loadFromField($entity);
$repository->loadToField($entity);
$repository->loadCcField($entity);
$repository->loadBccField($entity);
$repository->loadReplyToField($entity);
$repository->loadNameHash($entity);
}
}
@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldProcessing\Email;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
use Espo\Entities\Email;
use Espo\ORM\Entity;
use Espo\Repositories\Email as EmailRepository;
/**
* @implements Loader<Email>
*/
class AddressLoader implements Loader
{
public function __construct(private EntityManager $entityManager)
{}
/**
* @inheritDoc
*/
public function process(Entity $entity, Params $params): void
{
/** @var EmailRepository $repository */
$repository = $this->entityManager->getRepository(Email::ENTITY_TYPE);
$repository->loadFromField($entity);
$repository->loadToField($entity);
$repository->loadCcField($entity);
$repository->loadBccField($entity);
$repository->loadReplyToField($entity);
}
}
@@ -35,6 +35,7 @@ use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\ORM\EntityManager;
use Espo\Entities\User;
use Espo\ORM\Name\Attribute;
/**
* @implements Loader<Email>
@@ -57,7 +58,7 @@ class UserColumnsLoader implements Loader
Email::USERS_COLUMN_IN_ARCHIVE,
])
->where([
'deleted' => false,
Attribute::DELETED => false,
'userId' => $this->user->getId(),
'emailId' => $entity->getId(),
])
@@ -31,7 +31,7 @@ namespace Espo\Classes\FieldProcessing\InboundEmail;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\Utils\Config;
use Espo\Core\Mail\ConfigDataProvider;
use Espo\Entities\InboundEmail;
use Espo\ORM\Entity;
@@ -41,12 +41,12 @@ use Espo\ORM\Entity;
class IsSystemLoader implements Loader
{
public function __construct(
private Config $config,
private ConfigDataProvider $configDataProvider,
) {}
public function process(Entity $entity, Params $params): void
{
$isSystem = $entity->getEmailAddress() === $this->config->get('outboundEmailFromAddress');
$isSystem = $entity->getEmailAddress() === $this->configDataProvider->getSystemOutboundAddress();
$entity->set('isSystem', $isSystem);
}
@@ -1,3 +1,4 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -26,34 +27,46 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('crm:views/calendar/record/edit-view', ['views/record/base'], function (Dep) {
namespace Espo\Classes\FieldProcessing\OAuthAccount;
return Dep.extend({
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Entities\OAuthAccount;
use Espo\ORM\Entity;
use Espo\Tools\OAuth\ConfigDataProvider;
template: 'crm:calendar/record/edit-view',
/**
* @implements Loader<OAuthAccount>
*/
class DataLoader implements Loader
{
public function __construct(
private ConfigDataProvider $configDataProvider,
) {}
setup: function () {
Dep.prototype.setup.call(this);
public function process(Entity $entity, Params $params): void
{
if (!$entity->get('providerId')) {
return;
}
this.createField('mode', 'views/fields/enum', {
options: this.getMetadata().get(['clientDefs', 'Calendar', 'sharedViewModeList']) || [],
translation: 'DashletOptions.options.mode'
}, null, null, {
labelText: this.translate('mode', 'fields', 'DashletOptions')
});
$provider = $entity->getProvider();
this.createField('name', 'views/fields/varchar', {
required: true
}, null, null, {
labelText: this.translate('name', 'fields')
});
$scope = null;
this.createField('teams', 'crm:views/calendar/fields/teams', {
required: true
}, null, null, {
labelText: this.translate('teams', 'fields'),
foreignScope: 'Team'
});
},
});
});
if ($provider->getScopes()) {
$scope = implode($provider->getScopeSeparator() ?? ' ', $provider->getScopes());
}
$data = [
'endpoint' => $provider->getAuthorizationEndpoint(),
'clientId' => $provider->getClientId(),
'redirectUri' => $this->configDataProvider->getRedirectUri(),
'scope' => $scope,
'prompt' => $provider->getAuthorizationPrompt(),
'params' => $provider->getAuthorizationParams(),
];
$entity->set('data', $data);
}
}
@@ -0,0 +1,51 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldProcessing\OAuthProvider;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Entities\OAuthProvider;
use Espo\ORM\Entity;
use Espo\Tools\OAuth\ConfigDataProvider;
/**
* @implements Loader<OAuthProvider>
*/
class AuthorizationRedirectUriLoader implements Loader
{
public function __construct(
private ConfigDataProvider $configDataProvider,
) {}
public function process(Entity $entity, Params $params): void
{
$entity->set('authorizationRedirectUri', $this->configDataProvider->getRedirectUri());
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\FieldValidators;
use Espo\Core\Field\Currency;
use Espo\Core\Utils\Config;
use Espo\ORM\BaseEntity;
use Espo\ORM\Defs\Params\AttributeParam;
use Espo\ORM\Entity;
class CurrencyType extends FloatType
@@ -83,7 +84,7 @@ class CurrencyType extends FloatType
}
/** @var int $precision */
$precision = $entity->getAttributeParam($field, 'precision') ?? self::DEFAULT_PRECISION;
$precision = $entity->getAttributeParam($field, AttributeParam::PRECISION) ?? self::DEFAULT_PRECISION;
$value = $entity->get($field);
@@ -95,6 +96,8 @@ class CurrencyType extends FloatType
$pad = str_pad('', $precision, '9');
assert(is_numeric($pad));
$limit = Currency::create($pad, 'USD');
if ($currency->compare($limit) === 1) {
+106 -147
View File
@@ -41,7 +41,6 @@ use Espo\Entities\ArrayValue;
use Espo\Entities\Attachment;
use Espo\Entities\AuthLogRecord;
use Espo\Entities\AuthToken;
use Espo\Entities\Email;
use Espo\Entities\Job;
use Espo\Entities\Note;
use Espo\Entities\Notification;
@@ -98,7 +97,6 @@ class Cleanup implements JobDataLess
$this->cleanupJobs();
$this->cleanupScheduledJobLog();
$this->cleanupAttachments();
$this->cleanupEmails();
$this->cleanupNotifications();
$this->cleanupActionHistory();
$this->cleanupAuthToken();
@@ -149,7 +147,7 @@ class Cleanup implements JobDataLess
->where([
'modifiedAt<' => $this->getCleanupJobFromDate(),
'status=' => JobStatus::PENDING,
'deleted' => true,
Attribute::DELETED => true,
])
->build();
@@ -412,7 +410,7 @@ class Cleanup implements JobDataLess
->from($scope)
->withDeleted()
->where([
'deleted' => true,
Attribute::DELETED => true,
'modifiedAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
'modifiedAt>' => $datetimeFrom->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
])
@@ -465,7 +463,7 @@ class Cleanup implements JobDataLess
->delete()
->from(Attachment::ENTITY_TYPE)
->where([
'deleted' => true,
Attribute::DELETED => true,
'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
])
->build();
@@ -473,68 +471,6 @@ class Cleanup implements JobDataLess
$this->entityManager->getQueryExecutor()->execute($delete);
}
private function cleanupEmails(): void
{
$dateBefore = date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT, time() - 3600 * 24 * 20);
$query = $this->entityManager
->getQueryBuilder()
->select()
->from(Email::ENTITY_TYPE)
->withDeleted()
->build();
/** @var iterable<Email> $emails */
$emails = $this->entityManager
->getRDBRepository(Email::ENTITY_TYPE)
->clone($query)
->sth()
->select([Attribute::ID])
->where([
'createdAt<' => $dateBefore,
'deleted' => true,
])
->find();
foreach ($emails as $email) {
$id = $email->getId();
$attachments = $this->entityManager
->getRDBRepository(Attachment::ENTITY_TYPE)
->where([
'parentId' => $id,
'parentType' => Email::ENTITY_TYPE,
])
->find();
foreach ($attachments as $attachment) {
$this->entityManager->removeEntity($attachment);
}
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from(Email::ENTITY_TYPE)
->where([
'deleted' => true,
'id' => $id,
])
->build();
$this->entityManager->getQueryExecutor()->execute($delete);
$delete = $this->entityManager
->getQueryBuilder()
->delete()
->from(Email::RELATIONSHIP_EMAIL_USER)
->where([
'emailId' => $id,
])
->build();
$this->entityManager->getQueryExecutor()->execute($delete);
}
}
private function cleanupNotifications(): void
{
@@ -584,17 +520,16 @@ class Cleanup implements JobDataLess
{
$scope = $entity->getEntityType();
if (!$entity->get('deleted')) {
if (!$entity->get(Attribute::DELETED)) {
return;
}
$repository = $this->entityManager->getRepository($scope);
if (!$repository instanceof RDBRepository) {
return;
}
if (!$entity instanceof CoreEntity) {
if (
!$repository instanceof RDBRepository ||
!$entity instanceof CoreEntity
) {
return;
}
@@ -647,82 +582,14 @@ class Cleanup implements JobDataLess
}
}
$query = $this->entityManager
->getQueryBuilder()
->select()
->from(Note::ENTITY_TYPE)
->withDeleted()
->build();
$noteList = $this->entityManager
->getRDBRepository(Note::ENTITY_TYPE)
->clone($query)
->sth()
->where([
'OR' => [
[
'relatedType' => $scope,
'relatedId' => $entity->getId(),
],
[
'parentType' => $scope,
'parentId' => $entity->getId(),
]
]
])
->find();
foreach ($noteList as $note) {
$this->entityManager->removeEntity($note);
$note->set('deleted', true);
$this->cleanupDeletedEntity($note);
}
$this->cleanupEntityNotes($entity);
$this->cleanupEntityAttachments($entity);
if ($scope === Note::ENTITY_TYPE) {
$attachmentList = $this->entityManager
->getRDBRepository(Attachment::ENTITY_TYPE)
->where([
'parentId' => $entity->getId(),
'parentType' => Note::ENTITY_TYPE,
])
->find();
foreach ($attachmentList as $attachment) {
$this->entityManager->removeEntity($attachment);
$this->entityManager
->getRDBRepository(Attachment::ENTITY_TYPE)
->deleteFromDb($attachment->getId());
}
// @todo If ever reactions are supported not only for notes, then move out of the if-block.
$deleteReactionsQuery = DeleteBuilder::create()
->from(UserReaction::ENTITY_TYPE)
->where([
'parentId' => $entity->getId(),
'parentType' => Note::ENTITY_TYPE,
])
->build();
$this->entityManager->getQueryExecutor()->execute($deleteReactionsQuery);
$this->cleanupNoteReactions($entity);
}
$arrayValueList = $this->entityManager
->getRDBRepository(ArrayValue::ENTITY_TYPE)
->sth()
->where([
'entityType' => $entity->getEntityType(),
'entityId' => $entity->getId(),
])
->find();
foreach ($arrayValueList as $arrayValue) {
$this->entityManager
->getRDBRepository(ArrayValue::ENTITY_TYPE)
->deleteFromDb($arrayValue->getId());
}
$this->cleanupEntityArrayValues($entity);
}
private function cleanupDeletedRecords(): void
@@ -759,13 +626,13 @@ class Cleanup implements JobDataLess
$service = $this->recordServiceContainer->get($scope);
$whereClause = ['deleted' => true];
$whereClause = [Attribute::DELETED => true];
if (
!$this->entityManager
->getDefs()
->getEntity($scope)
->hasAttribute('deleted')
->hasAttribute(Attribute::DELETED)
) {
continue;
}
@@ -816,4 +683,96 @@ class Cleanup implements JobDataLess
return $datetime;
}
private function cleanupEntityAttachments(CoreEntity $entity): void
{
// @todo Add file, image types support.
$attachments = $this->entityManager
->getRDBRepository(Attachment::ENTITY_TYPE)
->where([
'parentId' => $entity->getId(),
'parentType' => $entity->getEntityType(),
])
->find();
foreach ($attachments as $attachment) {
$this->entityManager->removeEntity($attachment);
$this->entityManager
->getRDBRepository(Attachment::ENTITY_TYPE)
->deleteFromDb($attachment->getId());
}
}
private function cleanupEntityNotes(CoreEntity $entity): void
{
$scope = $entity->getEntityType();
$query = $this->entityManager
->getQueryBuilder()
->select()
->from(Note::ENTITY_TYPE)
->withDeleted()
->build();
$noteList = $this->entityManager
->getRDBRepository(Note::ENTITY_TYPE)
->clone($query)
->sth()
->where([
'OR' => [
[
'relatedType' => $scope,
'relatedId' => $entity->getId(),
],
[
'parentType' => $scope,
'parentId' => $entity->getId(),
]
]
])
->find();
foreach ($noteList as $note) {
$this->entityManager->removeEntity($note);
$note->set(Attribute::DELETED, true);
$this->cleanupDeletedEntity($note);
}
}
private function cleanupNoteReactions(CoreEntity $entity): void
{
// @todo If ever reactions are supported not only for notes, then move out of the if-block.
$deleteReactionsQuery = DeleteBuilder::create()
->from(UserReaction::ENTITY_TYPE)
->where([
'parentId' => $entity->getId(),
'parentType' => Note::ENTITY_TYPE,
])
->build();
$this->entityManager->getQueryExecutor()->execute($deleteReactionsQuery);
}
private function cleanupEntityArrayValues(CoreEntity $entity): void
{
$arrayValues = $this->entityManager
->getRDBRepository(ArrayValue::ENTITY_TYPE)
->sth()
->where([
'entityType' => $entity->getEntityType(),
'entityId' => $entity->getId(),
])
->find();
foreach ($arrayValues as $arrayValue) {
$this->entityManager
->getRDBRepository(ArrayValue::ENTITY_TYPE)
->deleteFromDb($arrayValue->getId());
}
}
}
@@ -0,0 +1,71 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Record\OAuthProvider;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Record\Input\Data;
use Espo\Core\Record\Input\Filter;
use Espo\Core\Utils\Crypt;
/**
* @noinspection PhpUnused
*/
class GeneralFilter implements Filter
{
private const ATTR_CLIENT_SECRET = 'clientSecret';
public function __construct(private Crypt $crypt) {}
/**
* @throws BadRequest
*/
public function filter(Data $data): void
{
$this->processClientSecret($data);
}
/**
* @throws BadRequest
*/
private function processClientSecret(Data $data): void
{
$value = $data->get(self::ATTR_CLIENT_SECRET);
if ($value === null) {
return;
}
if (!is_string($value)) {
throw new BadRequest();
}
$data->set(self::ATTR_CLIENT_SECRET, $this->crypt->encrypt($value));
}
}
@@ -29,7 +29,7 @@
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Mail\Sender;
use Espo\Core\Mail\EmailSender;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\ORM\Entity;
@@ -42,7 +42,7 @@ class BeforeCreate implements SaveHook
public function process(Entity $entity): void
{
if ($entity->getStatus() === Email::STATUS_SENDING) {
$messageId = Sender::generateMessageId($entity);
$messageId = EmailSender::generateMessageId($entity);
$entity->setMessageId('<' . $messageId . '>');
}
@@ -33,13 +33,13 @@ use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\ORM\Entity;
use Espo\Tools\Email\Util;
/**
* @implements SaveHook<Email>
*/
class BeforeSave implements SaveHook
{
public function process(Entity $entity): void
{
if (
@@ -49,5 +49,22 @@ class BeforeSave implements SaveHook
) {
throw new BadRequest("Cannot set send-at if status is not Draft.");
}
$this->processBodyPlain($entity);
}
private function processBodyPlain(Email $entity): void
{
if (!$entity->isHtml() || !$entity->isAttributeChanged('body')) {
return;
}
$body = $entity->getBody();
if ($body) {
$body = Util::stripHtml($body) ?: null;
}
$entity->setBodyPlain($body);
}
}
@@ -29,7 +29,7 @@
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Mail\Sender;
use Espo\Core\Mail\EmailSender;
use Espo\Core\Name\Field;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\FieldUtil;
@@ -94,7 +94,7 @@ class BeforeUpdate implements SaveHook
}
if ($entity->getStatus() == Email::STATUS_SENDING) {
$messageId = Sender::generateMessageId($entity);
$messageId = EmailSender::generateMessageId($entity);
$entity->setMessageId('<' . $messageId . '>');
}
@@ -201,11 +201,16 @@ class InFolder implements ItemConverter
return WhereClause::fromRaw([
'groupFolderId' => $groupFolderId,
'groupStatusFolder' => null,
'createdById!=' => $this->user->getId(),
'fromEmailAddressId!=' => $this->getEmailAddressIdList(),
'status' => [
Email::STATUS_ARCHIVED,
Email::STATUS_SENT,
],
'OR' => [
'status' => Email::STATUS_ARCHIVED,
'createdById!=' => $this->user->getId(),
],
]);
}
@@ -30,6 +30,7 @@
namespace Espo\Classes\Select\EmailAddress\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Query\SelectBuilder;
class Orphan implements Filter
@@ -42,24 +43,24 @@ class Orphan implements Filter
'EntityEmailAddress',
'entityEmailAddress',
[
'emailAddressId:' => 'id',
'deleted' => false,
'emailAddressId:' => Attribute::ID,
Attribute::DELETED => false,
]
)
->leftJoin(
'EmailEmailAddress',
'emailEmailAddress',
[
'emailAddressId:' => 'id',
'deleted' => false,
'emailAddressId:' => Attribute::ID,
Attribute::DELETED => false,
]
)
->leftJoin(
'Email',
'email',
[
'fromEmailAddressId:' => 'id',
'deleted' => false,
'fromEmailAddressId:' => Attribute::ID,
Attribute::DELETED => false,
]
)
->where([
@@ -30,6 +30,7 @@
namespace Espo\Classes\Select\PhoneNumber\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Query\SelectBuilder;
class Orphan implements Filter
@@ -44,8 +45,8 @@ class Orphan implements Filter
'EntityPhoneNumber',
'entityPhoneNumber',
[
'phoneNumberId:' => 'id',
'deleted' => false,
'phoneNumberId:' => Attribute::ID,
Attribute::DELETED => false,
]
);
@@ -32,30 +32,26 @@ namespace Espo\Classes\TemplateHelpers;
use Espo\Core\Htmlizer\Helper;
use Espo\Core\Htmlizer\Helper\Data;
use Espo\Core\Htmlizer\Helper\Result;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use const CURLOPT_FOLLOWLOCATION;
use const CURLOPT_HEADER;
use const CURLOPT_HTTPHEADER;
use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_TIMEOUT;
use const CURLOPT_URL;
use const CURLOPT_USERAGENT;
class GoogleMaps implements Helper
{
private const DEFAULT_SIZE = '400x400';
private $metadata;
private $config;
private $log;
public function __construct(
Metadata $metadata,
Config $config,
Log $log
) {
$this->metadata = $metadata;
$this->config = $config;
$this->log = $log;
}
private Metadata $metadata,
private Config $config,
private Log $log,
) {}
public function render(Data $data): Result
{
@@ -194,6 +190,7 @@ class GoogleMaps implements Helper
}
/**
* @param non-empty-string $url
* @return string|bool
*/
private function getImage(string $url)
@@ -207,13 +204,13 @@ class GoogleMaps implements Helper
$c = curl_init();
curl_setopt($c, \CURLOPT_URL, $url);
curl_setopt($c, \CURLOPT_HTTPHEADER, $headers);
curl_setopt($c, \CURLOPT_HEADER, 0);
curl_setopt($c, \CURLOPT_USERAGENT, $agent);
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($c, CURLOPT_URL, $url);
curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
curl_setopt($c, CURLOPT_HEADER, false);
curl_setopt($c, CURLOPT_USERAGENT, $agent);
curl_setopt($c, CURLOPT_TIMEOUT, 10);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
$raw = curl_exec($c);
+7 -1
View File
@@ -54,7 +54,8 @@ class Admin
private Manager $adminNotificationManager,
private SystemRequirements $systemRequirements,
private ScheduledJob $scheduledJob,
private DataManager $dataManager
private DataManager $dataManager,
private Config\SystemConfig $systemConfig,
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
@@ -179,9 +180,14 @@ class Admin
* database: array<string, array<string, mixed>>,
* permission: array<string, array<string, mixed>>,
* }
* @throws Forbidden
*/
public function getActionSystemRequirementList(): object
{
if (!$this->user->isSuperAdmin() && $this->systemConfig->isRestrictedMode()) {
throw new Forbidden();
}
return (object) $this->systemRequirements->getAllRequiredList();
}
}
+15 -9
View File
@@ -30,31 +30,33 @@
namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\NotFoundSilent;
use Espo\Tools\Formula\Service;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Entities\User;
use Espo\Core\Field\LinkParent;
use stdClass;
class Formula
{
private Service $service;
public function __construct(Service $service, User $user)
{
$this->service = $service;
/**
* @throws ForbiddenSilent
*/
public function __construct(
private Service $service,
User $user,
) {
if (!$user->isAdmin()) {
throw new ForbiddenSilent();
}
}
/**
* @throws BadRequest
*/
public function postActionCheckSyntax(Request $request): stdClass
{
$expression = $request->getParsedBody()->expression ?? null;
@@ -66,6 +68,10 @@ class Formula
return $this->service->checkSyntax($expression)->toStdClass();
}
/**
* @throws BadRequest
* @throws NotFoundSilent
*/
public function postActionRun(Request $request): stdClass
{
$expression = $request->getParsedBody()->expression ?? null;
+16 -9
View File
@@ -29,31 +29,34 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\NotFound;
use Espo\Services\Integration as Service;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Api\Request;
use Espo\Entities\User;
use stdClass;
class Integration
{
private $service;
private $user;
public function __construct(Service $service, User $user)
{
$this->service = $service;
$this->user = $user;
/**
* @throws Forbidden
*/
public function __construct(
private Service $service,
private User $user,
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
}
/**
* @throws Forbidden
* @throws NotFound
*/
public function getActionRead(Request $request): stdClass
{
/** @var string $id */
@@ -64,6 +67,10 @@ class Integration
return $entity->getValueMap();
}
/**
* @throws Forbidden
* @throws NotFound
*/
public function putActionUpdate(Request $request): stdClass
{
/** @var string $id */
+4 -4
View File
@@ -35,6 +35,9 @@ use Espo\Tools\ActionHistory\Service as Service;
use stdClass;
/**
* @noinspection PhpUnused
*/
class LastViewed
{
public function __construct(private SearchParamsFetcher $searchParamsFetcher, private Service $service)
@@ -49,9 +52,6 @@ class LastViewed
$result = $this->service->getLastViewed($maxSize, $offset);
return (object) [
'total' => $result->getTotal(),
'list' => $result->getValueMapList(),
];
return $result->toApiOutput();
}
}
@@ -83,10 +83,7 @@ class Notification extends RecordBase
$recordCollection = $this->getNotificationService()->get($userId, $searchParams);
return (object) [
'total' => $recordCollection->getTotal(),
'list' => $recordCollection->getValueMapList(),
];
return $recordCollection->toApiOutput();
}
public function getActionNotReadCount(): int
@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Controllers\RecordBase;
/**
* @noinspection PhpUnused
*/
class OAuthAccount extends RecordBase
{
protected function checkAccess(): bool
{
if (!$this->user->isAdmin()) {
return false;
}
return true;
}
}
@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Controllers\Record;
/**
* @noinspection PhpUnused
*/
class OAuthProvider extends Record
{
protected function checkAccess(): bool
{
if (!$this->user->isAdmin()) {
return false;
}
return true;
}
}
+13 -19
View File
@@ -79,12 +79,12 @@ class Stream
$reactionsCheckDate = DateTime::createNow();
return (object) [
'total' => $collection->getTotal(),
'list' => $collection->getValueMapList(),
'reactionsCheckDate' => $reactionsCheckDate->toString(),
'updatedReactions' => $this->getReactionUpdates($request, $id),
];
$output = $collection->toApiOutput();
$output->reactionsCheckDate = $reactionsCheckDate->toString();
$output->updatedReactions = $this->getReactionUpdates($request, $id);
return $output;
}
if ($id === null) {
@@ -94,11 +94,11 @@ class Stream
$collection = $this->service->find($scope, $id, $searchParams);
$pinnedCollection = $this->service->getPinned($scope, $id);
return (object) [
'total' => $collection->getTotal(),
'list' => $collection->getValueMapList(),
'pinnedList' => $pinnedCollection->getValueMapList(),
];
$output = $collection->toApiOutput();
$output->pinnedList = $pinnedCollection->getValueMapList();
return $output;
}
/**
@@ -126,10 +126,7 @@ class Stream
$this->userRecordService->find($id, $searchParams) :
$this->service->find($scope, $id ?? '', $searchParams);
return (object) [
'total' => $result->getTotal(),
'list' => $result->getValueMapList(),
];
return $result->toApiOutput();
}
/**
@@ -150,10 +147,7 @@ class Stream
$result = $this->service->findUpdates($scope, $id, $searchParams);
return (object) [
'total' => $result->getTotal(),
'list' => $result->getValueMapList(),
];
return $result->toApiOutput();
}
/**
+2 -3
View File
@@ -31,11 +31,10 @@ namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use Espo\Core\Controllers\Record;
use stdClass;
class Webhook extends RecordBase
class Webhook extends Record
{
protected function checkAccess(): bool
{
@@ -0,0 +1,61 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\RecordBase;
use Espo\Core\Exceptions\Forbidden;
use stdClass;
/**
* @noinspection PhpUnused
*/
class WebhookEventQueueItem extends RecordBase
{
protected function checkAccess(): bool
{
if (!$this->user->isAdmin()) {
return false;
}
return true;
}
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
}
@@ -31,6 +31,11 @@ namespace Espo\Core\Acl;
use Espo\Entities\User;
/**
* Bindings:
* - `$entityType` as of v9.1.0.
* - `Espo\Core\AclManager`
*/
interface AccessChecker
{
/**
@@ -58,7 +58,7 @@ class AccessCheckerFactory
{
$className = $this->getClassName($scope);
$bindingContainer = $this->createBindingContainer($aclManager);
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
}
@@ -77,19 +77,27 @@ class AccessCheckerFactory
}
if (!$this->metadata->get(['scopes', $scope])) {
throw new NotImplemented("Access checker is not implemented for '{$scope}'.");
throw new NotImplemented("Access checker is not implemented for '$scope'.");
}
return $this->defaultClassName;
}
private function createBindingContainer(AclManager $aclManager): BindingContainer
/**
* @param class-string<AccessChecker> $className
*/
private function createBindingContainer(string $className, AclManager $aclManager, string $scope): BindingContainer
{
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder->bindInstance(AclManager::class, $aclManager);
$binder
->for($className)
->bindValue('$entityType', $scope);
return new BindingContainer($bindingData);
}
}
@@ -29,7 +29,7 @@
namespace Espo\Core\Acl;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\Utils\Metadata;
@@ -91,10 +91,10 @@ class GlobalRestriction
private Metadata $metadata,
private DataCache $dataCache,
private FieldUtil $fieldUtil,
Config $config
SystemConfig $systemConfig,
) {
$useCache = $config->get('useCache');
$useCache = $systemConfig->useCache();
if ($useCache && $this->dataCache->has($this->cacheKey)) {
/** @var stdClass $cachedData */
@@ -58,6 +58,9 @@ class DataBuilder
public function __construct(private MetadataProvider $metadataProvider, private FieldUtil $fieldUtil)
{}
/**
* @return stdClass&object{table: stdClass, fieldTable: stdClass}
*/
public function build(Table $table): stdClass
{
$data = (object) [
+4 -4
View File
@@ -57,14 +57,14 @@ class Map
public function __construct(
Table $table,
private DataBuilder $dataBuilder,
private Config $config,
private DataCache $dataCache,
CacheKeyProvider $cacheKeyProvider
CacheKeyProvider $cacheKeyProvider,
Config\SystemConfig $systemConfig,
) {
$this->cacheKey = $cacheKeyProvider->get();
if ($this->config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
if ($systemConfig->useCache() && $this->dataCache->has($this->cacheKey)) {
/** @var stdClass $cachedData */
$cachedData = $this->dataCache->get($this->cacheKey);
@@ -72,7 +72,7 @@ class Map
} else {
$this->data = $this->dataBuilder->build($table);
if ($this->config->get('useCache')) {
if ($systemConfig->useCache()) {
$this->dataCache->store($this->cacheKey, $this->data);
}
}
@@ -29,4 +29,9 @@
namespace Espo\Core\Acl;
/**
* Bindings:
* - `$entityType` as of v9.1.0.
* - `Espo\Core\AclManager`
*/
interface OwnershipChecker {}
@@ -58,7 +58,7 @@ class OwnershipCheckerFactory
{
$className = $this->getClassName($scope);
$bindingContainer = $this->createBindingContainer($aclManager);
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
}
@@ -83,7 +83,10 @@ class OwnershipCheckerFactory
return $this->defaultClassName;
}
private function createBindingContainer(AclManager $aclManager): BindingContainer
/**
* @param class-string<OwnershipChecker> $className
*/
private function createBindingContainer(string $className, AclManager $aclManager, string $scope): BindingContainer
{
$bindingData = new BindingData();
@@ -91,6 +94,10 @@ class OwnershipCheckerFactory
$binder->bindInstance(AclManager::class, $aclManager);
$binder
->for($className)
->bindValue('$entityType', $scope);
return new BindingContainer($bindingData);
}
}
@@ -29,15 +29,14 @@
namespace Espo\Core\Acl\Table;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Entities\User;
use Espo\Core\Acl\FieldData;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\Metadata;
use stdClass;
use RuntimeException;
@@ -96,7 +95,7 @@ class DefaultTable implements Table
private RoleListProvider $roleListProvider,
CacheKeyProvider $cacheKeyProvider,
protected User $user,
Config $config,
SystemConfig $systemConfig,
protected Metadata $metadata,
DataCache $dataCache,
) {
@@ -116,7 +115,7 @@ class DefaultTable implements Table
$this->cacheKey = $cacheKeyProvider->get();
if ($config->get('useCache') && $dataCache->has($this->cacheKey)) {
if ($systemConfig->useCache() && $dataCache->has($this->cacheKey)) {
/** @var stdClass $cachedData */
$cachedData = $dataCache->get($this->cacheKey);
@@ -124,7 +123,7 @@ class DefaultTable implements Table
} else {
$this->load();
if ($config->get('useCache')) {
if ($systemConfig->useCache()) {
$dataCache->store($this->cacheKey, $this->data);
}
}
@@ -219,7 +218,7 @@ class DefaultTable implements Table
$this->applyAdminMandatory($aclTable, $fieldTable);
}
foreach ($aclTable as $scope => $data) {
foreach (get_object_vars($aclTable) as $scope => $data) {
if (is_string($data) && isset($aclTable->$data)) {
$aclTable->$scope = $aclTable->$data;
}
+3 -2
View File
@@ -308,11 +308,12 @@ class AclManager
$checker = $this->getAccessChecker($scope);
/** @var non-falsy-string $methodName */
$methodName = 'checkEntity' . ucfirst($action);
$interface = $this->entityActionInterfaceMap[$action] ?? null;
if ($interface && $checker instanceof $interface) {
if ($interface && $checker instanceof $interface && method_exists($checker, $methodName)) {
return $checker->$methodName($user, $entity, $data);
}
@@ -322,7 +323,7 @@ class AclManager
/**
* Check 'read' access to a specific entity.
*
* @throws NotImplemented.
* @throws NotImplemented
*/
public function checkEntityRead(User $user, Entity $entity): bool
{
@@ -47,6 +47,9 @@ use Espo\Entities\User;
use Espo\Tools\Currency\Conversion\EntityConverterFactory;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class ConvertCurrency implements Action
{
public function __construct(
@@ -34,16 +34,19 @@ use Espo\Core\Acl\Table;
use Espo\Core\Action\Params;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Name\Field;
use Espo\Core\ORM\EntityManager;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Record\ActionHistory\Action;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\ObjectUtil;
use Espo\Entities\Note;
use Espo\ORM\Entity;
use Espo\Entities\EmailAddress;
use Espo\Entities\PhoneNumber;
use Espo\ORM\Type\RelationType;
use stdClass;
class Merger
@@ -94,12 +97,12 @@ class Merger
$entityDefs = $this->entityManager->getDefs()->getEntity($entityType);
$hasPhoneNumber =
$entityDefs->hasField('phoneNumber') &&
$entityDefs->getField('phoneNumber')->getType() === FieldType::PHONE;
$entityDefs->hasField(Field::PHONE_NUMBER) &&
$entityDefs->getField(Field::PHONE_NUMBER)->getType() === FieldType::PHONE;
$hasEmailAddress =
$entityDefs->hasField('emailAddress') &&
$entityDefs->getField('emailAddress')->getType() === FieldType::EMAIL;
$entityDefs->hasField(Field::EMAIL_ADDRESS) &&
$entityDefs->getField(Field::EMAIL_ADDRESS)->getType() === FieldType::EMAIL;
if ($hasPhoneNumber) {
$phoneNumberToRelateList = $this->fetchEntityPhoneNumberList($entity);
@@ -196,7 +199,6 @@ class Merger
/** @var iterable<PhoneNumber> $collection */
$collection = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'phoneNumbers')
->find();
@@ -216,7 +218,6 @@ class Merger
/** @var iterable<EmailAddress> $collection */
$collection = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'emailAddresses')
->find();
@@ -232,13 +233,17 @@ class Merger
$updateQuery = $this->entityManager
->getQueryBuilder()
->update()
->in('Note')
->in(Note::ENTITY_TYPE)
->set([
'parentId' => $targetEntity->getId(),
'parentType' => $targetEntity->getEntityType(),
])
->where([
'type' => ['Post', 'EmailSent', 'EmailReceived'],
'type' => [
Note::TYPE_POST,
Note::TYPE_EMAIL_SENT,
Note::TYPE_EMAIL_RECEIVED,
],
'parentId' => $sourceEntity->getId(),
'parentType' => $sourceEntity->getEntityType(),
])
@@ -251,16 +256,24 @@ class Merger
private function updateRelations(Entity $sourceEntity, Entity $targetEntity, string $link): void
{
$repository = $this->entityManager->getRDBRepository($targetEntity->getEntityType());
$entityType = $sourceEntity->getEntityType();
$collection = $repository
$columnAttributeMap = $this->getLinkColumnAttributeMap($entityType, $link);
$collection = $this->entityManager
->getRelation($sourceEntity, $link)
->find();
foreach ($collection as $relatedEntity) {
$repository
$map = null;
if ($columnAttributeMap) {
$map = array_map(fn ($attribute) => $relatedEntity->get($attribute), $columnAttributeMap);
}
$this->entityManager
->getRelation($targetEntity, $link)
->relate($relatedEntity);
->relate($relatedEntity, $map);
}
}
@@ -383,4 +396,35 @@ class Merger
}
}
}
/**
* @return ?array<string, string>
*/
private function getLinkColumnAttributeMap(string $entityType, string $link): ?array
{
$entityDefs = $this->entityManager
->getDefs()
->getEntity($entityType);
$columnAttributeMap = null;
$relationDefs = $entityDefs->tryGetRelation($link);
if (
$relationDefs &&
$relationDefs->getType() === RelationType::MANY_MANY &&
$relationDefs->hasForeignEntityType() &&
$relationDefs->hasForeignRelationName()
) {
$foreignRelationDefs = $this->entityManager
->getDefs()
->getEntity($relationDefs->getForeignEntityType())
->getRelation($relationDefs->getForeignRelationName());
/** ?@var array<string, string> $columnAttributeMap */
$columnAttributeMap = $foreignRelationDefs->getParam('columnAttributeMap');
}
return $columnAttributeMap;
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ namespace Espo\Core\Action;
use RuntimeException;
/**
* @immutable
* Immutable.
*/
class Params
{
+29 -4
View File
@@ -30,14 +30,15 @@
namespace Espo\Core\Api;
use Espo\Core\Api\Route\RouteParamsFetcher;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\Route as RouteUtil;
use Espo\Core\Utils\Log;
use Psr\Container\ContainerInterface;
use Slim\App as SlimApp;
use Slim\Exception\HttpBadRequestException;
use Slim\Factory\AppFactory as SlimAppFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Psr7Response;
use Psr\Http\Message\ServerRequestInterface as Psr7Request;
@@ -54,7 +55,7 @@ class Starter
private RouteParamsFetcher $routeParamsFetcher,
private MiddlewareProvider $middlewareProvider,
private Log $log,
private Config $config,
private SystemConfig $systemConfig,
?string $routeCacheFile = null
) {
$this->routeCacheFile = $routeCacheFile ?? $this->routeCacheFile;
@@ -64,7 +65,13 @@ class Starter
{
$slim = SlimAppFactory::create();
if ($this->config->get('useCache')) {
if (RouteUtil::isBadUri()) {
$this->processError($slim);
return;
}
if ($this->systemConfig->useCache()) {
$slim->getRouteCollector()->setCacheFile($this->routeCacheFile);
}
@@ -126,4 +133,22 @@ class Starter
$slimRoute->addMiddleware($middleware);
}
}
/**
* @param SlimApp<ContainerInterface|null> $slim
*/
private function processError(SlimApp $slim): void
{
$slim->add(function (Psr7Request $request): Psr7Response {
throw new HttpBadRequestException($request, 'Malformed request path.');
});
$slim->addErrorMiddleware(
displayErrorDetails: false,
logErrors: false,
logErrorDetails: false,
);
$slim->run();
}
}
+2
View File
@@ -76,6 +76,8 @@ class Util
public function obtainIpFromRequest(Request $request): ?string
{
// Do not add support of any more parameters here.
$param = $this->config->get('ipAddressServerParam') ?? 'REMOTE_ADDR';
return $request->getServerParam($param);
@@ -29,10 +29,15 @@
namespace Espo\Core\Application;
use Espo\Core\Application\Exceptions\RunnerException;
/**
* Runs an application.
*/
interface Runner
{
/**
* @throws RunnerException
*/
public function run(): void;
}
@@ -35,7 +35,7 @@ use SensitiveParameter;
/**
* An auth token data. Used for auth token creation.
*
* @immutable
* Immutable.
*/
class Data
{
@@ -159,17 +159,6 @@ class EspoManager implements Manager
{
$length = self::TOKEN_RANDOM_LENGTH;
if (function_exists('random_bytes')) {
/** @noinspection PhpUnhandledExceptionInspection */
return bin2hex(random_bytes($length));
}
if (function_exists('openssl_random_pseudo_bytes')) {
$randomValue = openssl_random_pseudo_bytes($length);
return bin2hex($randomValue);
}
throw new RuntimeException("Could not generate token.");
return bin2hex(random_bytes($length));
}
}
@@ -211,7 +211,7 @@ class Authentication
if (!$user->isAdmin() && $this->configDataProvider->isMaintenanceMode()) {
throw ServiceUnavailable::createWithBody(
"Application is in maintenance mod1e.",
"Application is in maintenance mode.",
Body::create()
->withMessage($this->language->translateLabel('maintenanceModeError', 'messages'))
);
@@ -29,8 +29,10 @@
namespace Espo\Core\Authentication;
use SensitiveParameter;
/**
* @immutable
* Immutable.
*/
class AuthenticationData
{
@@ -87,7 +89,7 @@ class AuthenticationData
return $obj;
}
public function withPassword(?string $password): self
public function withPassword(#[SensitiveParameter] ?string $password): self
{
$obj = clone $this;
$obj->password = $password;
@@ -34,7 +34,7 @@ use UnexpectedValueException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Rsa implements Key
{
@@ -36,7 +36,7 @@ use RuntimeException;
/**
* JWT token.
*
* @immutable
* Immutable.
*/
class Token
{
@@ -35,7 +35,7 @@ use JsonException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Header
{
@@ -35,7 +35,7 @@ use JsonException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Payload
{
@@ -345,11 +345,10 @@ class LdapLogin implements Login
private function createUser(array $userData, bool $isPortal = false): ?User
{
$this->log->info("LDAP: Creating new user.");
$this->log->debug("LDAP: user data: {userData}", ['userData' => print_r($userData, true)]);
$data = [];
$this->log->debug("LDAP: user data: {userData}", ['userData' => print_r($userData, true)]);
$ldapFields = $this->loadFields('ldap');
foreach ($ldapFields as $espo => $ldap) {
@@ -366,21 +365,20 @@ class LdapLogin implements Login
}
if ($isPortal) {
$userFields = $this->loadFields('portalUser');
$userAttributes = $this->loadFields('portalUser');
$userFields['type'] = 'portal';
$userAttributes['type'] = User::TYPE_PORTAL;
} else {
$userFields = $this->loadFields('user');
$userAttributes = $this->loadFields('user');
}
foreach ($userFields as $fieldName => $fieldValue) {
$data[$fieldName] = $fieldValue;
foreach ($userAttributes as $key => $value) {
$data[$key] = $value;
}
/** @var User $user */
$user = $this->entityManager->getNewEntity(User::ENTITY_TYPE);
$user = $this->entityManager->getRDBRepositoryByClass(User::class)->getNew();
$user->set($data);
$user->setMultiple($data);
$this->entityManager->saveEntity($user, [
// Prevent `user` service being loaded by hooks.
@@ -388,10 +386,7 @@ class LdapLogin implements Login
SaveOption::KEEP_NEW => true,
]);
$saverParams = SaverParams::create()
->withRawOptions([
'skipLinkMultipleHooks' => true,
]);
$saverParams = SaverParams::create()->withRawOptions(['skipLinkMultipleHooks' => true]);
$this->linkMultipleSaver->process($user, Field::TEAMS, $saverParams);
$this->linkMultipleSaver->process($user, 'portals', $saverParams);
@@ -402,31 +397,27 @@ class LdapLogin implements Login
$user->setAsNotNew();
$user->updateFetchedValues();
/** @var ?User */
return $this->entityManager->getEntityById(User::ENTITY_TYPE, $user->getId());
return $this->entityManager->getRDBRepositoryByClass(User::class)->getById($user->getId());
}
/**
* Find LDAP user DN by his username.
* Find LDAP user DN by their username.
*
* @throws LdapException
*/
private function findLdapUserDnByUsername(string $username): ?string
{
$ldapClient = $this->getLdapClient();
$options = $this->utils->getOptions();
$loginFilterString = '';
$filterString = !empty($options['userLoginFilter']) ?
$this->convertToFilterFormat($options['userLoginFilter']) : '';
if (!empty($options['userLoginFilter'])) {
$loginFilterString = $this->convertToFilterFormat($options['userLoginFilter']);
}
$objectClass = $options['userObjectClass'];
$attribute = $options['userNameAttribute'];
$usernameEscaped = $this->escapeUsernameFilter($username);
$searchString =
'(&(objectClass=' . $options['userObjectClass'] . ')' .
'(' . $options['userNameAttribute'] . '=' . $username . ')' .
$loginFilterString . ')';
$searchString = "(&(objectClass=$objectClass)($attribute=$usernameEscaped)$filterString)";
/** @var array<int, array{dn: string}> $result */
/** @noinspection PhpRedundantOptionalArgumentInspection */
@@ -435,12 +426,25 @@ class LdapLogin implements Login
$this->log->debug("LDAP: user search string: {string}.", ['string' => $searchString]);
foreach ($result as $item) {
return $item["dn"];
return $item['dn'];
}
return null;
}
private function escapeUsernameFilter(string $username): string
{
$map = [
'\\' => '\\5c',
'*' => '\\2a',
'(' => '\\28',
')' => '\\29',
"\x00" => '\\00',
];
return strtr($username, $map);
}
/**
* Check and convert filter item into LDAP format.
*/
@@ -30,6 +30,7 @@
namespace Espo\Core\Authentication\Login;
use Espo\Core\Authentication\AuthToken\AuthToken;
use SensitiveParameter;
class DataBuilder
{
@@ -44,7 +45,7 @@ class DataBuilder
return $this;
}
public function setPassword(?string $password): self
public function setPassword(#[SensitiveParameter] ?string $password): self
{
$this->password = $password;
@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Login;
/**
* @immutable
* Immutable.
*/
class MetadataParams
{
@@ -50,7 +50,7 @@ class Hmac implements Login
{
$authString = base64_decode($request->getHeader('X-Hmac-Authorization') ?? '');
list($apiKey, $hash) = explode(':', $authString, 2);
[$apiKey, $hash] = explode(':', $authString, 2);
if (!$apiKey) {
return Result::fail(FailReason::WRONG_CREDENTIALS);
@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Logout;
/**
* @immutable
* Immutable.
*/
class Params
{
@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Logout;
/**
* @immutable
* Immutable.
*/
class Result
{
@@ -104,6 +104,11 @@ class ConfigDataProvider
return $this->object->get('oidcTokenEndpoint');
}
public function getUserInfoEndpoint(): ?string
{
return $this->object->get('oidcUserInfoEndpoint');
}
public function getJwksEndpoint(): ?string
{
return $this->object->get('oidcJwksEndpoint');
@@ -33,7 +33,7 @@ use Espo\Core\Authentication\Jwt\Exceptions\UnsupportedKey;
use Espo\Core\Authentication\Jwt\Key;
use Espo\Core\Authentication\Jwt\KeyFactory;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Log;
@@ -48,10 +48,10 @@ class KeysProvider
public function __construct(
private DataCache $dataCache,
private Config $config,
private ConfigDataProvider $configDataProvider,
private KeyFactory $factory,
private Log $log
private Log $log,
private SystemConfig $systemConfig,
) {}
/**
@@ -147,7 +147,7 @@ class KeysProvider
*/
private function getRawFromCache(): ?array
{
if (!$this->config->get('useCache')) {
if (!$this->systemConfig->useCache()) {
return null;
}
@@ -189,7 +189,7 @@ class KeysProvider
*/
private function storeRawToCache(array $raw): void
{
if (!$this->config->get('useCache')) {
if (!$this->systemConfig->useCache()) {
return;
}
@@ -38,6 +38,7 @@ use Espo\Core\Authentication\Logins\Espo;
use Espo\Core\Authentication\Jwt\Exceptions\Invalid;
use Espo\Core\Authentication\Jwt\Exceptions\SignatureNotVerified;
use Espo\Core\Authentication\Jwt\Validator;
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
use Espo\Core\Authentication\Result;
use Espo\Core\Authentication\Result\FailReason;
use Espo\Core\Utils\Json;
@@ -45,6 +46,7 @@ use Espo\Core\Utils\Log;
use JsonException;
use LogicException;
use RuntimeException;
use SensitiveParameter;
use stdClass;
class Login implements LoginInterface
@@ -62,7 +64,8 @@ class Login implements LoginInterface
private Validator $validator,
private TokenValidator $tokenValidator,
private UserProvider $userProvider,
private ApplicationState $applicationState
private ApplicationState $applicationState,
private UserInfoDataProvider $userInfoDataProvider,
) {}
public function login(Data $data, Request $request): Result
@@ -99,7 +102,8 @@ class Login implements LoginInterface
throw new RuntimeException("No client secret.");
}
[$rawToken, $failResult] = $this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
[$rawToken, $failResult, $accessToken] =
$this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
if ($failResult) {
return $failResult;
@@ -144,7 +148,9 @@ class Login implements LoginInterface
return Result::fail(FailReason::DENIED);
}
$user = $this->userProvider->get($tokenPayload);
$userInfo = $this->getUserInfo($tokenPayload, $accessToken);
$user = $this->userProvider->get($userInfo);
if (!$user) {
return Result::fail(FailReason::USER_NOT_FOUND);
@@ -198,7 +204,7 @@ class Login implements LoginInterface
}
/**
* @return array{?string, ?Result}
* @return array{?string, ?Result, ?string}
*/
private function requestToken(
string $endpoint,
@@ -250,7 +256,7 @@ class Login implements LoginInterface
$this->log->warning(self::composeLogMessage('Token request error.', $status, $response));
return [null, Result::fail(FailReason::DENIED)];
return [null, Result::fail(FailReason::DENIED), null];
}
$parsedResponse = null;
@@ -266,6 +272,7 @@ class Login implements LoginInterface
}
$token = $parsedResponse->id_token ?? null;
$accessToken = $parsedResponse->access_token ?? null;
if (!$token || !is_string($token)) {
$this->log->error(self::composeLogMessage('Bad token response.', $status, $response));
@@ -273,7 +280,7 @@ class Login implements LoginInterface
throw new RuntimeException();
}
return [$token, null];
return [$token, null, $accessToken];
}
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
@@ -295,4 +302,21 @@ class Login implements LoginInterface
$this->tokenValidator->validateFields($token);
$this->tokenValidator->validateSignature($token);
}
private function getUserInfo(Token\Payload $payload, #[SensitiveParameter] ?string $accessToken): UserInfo
{
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
if (!$endpoint) {
return new UserInfo($payload, []);
}
if (!$accessToken) {
throw new RuntimeException("OIDC: No access token received.");
}
$data = $this->userInfoDataProvider->get($accessToken);
return new UserInfo($payload, $data);
}
}
@@ -0,0 +1,121 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\Oidc;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Log;
use JsonException;
use RuntimeException;
use SensitiveParameter;
class UserInfoDataProvider
{
private const REQUEST_TIMEOUT = 10;
public function __construct(
private ConfigDataProvider $configDataProvider,
private Log $log,
) {}
/**
* @return array<string, mixed>
*/
public function get(#[SensitiveParameter] string $accessToken): array
{
return $this->load($accessToken);
}
/**
* @return array<string, mixed>
*/
private function load(#[SensitiveParameter] string $accessToken): array
{
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
if (!$endpoint) {
throw new RuntimeException("No userinfo endpoint.");
}
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $endpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => self::REQUEST_TIMEOUT,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
'Accept: application/json',
],
]);
/** @var string|false $response */
$response = curl_exec($curl);
$error = curl_error($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($response === false) {
$response = '';
}
if ($error || is_int($status) && ($status >= 400 && $status < 500)) {
$this->log->error(self::composeLogMessage('UserInfo response error.', $status, $response));
throw new RuntimeException("OIDC: Userinfo request error.");
}
$parsedResponse = null;
try {
$parsedResponse = Json::decode($response, true);
} catch (JsonException) {}
if (!is_array($parsedResponse)) {
throw new RuntimeException("OIDC: Bad userinfo response.");
}
return $parsedResponse;
}
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
{
if ($status === null) {
return "OIDC: $text";
}
return "OIDC: $text; Status: $status; Response: $response";
}
}
@@ -29,10 +29,10 @@
namespace Espo\Core\Authentication\Oidc;
use Espo\Core\Authentication\Jwt\Token\Payload;
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
use Espo\Entities\User;
interface UserProvider
{
public function get(Payload $payload): ?User;
public function get(UserInfo $userInfo): ?User;
}
@@ -30,11 +30,11 @@
namespace Espo\Core\Authentication\Oidc\UserProvider;
use Espo\Core\ApplicationState;
use Espo\Core\Authentication\Jwt\Token\Payload;
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
use Espo\Core\Authentication\Oidc\UserProvider;
use Espo\Core\Utils\Log;
use Espo\Entities\User;
use RuntimeException;
class DefaultUserProvider implements UserProvider
@@ -44,30 +44,30 @@ class DefaultUserProvider implements UserProvider
private Sync $sync,
private UserRepository $userRepository,
private ApplicationState $applicationState,
private Log $log
private Log $log,
) {}
public function get(Payload $payload): ?User
public function get(UserInfo $userInfo): ?User
{
$user = $this->findUser($payload);
$user = $this->findUser($userInfo);
if ($user === false) {
return null;
}
if ($user) {
$this->syncUser($user, $payload);
$this->syncUser($user, $userInfo);
return $user;
}
return $this->tryToCreateUser($payload);
return $this->tryToCreateUser($userInfo);
}
/**
* @return User|false|null
*/
private function findUser(Payload $payload): User|bool|null
private function findUser(UserInfo $userInfo): User|bool|null
{
$usernameClaim = $this->configDataProvider->getUsernameClaim();
@@ -75,10 +75,10 @@ class DefaultUserProvider implements UserProvider
throw new RuntimeException("No username claim in config.");
}
$username = $payload->get($usernameClaim);
$username = $userInfo->get($usernameClaim);
if (!$username) {
throw new RuntimeException("No username claim `$usernameClaim` in token.");
throw new RuntimeException("No username claim `$usernameClaim` in token and userinfo.");
}
$username = $this->sync->normalizeUsername($username);
@@ -136,7 +136,7 @@ class DefaultUserProvider implements UserProvider
return $user;
}
private function tryToCreateUser(Payload $payload): ?User
private function tryToCreateUser(UserInfo $userInfo): ?User
{
if (!$this->configDataProvider->createUser()) {
return null;
@@ -148,16 +148,16 @@ class DefaultUserProvider implements UserProvider
throw new RuntimeException("Could not create a user. No OIDC username claim in config.");
}
$username = $payload->get($usernameClaim);
$username = $userInfo->get($usernameClaim);
if (!$username) {
throw new RuntimeException("Could not create a user. No username claim returned in token.");
throw new RuntimeException("Could not create a user. No username claim in token and userinfo.");
}
return $this->sync->createUser($payload);
return $this->sync->createUser($userInfo);
}
private function syncUser(User $user, Payload $payload): void
private function syncUser(User $user, UserInfo $userInfo): void
{
if (
!$this->configDataProvider->sync() &&
@@ -166,6 +166,6 @@ class DefaultUserProvider implements UserProvider
return;
}
$this->sync->syncUser($user, $payload);
$this->sync->syncUser($user, $userInfo);
}
}
@@ -31,7 +31,6 @@ namespace Espo\Core\Authentication\Oidc\UserProvider;
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
use Espo\Core\ApplicationState;
use Espo\Core\Authentication\Jwt\Token\Payload;
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Name\Field;
@@ -50,30 +49,31 @@ class Sync
private UserRepository $userRepository,
private PasswordHash $passwordHash,
private AclCacheClearer $aclCacheClearer,
private ApplicationState $applicationState
private ApplicationState $applicationState,
) {}
public function createUser(Payload $payload): User
public function createUser(UserInfo $userInfo): User
{
$username = $this->getUsernameFromToken($payload);
$username = $this->getUsernameFromToken($userInfo);
$this->usernameValidator->validate($username);
$user = $this->userRepository->getNew();
$user->set([
'type' => User::TYPE_REGULAR,
'userName' => $username,
$user->setType(User::TYPE_REGULAR);
$user->setUserName($username);
$user->setMultiple([
'password' => $this->passwordHash->hash(Util::generatePassword(10, 4, 2, true)),
]);
$user->set($this->getUserDataFromToken($payload));
$user->set($this->getUserTeamsDataFromToken($payload));
$user->set($this->getUserDataFromToken($userInfo));
$user->set($this->getUserTeamsDataFromToken($userInfo));
if ($this->applicationState->isPortal()) {
$portalId = $this->applicationState->getPortalId();
$user->set('type', User::TYPE_PORTAL);
$user->setType(User::TYPE_PORTAL);
$user->setPortals(LinkMultiple::create()->withAddedId($portalId));
}
@@ -82,7 +82,7 @@ class Sync
return $user;
}
public function syncUser(User $user, Payload $payload): void
public function syncUser(User $user, UserInfo $payload): void
{
$username = $this->getUsernameFromToken($payload);
@@ -116,19 +116,19 @@ class Sync
/**
* @return array<string, mixed>
*/
private function getUserDataFromToken(Payload $payload): array
private function getUserDataFromToken(UserInfo $userInfo): array
{
return [
'emailAddress' => $payload->get('email'),
'phoneNumber' => $payload->get('phone_number'),
'emailAddress' => $userInfo->get('email'),
'phoneNumber' => $userInfo->get('phone_number'),
'emailAddressData' => null,
'phoneNumberData' => null,
'firstName' => $payload->get('given_name'),
'lastName' => $payload->get('family_name'),
'middle_name' => $payload->get('middle_name'),
'firstName' => $userInfo->get('given_name'),
'lastName' => $userInfo->get('family_name'),
'middle_name' => $userInfo->get('middle_name'),
'gender' =>
in_array($payload->get('gender'), ['male', 'female']) ?
ucfirst($payload->get('gender') ?? '') :
in_array($userInfo->get('gender'), ['male', 'female']) ?
ucfirst($userInfo->get('gender') ?? '') :
null,
];
}
@@ -136,14 +136,14 @@ class Sync
/**
* @return array<string, mixed>
*/
private function getUserTeamsDataFromToken(Payload $payload): array
private function getUserTeamsDataFromToken(UserInfo $userInfo): array
{
return [
'teamsIds' => $this->getTeamIdList($payload),
'teamsIds' => $this->getTeamIdList($userInfo),
];
}
private function getUsernameFromToken(Payload $payload): string
private function getUsernameFromToken(UserInfo $userInfo): string
{
$usernameClaim = $this->configDataProvider->getUsernameClaim();
@@ -151,7 +151,7 @@ class Sync
throw new RuntimeException("No OIDC username claim in config.");
}
$username = $payload->get($usernameClaim);
$username = $userInfo->get($usernameClaim);
if (!$username) {
throw new RuntimeException("No username claim returned in token.");
@@ -167,7 +167,7 @@ class Sync
/**
* @return string[]
*/
private function getTeamIdList(Payload $payload): array
private function getTeamIdList(UserInfo $userInfo): array
{
$idList = $this->configDataProvider->getTeamIds() ?? [];
$columns = $this->configDataProvider->getTeamColumns() ?? (object) [];
@@ -176,7 +176,7 @@ class Sync
return [];
}
$groupList = $this->getGroups($payload);
$groupList = $this->getGroups($userInfo);
$resultIdList = [];
@@ -194,7 +194,7 @@ class Sync
/**
* @return string[]
*/
private function getGroups(Payload $payload): array
private function getGroups(UserInfo $userInfo): array
{
$groupClaim = $this->configDataProvider->getGroupClaim();
@@ -202,7 +202,7 @@ class Sync
return [];
}
$value = $payload->get($groupClaim);
$value = $userInfo->get($groupClaim);
if (!$value) {
return [];
@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\Oidc\UserProvider;
use Espo\Core\Authentication\Jwt\Token\Payload;
class UserInfo
{
/**
* @internal
* @param array<string, mixed> $data
*/
public function __construct(
private Payload $payload,
private array $data,
) {}
public function get(string $name): mixed
{
return $this->payload->get($name) ?? $this->data[$name] ?? null;
}
}
@@ -37,7 +37,7 @@ use stdClass;
/**
* An authentication result.
*
* @immutable
* Immutable.
*/
class Result
{
@@ -32,7 +32,7 @@ namespace Espo\Core\Authentication\Result;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Data
{
@@ -75,7 +75,7 @@ class ContextualBinder
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<T> $serviceName A service name.
* @param string $serviceName A service name.
*/
public function bindService(string|NamedClassKey $key, string $serviceName): self
{
@@ -73,6 +73,7 @@ class EspoBindingLoader implements BindingLoader
private function loadCustom(Binder $binder): void
{
/** @var class-string<BindingProcessor>|string $className */
$className = 'Espo\\Custom\\Binding';
if (!class_exists($className)) {
@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
/**
* Command parameters.
*
* @immutable
* Immutable.
*/
class Params
{
@@ -248,11 +248,10 @@ class Extension implements Command
private function printList(IO $io): void
{
$collection = $this->entityManager
->getRDBRepository(ExtensionEntity::ENTITY_TYPE)
->getRDBRepositoryByClass(ExtensionEntity::class)
->find();
/** @noinspection PhpParamsInspection */
$count = is_countable($collection) ? count($collection) : iterator_count($collection);
$count = count($collection);
/** @noinspection PhpIfWithCommonPartsInspection */
if ($count === 0) {
@@ -268,9 +267,9 @@ class Extension implements Command
$io->writeLine("");
foreach ($collection as $extension) {
$isInstalled = $extension->get('isInstalled');
$isInstalled = $extension->isInstalled();
$io->writeLine(' Name: ' . $extension->get(Field::NAME));
$io->writeLine(' Name: ' . $extension->getName());
$io->writeLine(' ID: ' . $extension->getId());
$io->writeLine(' Version: ' . $extension->getVersion());
$io->writeLine(' Installed: ' . ($isInstalled ? 'yes' : 'no'));
@@ -151,6 +151,8 @@ class Upgrade implements Command
if (!$packageFile) {
fwrite(STDOUT, "Error: Unable to download upgrade package.\n");
$io->setExitStatus(1);
return;
}
}
@@ -177,6 +179,8 @@ class Upgrade implements Command
fwrite(STDOUT, $errorMessage . "\n");
$io->setExitStatus(1);
return;
}
@@ -491,13 +495,19 @@ class Upgrade implements Command
return null;
}
/** @var string */
return realpath($localFilePath);
$path = realpath($localFilePath);
assert($path !== false);
return $path;
}
private function isShellEnabled(): bool
{
if (!function_exists('exec') || !is_callable('shell_exec')) {
if (
!function_exists('exec') ||
!is_callable('shell_exec') /** @phpstan-ignore-line */
) {
return false;
}
@@ -512,7 +522,7 @@ class Upgrade implements Command
private function getCurrentVersion(): ?string
{
$configData = include "data/config.php";
$configData = include "data/config.php"; /** @phpstan-ignore-line */
if (!$configData) {
return null;
@@ -36,16 +36,12 @@ use Espo\Core\Utils\Config;
class Version implements Command
{
public function __construct(private Config $config)
public function __construct(private Config\SystemConfig $config)
{}
public function run(Params $params, IO $io): void
{
$version = $this->config->get('version');
if (is_null($version)) {
return;
}
$version = $this->config->getVersion();
$io->writeLine($version);
}
+8 -2
View File
@@ -187,13 +187,19 @@ class Container implements ContainerInterface
}
if ($id === self::ID_CONTAINER) {
$this->classCache[$id] = new ReflectionClass(Container::class);
/** @var ReflectionClass<object> $object */
$object = new ReflectionClass(Container::class);
$this->classCache[$id] = $object;
return;
}
if ($id === self::ID_INJECTABLE_FACTORY) {
$this->classCache[$id] = new ReflectionClass(InjectableFactory::class);
/** @var ReflectionClass<object> $object */
$object = new ReflectionClass(InjectableFactory::class);
$this->classCache[$id] = $object;
return;
}
@@ -168,12 +168,14 @@ class ContainerBuilder
)
);
/** @var FileManager $fileManager */
$fileManager = $this->services['fileManager'] ?? (
new $this->fileManagerClassName(
$config->get('defaultPermissions')
)
);
/** @var DataCache $dataCache */
$dataCache = $this->services['dataCache'] ?? (
new $this->dataCacheClassName($fileManager)
);
@@ -185,10 +187,13 @@ class ContainerBuilder
new $this->moduleClassName($fileManager, $dataCache, $useCache)
);
$systemConfig = new Config\SystemConfig($config);
$this->services['config'] = $config;
$this->services['fileManager'] = $fileManager;
$this->services['dataCache'] = $dataCache;
$this->services['module'] = $module;
$this->services['systemConfig'] = $systemConfig;
$bindingLoader = $this->bindingLoader ?? (
new EspoBindingLoader($module)
@@ -39,8 +39,8 @@ class ContainerConfiguration implements Configuration
{
/**
* Log must be loaded before anything.
* @phpstan-ignore-next-line
* @noinspection PhpPropertyOnlyWrittenInspection
* @phpstan-ignore-next-line
*/
private Log $log;
+3 -4
View File
@@ -63,12 +63,12 @@ abstract class Base
private $container;
/**
* @var User;
* @var User
*/
protected $user;
/**
* @var Acl;
* @var Acl
*/
protected $acl;
@@ -161,12 +161,11 @@ abstract class Base
}
/**
* @return void;
* @return void
* @deprecated
*/
protected function checkControllerAccess()
{
return;
}
/**
+2 -6
View File
@@ -31,7 +31,6 @@ namespace Espo\Core\Controllers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\NotFoundSilent;
@@ -65,12 +64,9 @@ class Record extends RecordBase
$searchParams = $this->fetchSearchParamsFromRequest($request);
$recordCollection = $this->getRecordService()->findLinked($id, $link, $searchParams);
$result = $this->getRecordService()->findLinked($id, $link, $searchParams);
return (object) [
'total' => $recordCollection->getTotal(),
'list' => $recordCollection->getValueMapList(),
];
return $result->toApiOutput();
}
/**
@@ -40,6 +40,7 @@ use Espo\Core\Record\ServiceContainer as RecordServiceContainer;
use Espo\Core\Record\SearchParamsFetcher;
use Espo\Core\Record\CreateParamsFetcher;
use Espo\Core\Record\ReadParamsFetcher;
use Espo\Core\Record\UpdateContext;
use Espo\Core\Record\UpdateParamsFetcher;
use Espo\Core\Record\DeleteParamsFetcher;
use Espo\Core\Record\FindParamsFetcher;
@@ -222,8 +223,16 @@ class RecordBase extends Base implements
$params = $this->updateParamsFetcher->fetch($request);
$context = new UpdateContext();
$params = $params->withContext($context);
$entity = $this->getRecordService()->update($id, $data, $params);
if ($context->linkUpdated) {
$response->setHeader('X-Record-Link-Updated', 'true');
}
return $entity->getValueMap();
}
@@ -246,10 +255,7 @@ class RecordBase extends Base implements
$recordCollection = $this->getRecordService()->find($searchParams, $findParams);
return (object) [
'total' => $recordCollection->getTotal(),
'list' => $recordCollection->getValueMapList(),
];
return $recordCollection->toApiOutput();
}
/**
@@ -63,15 +63,12 @@ class RecordTree extends Record
return (object) $this->actionListTree($request->getRouteParams(), $request->getParsedBody(), $request);
}
$where = $request->getQueryParams()['where'] ?? null;
$selectParams = $this->fetchSearchParamsFromRequest($request);
$parentId = $request->getQueryParam('parentId');
$maxDepth = $request->getQueryParam('maxDepth');
$onlyNotEmpty = (bool) $request->getQueryParam('onlyNotEmpty');
if ($where !== null && !is_array($where)) {
throw new BadRequest();
}
if ($maxDepth !== null) {
$maxDepth = (int) $maxDepth;
}
@@ -79,7 +76,7 @@ class RecordTree extends Record
$collection = $this->getRecordTreeService()->getTree(
$parentId,
[
'where' => $where,
'where' => $selectParams->getWhere(),
'onlyNotEmpty' => $onlyNotEmpty,
],
$maxDepth
@@ -35,6 +35,11 @@ class CalculatorUtil
{
private const SCALE = 14;
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*/
public static function add(string $arg1, string $arg2): string
{
if (!function_exists('bcadd')) {
@@ -50,6 +55,11 @@ class CalculatorUtil
);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*/
public static function subtract(string $arg1, string $arg2): string
{
if (!function_exists('bcsub')) {
@@ -65,6 +75,11 @@ class CalculatorUtil
);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*/
public static function multiply(string $arg1, string $arg2): string
{
if (!function_exists('bcmul')) {
@@ -80,6 +95,11 @@ class CalculatorUtil
);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*/
public static function divide(string $arg1, string $arg2): string
{
if (!function_exists('bcdiv')) {
@@ -88,20 +108,23 @@ class CalculatorUtil
);
}
/** @var ?string $result */
$result = bcdiv(
$arg1,
$arg2,
self::SCALE
);
if ($result === null) {
if ($result === null) { /** @phpstan-ignore-line */
throw new DivisionByZeroError();
}
return $result;
}
/**
* @param numeric-string $arg
* @return numeric-string
*/
public static function round(string $arg, int $precision = 0): string
{
if (!function_exists('bcadd')) {
@@ -114,6 +137,8 @@ class CalculatorUtil
$addition = '-' . $addition;
}
assert(is_numeric($addition));
return bcadd(
$arg,
$addition,
@@ -121,6 +146,10 @@ class CalculatorUtil
);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
*/
public static function compare(string $arg1, string $arg2): int
{
if (!function_exists('bccomp')) {
@@ -100,6 +100,10 @@ class Converter
return $this->convert($value, $targetCurrencyCode);
}
/**
* @param numeric-string $amount
* @return numeric-string
*/
private function convertAmount(string $amount, float $rate, float $targetRate): string
{
return CalculatorUtil::divide(
+3
View File
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\Crypt;
/**
* @phpstan-ignore-next-line
*/
trait CryptSetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\DataManager;
/**
* @phpstan-ignore-next-line
*/
trait DataManagerSetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Mail\EmailSender as EmailSender;
/**
* @phpstan-ignore-next-line
*/
trait EmailSenderSetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Htmlizer\HtmlizerFactory as HtmlizerFactory;
/**
* @phpstan-ignore-next-line
*/
trait HtmlizerFactorySetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\Language;
/**
* @phpstan-ignore-next-line
*/
trait LanguageSetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Entities\Preferences;
/**
* @phpstan-ignore-next-line
*/
trait PreferencesSetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Select\SelectManagerFactory;
/**
* @phpstan-ignore-next-line
*/
trait SelectManagerFactorySetter
{
/**
@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\TemplateFileManager;
/**
* @phpstan-ignore-next-line
*/
trait TemplateFileManagerSetter
{
/**

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