Compare commits

...

593 Commits
9.0.3 ... 9.1.3

Author SHA1 Message Date
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
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
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
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
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
1189 changed files with 27715 additions and 11606 deletions

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'

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'

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
.idea/jsonSchemas.xml generated
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
.vscode/settings.json vendored
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"

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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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()])

View File

@@ -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();

View File

@@ -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}',
],
],

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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(),
])

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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) {

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());
}
}
}

View File

@@ -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));
}
}

View File

@@ -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 . '>');
}

View File

@@ -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);
}
}

View File

@@ -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 . '>');
}

View File

@@ -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(),
],
]);
}

View File

@@ -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([

View File

@@ -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,
]
);

View File

@@ -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);

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;

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 */

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();
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

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();
}
/**

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
{

View File

@@ -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();
}
}

View File

@@ -31,6 +31,11 @@ namespace Espo\Core\Acl;
use Espo\Entities\User;
/**
* Bindings:
* - `$entityType` as of v9.1.0.
* - `Espo\Core\AclManager`
*/
interface AccessChecker
{
/**

View File

@@ -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);
}
}

View File

@@ -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 */

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);
}
}

View File

@@ -29,4 +29,9 @@
namespace Espo\Core\Acl;
/**
* Bindings:
* - `$entityType` as of v9.1.0.
* - `Espo\Core\AclManager`
*/
interface OwnershipChecker {}

View File

@@ -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);
}
}

View File

@@ -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;
}

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
{

View File

@@ -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(

View File

@@ -32,7 +32,7 @@ namespace Espo\Core\Action;
use RuntimeException;
/**
* @immutable
* Immutable.
*/
class Params
{

View File

@@ -30,7 +30,7 @@
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;
@@ -54,7 +54,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 +64,7 @@ class Starter
{
$slim = SlimAppFactory::create();
if ($this->config->get('useCache')) {
if ($this->systemConfig->useCache()) {
$slim->getRouteCollector()->setCacheFile($this->routeCacheFile);
}

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);

View File

@@ -35,7 +35,7 @@ use SensitiveParameter;
/**
* An auth token data. Used for auth token creation.
*
* @immutable
* Immutable.
*/
class Data
{

View File

@@ -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));
}
}

View File

@@ -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'))
);

View File

@@ -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;

View File

@@ -34,7 +34,7 @@ use UnexpectedValueException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Rsa implements Key
{

View File

@@ -36,7 +36,7 @@ use RuntimeException;
/**
* JWT token.
*
* @immutable
* Immutable.
*/
class Token
{

View File

@@ -35,7 +35,7 @@ use JsonException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Header
{

View File

@@ -35,7 +35,7 @@ use JsonException;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Payload
{

View File

@@ -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;

View File

@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Login;
/**
* @immutable
* Immutable.
*/
class MetadataParams
{

View File

@@ -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);

View File

@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Logout;
/**
* @immutable
* Immutable.
*/
class Params
{

View File

@@ -30,7 +30,7 @@
namespace Espo\Core\Authentication\Logout;
/**
* @immutable
* Immutable.
*/
class Result
{

View File

@@ -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');

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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 [];

View File

@@ -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;
}
}

View File

@@ -37,7 +37,7 @@ use stdClass;
/**
* An authentication result.
*
* @immutable
* Immutable.
*/
class Result
{

View File

@@ -32,7 +32,7 @@ namespace Espo\Core\Authentication\Result;
use stdClass;
/**
* @immutable
* Immutable.
*/
class Data
{

View File

@@ -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
{

View File

@@ -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)) {

View File

@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
/**
* Command parameters.
*
* @immutable
* Immutable.
*/
class Params
{

View File

@@ -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'));

View File

@@ -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;

View File

@@ -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);
}

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;
}

View File

@@ -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)

View File

@@ -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;

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;
}
/**

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();
}
/**

View File

@@ -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();
}
/**

View File

@@ -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

View File

@@ -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')) {

View File

@@ -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(

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\Crypt;
/**
* @phpstan-ignore-next-line
*/
trait CryptSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\DataManager;
/**
* @phpstan-ignore-next-line
*/
trait DataManagerSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Mail\EmailSender as EmailSender;
/**
* @phpstan-ignore-next-line
*/
trait EmailSenderSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Htmlizer\HtmlizerFactory as HtmlizerFactory;
/**
* @phpstan-ignore-next-line
*/
trait HtmlizerFactorySetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\Language;
/**
* @phpstan-ignore-next-line
*/
trait LanguageSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Entities\Preferences;
/**
* @phpstan-ignore-next-line
*/
trait PreferencesSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Select\SelectManagerFactory;
/**
* @phpstan-ignore-next-line
*/
trait SelectManagerFactorySetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\Utils\TemplateFileManager;
/**
* @phpstan-ignore-next-line
*/
trait TemplateFileManagerSetter
{
/**

View File

@@ -31,6 +31,9 @@ namespace Espo\Core\Di;
use Espo\Core\WebSocket\Submission;
/**
* @phpstan-ignore-next-line
*/
trait WebSocketSubmissionSetter
{
/**

View File

@@ -31,11 +31,12 @@ namespace Espo\Core\Di;
use Espo\Core\Webhook\Manager;
/**
* @phpstan-ignore-next-line
*/
trait WebhookManagerSetter
{
/**
* @var Manager
*/
/** @var Manager */
protected $webhookManager;
public function setWebhookManager(Manager $webhookManager): void

View File

@@ -71,6 +71,10 @@ class Person extends Entity
{
$this->setInContainer('lastName', $value);
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
return;
}
$name = $this->helper->formatPersonName($this, 'name');
$this->setInContainer(Field::NAME, $name);
@@ -84,6 +88,10 @@ class Person extends Entity
{
$this->setInContainer('firstName', $value);
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
return;
}
$name = $this->helper->formatPersonName($this, 'name');
$this->setInContainer(Field::NAME, $name);
@@ -97,6 +105,10 @@ class Person extends Entity
{
$this->setInContainer('middleName', $value);
if (!$this->helper->hasAllPersonNameAttributes($this, 'name')) {
return;
}
$name = $this->helper->formatPersonName($this, 'name');
$this->setInContainer(Field::NAME, $name);
@@ -150,27 +162,27 @@ class Person extends Entity
return $this->get('middleName');
}
public function setFirstName(?string $firstName): self
public function setFirstName(?string $firstName): static
{
return $this->set('firstName', $firstName);
}
public function setLastName(?string $lastName): self
public function setLastName(?string $lastName): static
{
return $this->set('lastName', $lastName);
}
public function setMiddleName(?string $middleName): self
public function setMiddleName(?string $middleName): static
{
return $this->set('middleName', $middleName);
}
public function setEmailAddressGroup(EmailAddressGroup $group): self
public function setEmailAddressGroup(EmailAddressGroup $group): static
{
return $this->setValueObject('emailAddress', $group);
}
public function setPhoneNumberGroup(PhoneNumberGroup $group): self
public function setPhoneNumberGroup(PhoneNumberGroup $group): static
{
return $this->setValueObject('phoneNumber', $group);
}
@@ -181,7 +193,7 @@ class Person extends Entity
return $this->getValueObject('address');
}
public function setAddress(Address $address): self
public function setAddress(Address $address): static
{
return $this->setValueObject('address', $address);
}

View File

@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoint\Traits;
/**
* @deprecated
* @todo Remove in v10.0.
* @phpstan-ignore-next-line
*/
trait NotStrictAuth
{

View File

@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoints;
/**
* @deprecated Use `Espo\Core\EntryPoint\Traits\NoAuth` instead.
* @todo Remove in v10.0.
* @phpstan-ignore-next-line
*/
trait NoAuth
{

View File

@@ -31,6 +31,8 @@ namespace Espo\Core\EntryPoints;
/**
* @deprecated
* @todo Remove in v10.0.
* @phpstan-ignore-next-line
*/
trait NotStrictAuth
{

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