Compare commits

...

801 Commits
7.5.3 ... 8.0.5

Author SHA1 Message Date
Yuri Kuznetsov
9a013b7ea7 8.0.5 2023-11-08 10:04:43 +02:00
Yuri Kuznetsov
124214aee3 curl protocols option 2023-11-07 10:01:43 +02:00
Yuri Kuznetsov
3bd6b3bd46 webhook allow http and https only 2023-11-06 16:03:49 +02:00
Yuri Kuznetsov
c536cee637 image upload url check 2023-11-06 16:02:13 +02:00
Yuri Kuznetsov
b1d2795347 not copying status field on duplicate 2023-11-03 11:38:46 +02:00
Yuri Kuznetsov
0f9bce70cf mass update use select 2023-11-01 16:56:05 +02:00
Yuri Kuznetsov
6f1f72127f cs 2023-11-01 16:55:55 +02:00
Yuri Kuznetsov
986edeb79e cs 2023-11-01 16:49:13 +02:00
Yuri Kuznetsov
b3391df6e8 mass update targetLists 2023-11-01 16:43:05 +02:00
Yuri Kuznetsov
d8a114c90a popover improvements 2023-10-30 15:48:18 +02:00
Yuri Kuznetsov
3fa9f4bf91 8.0.4 2023-10-27 10:22:00 +03:00
Yuri Kuznetsov
6c4a139e66 KB portal 2023-10-26 21:29:49 +03:00
Yuri Kuznetsov
d3a1db25c2 comment 2023-10-26 16:57:22 +03:00
Yuri Kuznetsov
0fcd3cd780 msg fix 2023-10-26 16:48:54 +03:00
Yuri Kuznetsov
61cb573eec cs 2023-10-26 14:26:16 +03:00
Yuri Kuznetsov
5dd84bf9a1 SMTP translated message 2023-10-26 14:24:55 +03:00
Yuri Kuznetsov
5dad7c6bba cs 2023-10-26 14:01:06 +03:00
Yuri Kuznetsov
3007d50299 cs 2023-10-26 14:00:05 +03:00
Yuri Kuznetsov
11c0ac6987 noopener 2023-10-26 13:52:33 +03:00
Yuri Kuznetsov
2bb62883a4 cs 2023-10-26 13:33:03 +03:00
Yuri Kuznetsov
5796d2fb85 cs 2023-10-26 13:28:05 +03:00
Yuri Kuznetsov
dae318c9bb cs 2023-10-26 13:26:07 +03:00
Yuri Kuznetsov
342764e9ff related records modal fetch when rendered 2023-10-26 13:19:43 +03:00
Yuri Kuznetsov
27b9e28e20 cs 2023-10-26 12:35:16 +03:00
Yuri Kuznetsov
f8d5dd7fa6 modal select related sync mass remove 2023-10-26 12:06:49 +03:00
Yuri Kuznetsov
7408f97fbd cs 2023-10-26 11:56:03 +03:00
Yuri Kuznetsov
d1a9cf840f inspection let/const 2023-10-26 11:29:35 +03:00
Yuri Kuznetsov
d593927b14 cs 2023-10-26 11:29:25 +03:00
Yuri Kuznetsov
f62ebb8fe8 ref 2023-10-26 11:22:51 +03:00
Yuri Kuznetsov
ede798dbe5 schema additions 2023-10-26 11:15:54 +03:00
Yuri Kuznetsov
c3095b6a32 schema dynamic logic options 2023-10-26 11:06:09 +03:00
Yuri Kuznetsov
9eb5bfc9fe css fix 2023-10-26 10:59:11 +03:00
Yuri Kuznetsov
bf2c529884 labels 2023-10-26 10:48:49 +03:00
Yuri Kuznetsov
7ce8150541 fix link fields advanced filter 2023-10-25 20:38:20 +03:00
Yuri Kuznetsov
44de149923 link, link-multiple select method 2023-10-25 14:04:14 +03:00
Yuri Kuznetsov
8ecdc7b9d9 first panel filter 2023-10-25 12:41:31 +03:00
Yuri Kuznetsov
95c64359d2 cons 2023-10-25 12:06:46 +03:00
Yuri Kuznetsov
44203e2178 one primary filter 2023-10-25 12:03:22 +03:00
Yuri Kuznetsov
d3d940d9c9 fix 2023-10-25 11:03:29 +03:00
Yuri Kuznetsov
9d3efae141 fix list related modal unlink disabled 2023-10-24 20:20:30 +03:00
Yuri Kuznetsov
e6eb3e7099 type fix 2023-10-24 10:37:15 +03:00
DrWarpMan
3ab2b37471 fix validatorClassNameMap (#2881) 2023-10-24 10:29:04 +03:00
Yuri Kuznetsov
33e7f4e3ef json retrive support empty path 2023-10-23 20:59:59 +03:00
Yuri Kuznetsov
6463eaa6f6 less placement.less 2023-10-23 14:22:25 +03:00
Yuri Kuznetsov
a066c0a187 style fix 2023-10-23 14:09:54 +03:00
Yuri Kuznetsov
176f65713f list-group css fix 2023-10-23 13:57:25 +03:00
Yuri Kuznetsov
493afdf3d4 tableClassName 2023-10-19 17:22:30 +03:00
Yuri Kuznetsov
c85b6a37a6 cs 2023-10-19 17:17:03 +03:00
Yuri Kuznetsov
81971d0eda doc fix 2023-10-19 17:02:38 +03:00
Yuri Kuznetsov
8a6cc9ea35 before upgrade version check 2023-10-19 16:55:30 +03:00
Yuri Kuznetsov
4fd125ddfc base url support index file 2023-10-19 16:48:50 +03:00
Yuri Kuznetsov
1298a8382d export: use additional applier 2023-10-19 11:17:35 +03:00
Yuri Kuznetsov
bb9c352f55 export: do not list not exportable fields 2023-10-19 10:40:23 +03:00
Yuri Kuznetsov
045533d080 export: do not load link-multiple if no field 2023-10-19 10:34:00 +03:00
Yuri Kuznetsov
6a5ab5f738 command listed 2023-10-17 15:22:28 +03:00
Yuri Kuznetsov
c1b0e4fd17 throw docs 2023-10-17 13:12:59 +03:00
Yuri Kuznetsov
e71928ffa7 css fix 2023-10-17 13:12:25 +03:00
Yuri Kuznetsov
de3f5de029 fix translation 2023-10-17 13:08:00 +03:00
Yuri Kuznetsov
57ceee4d4c export: do not sanitize numeric values 2023-10-17 13:04:38 +03:00
Yuri Kuznetsov
9a5cd7609b import ksort row 2023-10-17 12:45:14 +03:00
Yuri Kuznetsov
1382dca3e3 cs 2023-10-17 11:46:14 +03:00
Yuri Kuznetsov
bc7a3e8839 cs, browser compatibility fix 2023-10-17 11:38:17 +03:00
Yuri Kuznetsov
c5d6f4c63f entity manager disabled fix 2023-10-16 17:11:03 +03:00
Yuri Kuznetsov
6776f813af schema 2023-10-16 16:48:53 +03:00
Yuri Kuznetsov
6d18f2485d record helper as option 2023-10-13 16:19:50 +03:00
Yuri Kuznetsov
73753b364e unchain main view 2023-10-13 10:21:06 +03:00
Yuri Kuznetsov
68d6a14d04 orm: wrap and/or expr into braces 2023-10-11 10:43:39 +03:00
Yuri Kuznetsov
c155d37790 comment 2023-10-10 17:04:25 +03:00
Yuri Kuznetsov
6a6446d0b7 whenReady, field re-render with prepare fix 2023-10-10 16:34:55 +03:00
Yuri Kuznetsov
b0b4fe5570 set field read only race condition fix 2023-10-10 15:06:12 +03:00
Yuri Kuznetsov
6de8c3d1ca calendar dashlet size update 2023-10-10 12:01:25 +03:00
Yuri Kuznetsov
319f3f645a calaendar css fix 2023-10-10 11:52:23 +03:00
Yuri Kuznetsov
bedfbb0e6b calendar slot duration 2023-10-10 11:15:16 +03:00
Yuri Kuznetsov
148ffce6b7 re-reject on save error 2023-10-09 13:13:31 +03:00
Yuri Kuznetsov
85cb969780 fix schema docs 2023-10-09 11:51:56 +03:00
Yuri Kuznetsov
c1a75ad051 schema 2023-10-09 11:46:07 +03:00
Yuri Kuznetsov
7de49e9812 8.0.3 2023-10-09 10:00:50 +03:00
Yuri Kuznetsov
13c5d65f50 fix formula add attributes 2023-10-06 10:53:58 +03:00
Yuri Kuznetsov
34ce9d68bf sk lang 2023-10-06 10:34:20 +03:00
Yuri Kuznetsov
58dbadb869 fr lang 2023-10-06 10:33:19 +03:00
Yuri Kuznetsov
0a9d901874 css fix 2023-10-04 14:56:20 +03:00
Yuri Kuznetsov
9ebad9aad8 btn icon class 2023-10-04 14:38:06 +03:00
Yuri Kuznetsov
e44b921143 kb article acl level own 2023-10-04 11:30:57 +03:00
Yuri Kuznetsov
6cb44e65a6 calendar week range title fix 2023-10-04 10:10:31 +03:00
Yuri Kuznetsov
5398331956 style fix 2023-10-03 14:20:30 +03:00
Yuri Kuznetsov
de88ff0b6a merge 2023-10-03 11:33:10 +03:00
Yuri Kuznetsov
1bbc92e460 fix link field 2023-10-03 11:31:03 +03:00
Yuri Kuznetsov
e3ef9391ce fix link field 2023-10-03 11:28:58 +03:00
Yuri Kuznetsov
b317f99196 em not customizable guard 2023-09-30 15:18:52 +03:00
Yuri Kuznetsov
512c45c9b5 jsdoc fix 2023-09-30 14:53:33 +03:00
Yuri Kuznetsov
5f4ae01c85 layout manager fix 2023-09-30 14:50:19 +03:00
Yuri Kuznetsov
6c2bab44f6 hide layout foreign 2023-09-29 21:05:07 +03:00
Yuri Kuznetsov
b6f8a8bf9e fix 2023-09-29 21:00:25 +03:00
Yuri Kuznetsov
ed3030606f fix 2023-09-29 17:31:39 +03:00
Yuri Kuznetsov
274e46fa35 schema fix 2023-09-29 17:20:18 +03:00
Yuri Kuznetsov
e95620c131 link: createButton prop 2023-09-29 17:14:38 +03:00
Yuri Kuznetsov
31cb11f0d1 fix autotomplete url where 2023-09-29 15:28:07 +03:00
Yuri Kuznetsov
fae905efb0 email mass update layout 2023-09-29 10:00:03 +03:00
Yuri Kuznetsov
fcc1bada71 fix schema 2023-09-29 09:55:35 +03:00
Yuri Kuznetsov
8a140b2303 calendar span to another day in agenda view when duration <24h 2023-09-28 09:58:45 +03:00
Yuri Kuznetsov
9551590f92 fix all-day after drop 2023-09-28 09:54:28 +03:00
Yuri Kuznetsov
0d8278aa0d cs 2023-09-28 09:31:59 +03:00
Yuri Kuznetsov
c23a4e2085 calendar fix all-day issue 2023-09-28 09:26:54 +03:00
Yuri Kuznetsov
fe77f2c14c calendar fix update event rendering 2023-09-28 09:01:09 +03:00
Yuri Kuznetsov
5e06ad19ee cs 2023-09-28 08:18:53 +03:00
Yuri Kuznetsov
8bce12aca3 fix calendar initial date 2023-09-28 07:54:42 +03:00
Yuri Kuznetsov
d928ba7c3b calendar color fix 2023-09-26 15:08:59 +03:00
Yuri Kuznetsov
25b0077adc fix ldap warning 2023-09-26 11:31:53 +03:00
Yuri Kuznetsov
ee0bcb7685 fix upgrade script 2023-09-26 09:28:57 +03:00
Yuri Kuznetsov
642141f574 select list layout 2023-09-25 14:35:27 +03:00
Yuri Kuznetsov
90cdfb5e7e layout fix 2023-09-25 14:34:28 +03:00
Yuri Kuznetsov
9967bc26d9 link link multiple autocomplete apply all filters, fix empty autocomplete 2023-09-22 15:47:08 +03:00
Yuri Kuznetsov
fef2d74ec2 autocomplete on empty 2023-09-22 14:46:14 +03:00
Yuri Kuznetsov
9bbd262d05 fix param panelDefs.selectPrimaryFilterName usage 2023-09-22 14:27:51 +03:00
Yuri Kuznetsov
555d8a1a24 navbar fix 2023-09-22 12:21:05 +03:00
Yuri Kuznetsov
f7f549fb05 cs 2023-09-22 11:51:25 +03:00
Aurelio
d69c631a32 German Translation fixes and improvements (#2853)
* German translation of cancellation templates
2023-09-21 16:36:34 +03:00
Yuri Kuznetsov
5e3a4c12d7 style fix 2023-09-21 14:24:17 +03:00
Yuri Kuznetsov
f75d0562ba uix fix 2023-09-21 14:19:47 +03:00
Yuri Kuznetsov
ab745b3b03 de_DE update 2023-09-21 14:04:44 +03:00
Yuri Kuznetsov
f745d870f7 dont override bool filter list 2023-09-21 13:15:42 +03:00
Yuri Kuznetsov
0ebae7734a format 2023-09-21 13:09:48 +03:00
Yuri Kuznetsov
b1b61963fc login headers fix 2023-09-21 11:20:10 +03:00
Yuri Kuznetsov
5282f01cda link multiple default in orm metadata 2023-09-21 10:28:37 +03:00
Yuri Kuznetsov
6fed836bee calendar time on shared view 2023-09-19 14:22:46 +03:00
Yuri Kuznetsov
d39c8837fb v 2023-09-18 10:47:23 +03:00
Yuri Kuznetsov
870be0d1f0 upgrade fix 2023-09-18 10:41:12 +03:00
Yuri Kuznetsov
a889163d23 before upgrade check 2023-09-18 10:31:04 +03:00
Yuri Kuznetsov
1145d1c902 lang 2023-09-18 10:15:50 +03:00
Yuri Kuznetsov
d641e5faaa calendar css fix 2023-09-14 17:31:24 +03:00
Yuri Kuznetsov
57ca5ebf7b fix calendar month view day label 2023-09-14 15:26:18 +03:00
Yuri Kuznetsov
c2126e0680 fix email template plain text br 2023-09-14 14:36:18 +03:00
Yuri Kuznetsov
45ba66c0ff ref 2023-09-14 14:25:17 +03:00
Yuri Kuznetsov
a6af348a50 cs 2023-09-14 14:23:12 +03:00
Yuri Kuznetsov
95fe5a47f5 8.0.1 2023-09-14 13:30:09 +03:00
Yuri Kuznetsov
29b3166da9 shared calendar fix 2023-09-14 12:22:48 +03:00
Yuri Kuznetsov
38ab3fbc44 cs 2023-09-14 11:52:22 +03:00
Yuri Kuznetsov
b7ae677fb3 read-only link tooltip 2023-09-13 17:57:56 +03:00
Yuri Kuznetsov
f54656dde1 cleanup 2023-09-13 10:10:26 +03:00
Yuri Kuznetsov
fad1661b14 fix calendar all day 2023-09-13 10:06:53 +03:00
Yuri Kuznetsov
ed99c82137 fix calendar error msg on resize 2023-09-13 09:27:23 +03:00
Yuri Kuznetsov
c27a4fb609 fix calendar drop event 2023-09-13 09:21:03 +03:00
Yuri Kuznetsov
216268f225 cs 2023-09-13 09:06:40 +03:00
Yuri Kuznetsov
a5371d9a52 css fix 2023-09-12 10:36:26 +03:00
David
fdcf06efa9 fix render problems with prepare (#2840)
Co-authored-by: David Moškoř <david.moskor@apertia.cz>
2023-09-12 10:00:30 +03:00
Yuri Kuznetsov
755d48919f case emails link access read 2023-09-11 12:33:02 +03:00
Yuri Kuznetsov
51bd72dc62 fix warning 2023-09-11 10:15:31 +03:00
Yuri Kuznetsov
fe4a90ed97 ref 2023-09-10 14:00:35 +03:00
Yuri Kuznetsov
7ee08a6713 fix empty attributes error 2023-09-09 10:03:42 +03:00
Yuri Kuznetsov
0695fd87c0 api save option 2023-09-08 11:00:08 +03:00
Yuri Kuznetsov
ab8fe96919 fix doc 2023-09-08 10:55:31 +03:00
Yuri Kuznetsov
a20c3f14ac fix fullcalendar 2023-09-07 09:08:56 +03:00
Yuri Kuznetsov
62a0ca4fb5 fix field label element 2023-09-05 17:41:34 +03:00
Yuri Kuznetsov
2171f9c848 fix 2023-09-05 13:52:43 +03:00
Yuri Kuznetsov
384aed2a91 fix select with category 2023-09-04 12:40:35 +03:00
Yuri Kuznetsov
2d9f20f2cc shortcuts 2023-09-04 12:02:58 +03:00
Yuri Kuznetsov
8430e24eae fix doc 2023-09-04 12:00:37 +03:00
Yuri Kuznetsov
d13755ba5f diff fix 2023-09-04 11:12:24 +03:00
Yuri Kuznetsov
d496892878 colors 2023-09-04 10:37:19 +03:00
Yuri Kuznetsov
6d32879f21 throws 2023-09-02 19:13:56 +03:00
Yuri Kuznetsov
40e7eeb118 v 2023-09-01 09:32:29 +03:00
Yuri Kuznetsov
02ef6b73af Update README.md 2023-08-31 20:00:02 +03:00
Yuri Kuznetsov
e831768d92 ref email parser 2023-08-31 17:31:12 +03:00
Yuri Kuznetsov
a0695b492e attachment fallback disposition 2023-08-31 17:07:50 +03:00
Yuri Kuznetsov
cddcebfe27 fix 2023-08-31 13:42:30 +03:00
Yuri Kuznetsov
71ca25854f email select replyToString 2023-08-31 11:50:24 +03:00
Yuri Kuznetsov
ad962d5bcd all fields 2023-08-30 18:13:57 +03:00
Yuri Kuznetsov
0083d99d37 ref 2023-08-30 18:00:18 +03:00
Yuri Kuznetsov
5cd710e420 getMode 2023-08-28 15:40:38 +03:00
Yuri Kuznetsov
6da1bc083e isInlineEditMode 2023-08-28 15:35:05 +03:00
Yuri Kuznetsov
47aaecf3ac css fix 2023-08-28 11:57:34 +03:00
Yuri Kuznetsov
a3f2fadd9e schema 2023-08-28 10:19:44 +03:00
Yuri Kuznetsov
0cf851755b cs 2023-08-28 09:54:40 +03:00
Yuri Kuznetsov
1361168d00 helper iconClass 2023-08-28 09:54:33 +03:00
Yuri Kuznetsov
b888d3bcbc schema 2023-08-28 09:52:14 +03:00
Yuri Kuznetsov
203dce371c comment 2023-08-28 08:57:53 +03:00
Yuri Kuznetsov
1a81f4a8af merge 2023-08-27 15:47:12 +03:00
Yuri Kuznetsov
9b788c3c2d 7.5.6 2023-08-27 15:27:44 +03:00
Yuri Kuznetsov
17e487d011 update spatie 2023-08-27 15:19:58 +03:00
Yuri Kuznetsov
b2b11fba32 fix 2023-08-25 12:36:48 +03:00
Yuri Kuznetsov
4d4af995c8 fix lang 2023-08-25 09:54:01 +03:00
Yuri Kuznetsov
0d04aedd00 schema doc 2023-08-24 14:37:32 +03:00
Yuri Kuznetsov
53930866df loadAdditionalFieldsAfterUpdate 2023-08-23 13:48:50 +03:00
Yuri Kuznetsov
9ce5c7c2fa fix dashlet 2023-08-23 11:13:19 +03:00
Yuri Kuznetsov
dc30bd3991 fix 2023-08-21 14:01:21 +03:00
Yuri Kuznetsov
70c90972dc fix manage folder button appearance 2023-08-21 10:14:07 +03:00
Yuri Kuznetsov
9daa5b8583 schema 2023-08-19 19:14:21 +03:00
Yuri Kuznetsov
5916cfd345 schema 2023-08-19 19:08:53 +03:00
Yuri Kuznetsov
dec78c447d schema 2023-08-18 15:07:43 +03:00
Yuri Kuznetsov
100359ad6f schema 2023-08-17 14:13:07 +03:00
Yuri Kuznetsov
26e6f658fd fix msg 2023-08-17 14:03:53 +03:00
Yuri Kuznetsov
15bf2bf772 portal tab divideres 2023-08-17 12:14:19 +03:00
Yuri Kuznetsov
4e72413829 fix 2023-08-17 12:00:32 +03:00
Yuri Kuznetsov
0fb214434e schema 2023-08-17 10:55:28 +03:00
Yuri Kuznetsov
2b4c62eab8 fix 2023-08-17 10:42:16 +03:00
Hanefi Turkoz
0ed5f41fa8 typo (#2826) 2023-08-17 10:22:14 +03:00
Yuri Kuznetsov
7e973667c8 select field handler 2023-08-16 15:26:26 +03:00
Yuri Kuznetsov
8639fc5b39 fix 2023-08-16 15:06:11 +03:00
Yuri Kuznetsov
f503a08813 selectMandatoryAttributeList 2023-08-16 14:46:38 +03:00
Yuri Kuznetsov
634403cd6e fix notify close modal 2023-08-16 13:24:50 +03:00
Yuri Kuznetsov
6a539bcdcc schema 2023-08-16 10:33:28 +03:00
Yuri Kuznetsov
c43830cd6a fix docs 2023-08-15 20:13:34 +03:00
Yuri Kuznetsov
ca6c9dc312 fix 2023-08-15 20:12:33 +03:00
Yuri Kuznetsov
c5194edcdd docs 2023-08-15 20:10:59 +03:00
Yuri Kuznetsov
7905c1e254 ref 2023-08-15 19:56:24 +03:00
Yuri Kuznetsov
fa0d46dba9 date time methods 2023-08-15 19:55:19 +03:00
Yuri Kuznetsov
2bf8a96ec8 ref 2023-08-15 19:43:47 +03:00
Yuri Kuznetsov
41a15cc254 cs 2023-08-15 19:33:01 +03:00
Yuri Kuznetsov
2364ae67dd merge 2023-08-15 19:22:56 +03:00
Yuri Kuznetsov
3c64db25b9 portal entry point client fix 2023-08-15 19:14:47 +03:00
Yuri Kuznetsov
87b2703b1e select-related related-list layoutName option 2023-08-15 15:20:10 +03:00
Yuri Kuznetsov
d86a8e554b fix addActionHandler 2023-08-14 14:12:51 +03:00
Yuri Kuznetsov
af809c66b1 fix 2023-08-14 12:08:13 +03:00
Yuri Kuznetsov
702087f0b6 list layout label 2023-08-13 16:54:59 +03:00
Yuri Kuznetsov
d91db9ef62 fix doc 2023-08-13 16:40:32 +03:00
Arkadiy Asuratov
7af819a656 fix uuid matching in handlePostText (#2824) 2023-08-12 11:10:35 +03:00
Yuri Kuznetsov
633e678590 ref 2023-08-10 11:36:47 +03:00
Yuri Kuznetsov
a5f6d4e56a date filters fix 2023-08-10 10:55:36 +03:00
Yuri Kuznetsov
c71cf46cbe fix 2023-08-10 10:12:30 +03:00
Yuri Kuznetsov
3dc0dbbbac fix range 2023-08-10 09:27:05 +03:00
Yuri Kuznetsov
eeba4fd8bf cs 2023-08-09 15:56:30 +03:00
Yuri Kuznetsov
a797764534 do not use internal smtp 2023-08-09 15:55:45 +03:00
Rabii Brahimi
9f4aca43ee Fix wysiwyg.js (#2823) 2023-08-09 15:06:02 +03:00
Yuri Kuznetsov
849b992732 fix array add 2023-08-09 14:24:28 +03:00
Yuri Kuznetsov
cdf0e079b2 wysiwyg email tempalte fix 2023-08-09 14:15:34 +03:00
Yuri Kuznetsov
be647848ae donot allow portal customUrl in restricted mode 2023-08-09 12:50:12 +03:00
Yuri Kuznetsov
48369b203c cs 2023-08-09 12:44:16 +03:00
Yuri Kuznetsov
dd60c26591 fix empty values 2023-08-09 12:34:28 +03:00
Yuri Kuznetsov
5e834689c4 fix autocomplete 2023-08-09 11:59:35 +03:00
Yuri Kuznetsov
141c848260 ref 2023-08-09 11:08:55 +03:00
Yuri Kuznetsov
796a2fffda internal password recovery cooldown interval 2023-08-09 10:13:27 +03:00
Yuri Kuznetsov
6ea6102ae6 system data 2023-08-09 10:12:26 +03:00
Yuri Kuznetsov
bfeb504243 ref 2023-08-09 08:18:49 +03:00
Yuri Kuznetsov
174a86b497 cs 2023-08-09 07:31:36 +03:00
Yuri Kuznetsov
951902319e forbid passwordChangeLink change 2023-08-09 07:29:46 +03:00
Yuri Kuznetsov
dcb08a1d47 cs docs 2023-08-09 07:10:30 +03:00
Yuri Kuznetsov
6afe2beb5b config params system 2023-08-09 07:08:02 +03:00
Yuri Kuznetsov
906169fedc cs 2023-08-08 16:50:46 +03:00
Yuri Kuznetsov
68f5e58b57 cs 2023-08-08 16:50:30 +03:00
Yuri Kuznetsov
9755e8d08c fix 2023-08-08 16:37:42 +03:00
Yuri Kuznetsov
b1872bb08a fix 2023-08-08 16:05:21 +03:00
Yuri Kuznetsov
dd356cdfdc fix user password send 2023-08-08 16:04:25 +03:00
Yuri Kuznetsov
8231f1f839 fix doc 2023-08-08 16:04:13 +03:00
Yuri Kuznetsov
d248515d76 fix 2023-08-08 15:42:55 +03:00
Yuri Kuznetsov
113e075fb6 fix 2023-08-08 15:32:47 +03:00
Yuri Kuznetsov
c6a2622744 kanban status read only 2023-08-08 13:34:06 +03:00
Yuri Kuznetsov
cefec5e565 schmema 2023-08-08 10:40:37 +03:00
Yuri Kuznetsov
54ee4490c7 border radius fixes 2023-08-07 21:08:32 +03:00
Yuri Kuznetsov
2ab21839cb logo color fix 2023-08-07 14:33:31 +03:00
Yuri Kuznetsov
cbdf954eac logo color 2023-08-07 14:31:24 +03:00
Yuri Kuznetsov
6504505297 suppress warn 2023-08-07 14:02:13 +03:00
Yuri Kuznetsov
fa5307dc87 fix 2023-08-07 13:53:58 +03:00
Yuri Kuznetsov
80561c65fc remove 2023-08-04 16:52:14 +03:00
Yuri Kuznetsov
3d3128b831 create button change 2023-08-04 16:24:44 +03:00
Yuri Kuznetsov
b228d5188d fix typo 2023-08-04 16:09:41 +03:00
Yuri Kuznetsov
fdc8eeb650 css fix 2023-08-04 15:11:25 +03:00
Yuri Kuznetsov
8162f1f935 remove 2023-08-04 11:37:18 +03:00
Yuri Kuznetsov
b456037a08 button hidden 2023-08-04 11:35:00 +03:00
Yuri Kuznetsov
90f8eb20fc fix select with categories 2023-08-04 11:28:12 +03:00
Yuri Kuznetsov
19cb848877 fix line breaks 2023-08-03 14:42:15 +03:00
Andrew Fontana
7ef4535892 Localization of templates (it_IT) (#2813)
* Update subject.tpl

* Update subject.tpl

* Assignment template italian

* Mention Template Italian

* noteEmailReceived Template Italian

* notePost Template Italian

* notePostNoParent Template Italian

* noteStatus Template Italian

* twoFactorCode Template Italian
2023-08-03 14:39:29 +03:00
Aurelio
5e5d38aef2 Localization of templates (de_DE) (#2812) 2023-08-03 14:38:41 +03:00
Yuri Kuznetsov
f2b8dd8109 schema 2023-08-03 12:57:11 +03:00
Yuri Kuznetsov
8c706895b2 Update bug_report.md 2023-08-03 08:49:33 +03:00
Yuri Kuznetsov
8c479396f1 fix 2023-08-03 08:20:30 +03:00
Yuri Kuznetsov
fe83a35c0c cleanup 2023-08-02 18:20:22 +03:00
Yuri Kuznetsov
902b5887fc Update bug_report.md 2023-08-02 17:58:19 +03:00
Yuri Kuznetsov
9f9e2d7507 fix 2023-08-02 10:50:01 +03:00
Yuri Kuznetsov
ed50b357ad fix 2023-08-02 10:46:37 +03:00
Yuri Kuznetsov
65c45fa47d fix update note ui 2023-08-02 10:43:44 +03:00
Yuri Kuznetsov
3d4ceb9efb clone navbar on notifications/global search show 2023-08-01 11:31:55 +03:00
Yuri Kuznetsov
a6940d38a7 data object to panels 2023-07-31 16:37:33 +03:00
Yuri Kuznetsov
204410d599 ref 2023-07-31 16:31:34 +03:00
Yuri Kuznetsov
20f53eb50f record data object 2023-07-31 16:27:56 +03:00
Yuri Kuznetsov
887d0b208a ref 2023-07-31 16:01:32 +03:00
Yuri Kuznetsov
69dc2cf5a5 fix 2023-07-31 15:16:05 +03:00
Yuri Kuznetsov
a7353ee38a ref 2023-07-31 15:07:04 +03:00
Yuri Kuznetsov
541b0579e4 ref 2023-07-31 14:07:46 +03:00
Yuri Kuznetsov
ebb9ca1298 ref 2023-07-31 13:47:18 +03:00
Yuri Kuznetsov
90aa40bfb5 ref 2023-07-31 10:38:16 +03:00
Yuri Kuznetsov
6261d316f5 ref 2023-07-31 10:33:30 +03:00
Yuri Kuznetsov
3bb439e4b1 ref 2023-07-31 10:14:40 +03:00
Yuri Kuznetsov
d52d16ee19 defaultFileStorage readOnly 2023-07-31 09:19:20 +03:00
Yuri Kuznetsov
9d3dc38a13 ref 2023-07-30 18:39:30 +03:00
Yuri Kuznetsov
2888e1fadd ref 2023-07-30 16:45:40 +03:00
Yuri Kuznetsov
fcc990ac24 ref 2023-07-30 13:15:41 +03:00
Yuri Kuznetsov
5f9916202d ref 2023-07-29 14:20:34 +03:00
Arkadiy Asuratov
53a95ccf51 implement uuid generation compliant with rfc 4122 (#2809) 2023-07-28 15:59:20 +03:00
Yuri Kuznetsov
5c4f7b62a6 css fix 2023-07-28 13:40:26 +03:00
Yuri Kuznetsov
8186e6ebf0 fix diff 2023-07-28 11:26:59 +03:00
Yuri Kuznetsov
df2bd841ed cs 2023-07-27 19:46:25 +03:00
Yuri Kuznetsov
048fde70e2 fix jsdoc 2023-07-27 16:43:29 +03:00
Yuri Kuznetsov
5d87c27f23 onClick buttons and actions 2023-07-27 16:34:23 +03:00
Yuri Kuznetsov
9d44f250ab docs 2023-07-27 14:58:39 +03:00
Yuri Kuznetsov
31ace7d3f4 remove 2023-07-27 13:41:13 +03:00
Yuri Kuznetsov
d893328343 modal list un-notify 2023-07-27 13:28:40 +03:00
Yuri Kuznetsov
1ae22f929d list impr 2023-07-27 13:15:01 +03:00
Yuri Kuznetsov
73121fadf7 renaming 2023-07-27 11:44:47 +03:00
Yuri Kuznetsov
4bba92cdac remove 2023-07-27 11:32:00 +03:00
Yuri Kuznetsov
c3d6fa229c cs 2023-07-27 11:28:47 +03:00
Yuri Kuznetsov
4b09a6e29a update bullbone 2023-07-27 11:15:25 +03:00
Yuri Kuznetsov
876b8cc984 action handler pass target 2023-07-27 11:10:20 +03:00
Yuri Kuznetsov
cb188cc54d action handling fix 2023-07-27 11:06:45 +03:00
Yuri Kuznetsov
f3d4e85699 click action refactoring, using native event 2023-07-26 19:16:53 +03:00
Yuri Kuznetsov
74992821f6 fix modal adjust buttons not called 2023-07-26 17:58:25 +03:00
Yuri Kuznetsov
0299884306 ref 2023-07-26 17:56:50 +03:00
Yuri Kuznetsov
b459ef5550 comment 2023-07-26 17:26:10 +03:00
David
d526ce904a fixes https://github.com/espocrm/espocrm/issues/2805 (#2807)
Co-authored-by: David Moškoř <david.moskor@apertia.cz>
2023-07-26 17:22:49 +03:00
Yuri Kuznetsov
8b456270be shortcut support mathod name, action handling handler first priority 2023-07-26 16:29:50 +03:00
Yuri Kuznetsov
21569e4025 ref 2023-07-26 16:13:22 +03:00
Yuri Kuznetsov
621ada0c10 ref 2023-07-26 15:35:23 +03:00
Yuri Kuznetsov
50055b6500 ref 2023-07-26 13:46:13 +03:00
Yuri Kuznetsov
c81851d48d ref 2023-07-26 13:30:44 +03:00
Yuri Kuznetsov
8904d577e5 ref 2023-07-26 13:16:20 +03:00
Yuri Kuznetsov
34a7406167 ref 2023-07-26 13:07:02 +03:00
Yuri Kuznetsov
ab1b769240 ref 2023-07-26 12:49:21 +03:00
Yuri Kuznetsov
652ad7b344 ref 2023-07-26 12:27:46 +03:00
Yuri Kuznetsov
48fdfd88fa ref 2023-07-26 11:59:58 +03:00
Yuri Kuznetsov
1318f8d19d entity manager name max length on UI 2023-07-26 11:43:08 +03:00
Yuri Kuznetsov
c5fe52ede2 fix name length limitayion, throw exceptions with body 2023-07-26 11:38:39 +03:00
Yuri Kuznetsov
ae51d341e6 remove 2023-07-26 08:49:05 +03:00
Yuri Kuznetsov
96b08180d4 remove label usage 2023-07-25 20:28:35 +03:00
Yuri Kuznetsov
f0618e8465 fix form error handlnig 2023-07-25 16:06:40 +03:00
Yuri Kuznetsov
c9dd7722c3 fix phpdoc 2023-07-25 14:27:40 +03:00
Yuri Kuznetsov
025cdf246a ActionHistory interface 2023-07-25 14:25:19 +03:00
Yuri Kuznetsov
7fdd7cd280 formula allowedFunctionList 2023-07-25 10:55:17 +03:00
Yuri Kuznetsov
4bb9c11e55 fix install warning 2023-07-25 09:54:19 +03:00
Yuri Kuznetsov
1b0dccab42 mkdir fix 2023-07-25 09:16:59 +03:00
Yuri Kuznetsov
31c9a36bf4 ref 2023-07-25 09:02:01 +03:00
Yuri Kuznetsov
534b7e7226 ref 2023-07-24 18:21:52 +03:00
Yuri Kuznetsov
6b9d14a68b update bullbone 2023-07-24 18:21:40 +03:00
Yuri Kuznetsov
d41dfc86c7 suppress warn 2023-07-24 16:43:48 +03:00
Yuri Kuznetsov
b86e9c51f5 warning 2023-07-24 16:36:56 +03:00
Yuri Kuznetsov
0088a53ab8 color changes 2023-07-24 12:57:50 +03:00
Yuri Kuznetsov
efdff66593 fix event delete hook 2023-07-24 10:27:11 +03:00
Yuri Kuznetsov
837f8b2796 schedule/log label creation 2023-07-24 10:23:40 +03:00
Yuri Kuznetsov
82ff1772b2 theme fixes 2023-07-23 20:57:51 +03:00
Yuri Kuznetsov
36aa6739fc cs ref 2023-07-23 12:47:42 +03:00
Yuri Kuznetsov
0c26d35287 raw value import validations 2023-07-23 12:45:32 +03:00
Yuri Kuznetsov
753daebadf int float currency raw value validation 2023-07-23 10:21:44 +03:00
Yuri Kuznetsov
d21293ca13 cs 2023-07-23 09:23:44 +03:00
Yuri Kuznetsov
1e8fd10a0a list show more ref 2023-07-22 18:47:51 +03:00
Yuri Kuznetsov
b9df4b36ab list small on small screen 2023-07-22 13:15:01 +03:00
Yuri Kuznetsov
3832dec3e3 ref 2023-07-22 13:11:06 +03:00
Yuri Kuznetsov
45ff49da16 style fix 2023-07-22 12:54:39 +03:00
Yuri Kuznetsov
dfc000c1e1 list min width 500 2023-07-22 12:46:08 +03:00
Yuri Kuznetsov
604b7abc89 style fix 2023-07-21 15:56:42 +03:00
Yuri Kuznetsov
a4676be9d7 cleanup 2023-07-21 15:42:47 +03:00
Yuri Kuznetsov
19af4d8c96 style fix 2023-07-21 15:11:07 +03:00
Yuri Kuznetsov
272cff1cd9 installer fixes 2023-07-21 14:41:31 +03:00
Yuri Kuznetsov
d2f006892c scheduler free busy increase max range, non-working always 2023-07-21 13:25:29 +03:00
Yuri Kuznetsov
05637fd7bf cleanup 2023-07-21 12:50:22 +03:00
Yuri Kuznetsov
a9581907c7 label manager select 2023-07-21 12:40:16 +03:00
Yuri Kuznetsov
4d9fff91d4 ref 2023-07-21 12:37:30 +03:00
Yuri Kuznetsov
28b9cf683a style fix 2023-07-21 12:33:35 +03:00
Yuri Kuznetsov
20e1179085 style fix 2023-07-21 12:32:59 +03:00
Yuri Kuznetsov
c0ed4b7ed3 style fix 2023-07-21 12:06:40 +03:00
Yuri Kuznetsov
96fd21b381 light theme 2023-07-21 12:03:55 +03:00
Yuri Kuznetsov
49d4595049 style fix 2023-07-21 10:23:51 +03:00
Yuri Kuznetsov
eb98a1979d mkdir check 2023-07-21 09:27:07 +03:00
Yuri Kuznetsov
9416e3bc77 fix scheduler 2023-07-20 22:30:56 +03:00
Yuri Kuznetsov
350b121328 layout manager fix 2023-07-20 21:16:30 +03:00
Yuri Kuznetsov
47dfbce810 fix templates 2023-07-20 18:24:54 +03:00
Yuri Kuznetsov
e2c51f9d00 select applier for read action 2023-07-20 13:52:27 +03:00
Yuri Kuznetsov
d738188c8b additional applied for related find 2023-07-20 11:15:08 +03:00
Yuri Kuznetsov
0c8b3bc79e cs 2023-07-20 10:22:53 +03:00
Yuri Kuznetsov
7f8a152909 selectAccessControlDisabled 2023-07-20 09:51:09 +03:00
Yuri Kuznetsov
50c208e4db ref 2023-07-20 09:18:55 +03:00
Yuri Kuznetsov
92e9ef31a3 npm build frontend 2023-07-19 18:03:24 +03:00
Yuri Kuznetsov
d6cce6a887 fix po 2023-07-19 17:37:53 +03:00
Yuri Kuznetsov
8f7a76f2fe fix readme 2023-07-19 16:16:17 +03:00
Yuri Kuznetsov
6957de71ee readme fix 2023-07-19 16:15:26 +03:00
Yuri Kuznetsov
ee9436d45f deprecation comments 2023-07-19 16:03:18 +03:00
Yuri Kuznetsov
45c972f813 remove deprecated field manager util alias class 2023-07-19 15:42:58 +03:00
Yuri Kuznetsov
aa547c3934 fix deprecation docs 2023-07-19 15:37:06 +03:00
Yuri Kuznetsov
894cd14802 ref 2023-07-19 14:33:49 +03:00
Yuri Kuznetsov
950815890f fix 2023-07-19 14:17:17 +03:00
Yuri Kuznetsov
3d28723a92 calendar dashlet users field change 2023-07-19 14:14:21 +03:00
Yuri Kuznetsov
4b1519367f ref 2023-07-19 14:07:43 +03:00
Yuri Kuznetsov
b515b58ed1 fix 2023-07-19 14:03:27 +03:00
Yuri Kuznetsov
09cc9910a2 notify 2023-07-19 11:11:13 +03:00
Yuri Kuznetsov
463cedc12c template entity type not customizable 2023-07-19 10:57:59 +03:00
Yuri Kuznetsov
9d3abb7a0c ref 2023-07-19 10:51:17 +03:00
Yuri Kuznetsov
fef056fbe9 entity manager tool ref 2023-07-19 10:41:57 +03:00
Yuri Kuznetsov
acc8eaa4e2 frontendNonAdminHiddenPathList 2023-07-18 20:44:40 +03:00
Yuri Kuznetsov
d3f768975d ref 2023-07-18 20:27:21 +03:00
Yuri Kuznetsov
385dfa697a comment 2023-07-18 20:14:57 +03:00
Yuri Kuznetsov
560addae85 cs 2023-07-18 20:09:33 +03:00
Yuri Kuznetsov
4924b5f883 ref 2023-07-18 20:01:37 +03:00
Yuri Kuznetsov
49ed6eee31 entity manager ref 2023-07-18 19:58:04 +03:00
Yuri Kuznetsov
f6a5f3cf06 fix model 2023-07-18 19:44:11 +03:00
Yuri Kuznetsov
704e274e6a unlink required access check 2023-07-18 15:50:23 +03:00
Yuri Kuznetsov
6f71ad125d ref 2023-07-18 15:01:49 +03:00
Yuri Kuznetsov
c891b3a02e cs 2023-07-18 14:41:15 +03:00
Yuri Kuznetsov
417ce64c39 user latout set 2023-07-18 13:48:46 +03:00
Yuri Kuznetsov
d3d0c33fe2 fix 2023-07-18 13:47:28 +03:00
Yuri Kuznetsov
ae8f56381b fix 2023-07-18 13:39:07 +03:00
Yuri Kuznetsov
5752ccdf98 ref 2023-07-18 13:14:58 +03:00
Yuri Kuznetsov
7818f76e71 ref 2023-07-17 17:13:00 +03:00
Yuri Kuznetsov
0a7d80cbad types 2023-07-17 17:08:42 +03:00
Yuri Kuznetsov
54a753ec7e change issue template 2023-07-17 17:07:00 +03:00
Yuri Kuznetsov
d46f9ebd1f ref 2023-07-17 17:04:55 +03:00
Yuri Kuznetsov
41453b0fb3 refactor 2023-07-17 16:46:19 +03:00
Yuri Kuznetsov
2cebfc8046 cs 2023-07-17 16:15:25 +03:00
Yuri Kuznetsov
a20e7566c9 fix label 2023-07-17 13:48:39 +03:00
Yuri Kuznetsov
29b7bffadf required db version value change, add pg support 2023-07-17 12:49:48 +03:00
Yuri Kuznetsov
2142598058 style fix 2023-07-17 12:33:47 +03:00
Yuri Kuznetsov
0ed319927a installer postgresql 2023-07-17 12:01:54 +03:00
Yuri Kuznetsov
033f7a6a5f style fix 2023-07-17 10:47:06 +03:00
Yuri Kuznetsov
ac551b4448 install style fix 2023-07-17 10:41:33 +03:00
Yuri Kuznetsov
2299906c23 export custom 2023-07-17 10:24:04 +03:00
Yuri Kuznetsov
607b7c6985 fix global search 2023-07-17 10:23:39 +03:00
Yuri Kuznetsov
79c2378826 cs 2023-07-17 10:10:52 +03:00
Yuri Kuznetsov
241b668073 fix 2023-07-17 09:53:28 +03:00
Yuri Kuznetsov
e5ca8acdea field label text change 2023-07-17 09:50:34 +03:00
Yuri Kuznetsov
9e8800a8ba layout create modal change 2023-07-16 18:56:46 +03:00
Yuri Kuznetsov
b0d137b6bb jsdoc 2023-07-16 18:03:56 +03:00
Yuri Kuznetsov
5174b409fb update bullbone 2023-07-16 18:01:24 +03:00
Yuri Kuznetsov
0c0a4da7d3 controller entire support view instance 2023-07-16 17:57:55 +03:00
Yuri Kuznetsov
aead8f6edd controller main view instance 2023-07-16 17:39:23 +03:00
Yuri Kuznetsov
83f2899892 ref 2023-07-16 17:32:16 +03:00
Yuri Kuznetsov
3f25833d3d ref 2023-07-16 17:09:39 +03:00
Yuri Kuznetsov
26d2092ace jsdoc 2023-07-16 13:50:49 +03:00
Yuri Kuznetsov
c82c34f6a4 cleanup 2023-07-16 12:34:52 +03:00
Yuri Kuznetsov
00dca8b099 update bullbone 2023-07-16 12:33:54 +03:00
Yuri Kuznetsov
5c9c1ee4ed detail layout improvement and js docs 2023-07-16 12:14:55 +03:00
Yuri Kuznetsov
4e9c4e62a2 type fix 2023-07-16 11:39:31 +03:00
Yuri Kuznetsov
3e5377c830 cleanup cs 2023-07-15 20:07:08 +03:00
Yuri Kuznetsov
e3875812e7 types 2023-07-15 16:55:32 +03:00
Yuri Kuznetsov
ef16795ea7 fix formula 2023-07-15 10:42:35 +03:00
Yuri Kuznetsov
9490623d54 revert 2023-07-14 11:22:00 +03:00
Yuri Kuznetsov
f4f9086f79 quick search focus 2023-07-14 11:12:17 +03:00
Yuri Kuznetsov
22d057328a suppressValidationList, no Service class need for template, discard skip type validation in service 2023-07-14 10:45:44 +03:00
Yuri Kuznetsov
ef8c58e3b7 template entities repositories use existing classes 2023-07-14 10:01:26 +03:00
Yuri Kuznetsov
60c8502d27 contact role options reference 2023-07-14 09:05:20 +03:00
Yuri Kuznetsov
e65583dba7 comment 2023-07-14 09:00:39 +03:00
Yuri Kuznetsov
bebf02dccb ref 2023-07-13 21:25:48 +03:00
Yuri Kuznetsov
219d8b2e41 fix acceptance status filter 2023-07-13 17:38:24 +03:00
Yuri Kuznetsov
d20c0d452b fix 2023-07-13 17:20:02 +03:00
Yuri Kuznetsov
347c8bcef7 cs 2023-07-13 17:19:55 +03:00
Yuri Kuznetsov
93176838e2 fix role where 2023-07-13 16:19:11 +03:00
Yuri Kuznetsov
7dc6c4e5d0 title fetch null 2023-07-13 16:19:02 +03:00
Yuri Kuznetsov
430709b308 fix fetch search 2023-07-13 16:17:10 +03:00
Yuri Kuznetsov
417cdf5901 orm support exists where clause raw 2023-07-13 14:37:42 +03:00
Yuri Kuznetsov
ec0303ab09 opportunity contact role converter 2023-07-13 14:16:49 +03:00
Yuri Kuznetsov
b80fb7c817 field level converter 2023-07-13 14:16:37 +03:00
Yuri Kuznetsov
962e48c77a schema addition 2023-07-13 12:08:53 +03:00
Yuri Kuznetsov
4b1578237e fix typo 2023-07-13 12:06:25 +03:00
Yuri Kuznetsov
247d8763f7 fix deleted 0 2023-07-13 11:27:18 +03:00
Yuri Kuznetsov
a3e2a32a7d schema addition 2023-07-13 11:16:36 +03:00
Yuri Kuznetsov
237e39f495 job scheduler job data less support 2023-07-13 10:56:16 +03:00
Yuri Kuznetsov
a23b28bee9 duplicate save resolve 2023-07-13 10:35:29 +03:00
Yuri Kuznetsov
d8d13d5ae2 cs 2023-07-13 10:00:17 +03:00
Yuri Kuznetsov
0f6b35cbf6 types 2023-07-13 09:49:37 +03:00
Yuri Kuznetsov
969d06f934 types 2023-07-13 09:37:12 +03:00
Yuri Kuznetsov
ebcddd9477 ref 2023-07-13 09:05:59 +03:00
Yuri Kuznetsov
0cbe196bd3 layout delete cleanup 2023-07-12 17:27:45 +03:00
Yuri Kuznetsov
6087cad5ff custom list layouts 2023-07-12 17:19:10 +03:00
Yuri Kuznetsov
511d3f8eae ref 2023-07-12 09:14:13 +03:00
Yuri Kuznetsov
bbc8408c5a cs 2023-07-12 09:00:55 +03:00
Yuri Kuznetsov
f0723940e4 type fix 2023-07-12 08:59:58 +03:00
Yuri Kuznetsov
34516008f5 ref 2023-07-11 20:56:34 +03:00
Yuri Kuznetsov
000e3c0601 layout impr 2023-07-11 18:06:52 +03:00
Yuri Kuznetsov
ec6416bfcb fix system req 2023-07-11 13:08:54 +03:00
Yuri Kuznetsov
baa72e6f52 pdo_pgsql dependency 2023-07-11 12:55:12 +03:00
Yuri Kuznetsov
81f0b5ebe2 schema addition 2023-07-10 21:44:19 +03:00
Yuri Kuznetsov
e833af4e9d fix layout scroll 2023-07-10 17:51:25 +03:00
Yuri Kuznetsov
1255dd0728 layout impr 2023-07-10 17:47:42 +03:00
Yuri Kuznetsov
61ab5478bc css fix 2023-07-10 17:38:02 +03:00
Yuri Kuznetsov
c99eb08741 ref 2023-07-10 17:20:32 +03:00
Yuri Kuznetsov
52f1329ca4 layout impr 2023-07-10 15:55:05 +03:00
Yuri Kuznetsov
8f574b1275 ref 2023-07-10 15:44:40 +03:00
Yuri Kuznetsov
5e171464c3 side panel fields disabled 2023-07-10 14:59:46 +03:00
Yuri Kuznetsov
92bcb03cef utility usage 2023-07-10 14:42:51 +03:00
Yuri Kuznetsov
ead45003e7 fix 2023-07-10 14:40:36 +03:00
Yuri Kuznetsov
37a37e95c6 utility param 2023-07-10 14:26:35 +03:00
Yuri Kuznetsov
f68345d014 fix 2023-07-10 12:19:34 +03:00
Yuri Kuznetsov
9605afbb05 fix expanded layout field 2023-07-10 12:19:06 +03:00
Yuri Kuznetsov
7aa57ad36c bc 2023-07-10 12:18:54 +03:00
Yuri Kuznetsov
82efcefac7 duplicate check inprovements 2023-07-10 11:19:58 +03:00
Yuri Kuznetsov
9b176299ab updateDuplicateCheck param on UI 2023-07-10 09:23:18 +03:00
Yuri Kuznetsov
f70902255c recordDefs support 2023-07-10 09:15:14 +03:00
Yuri Kuznetsov
cbe79473ef Common entity type by default 2023-07-09 13:20:02 +03:00
Yuri Kuznetsov
158df5f652 type 2023-07-09 13:12:45 +03:00
Yuri Kuznetsov
253f54cb20 ref 2023-07-09 12:43:45 +03:00
Yuri Kuznetsov
90fa429fb9 ref 2023-07-08 15:54:03 +03:00
Yuri Kuznetsov
d7063a914f ref 2023-07-08 15:47:03 +03:00
Yuri Kuznetsov
0a6ba698ba fix stream portal user without portal 2023-07-08 14:35:22 +03:00
Yuri Kuznetsov
b11bfb0e92 name => entityType 2023-07-08 14:22:05 +03:00
Yuri Kuznetsov
ece7d1f3b4 ref 2023-07-08 11:17:55 +03:00
Yuri Kuznetsov
6d9a813d67 fix move to trash 2023-07-08 11:08:14 +03:00
Yuri Kuznetsov
f6382c7774 fix ajax 2023-07-08 10:56:34 +03:00
Yuri Kuznetsov
2525c53029 ref 2023-07-07 14:30:26 +03:00
Yuri Kuznetsov
ee9520811f ref 2023-07-06 16:15:06 +03:00
Yuri Kuznetsov
212e8e34ab update bullbone 2023-07-06 16:14:58 +03:00
Yuri Kuznetsov
1fe53ac5c2 ref 2023-07-06 15:38:47 +03:00
Yuri Kuznetsov
e1f7b64fe1 shortcuts original event 2023-07-06 15:26:24 +03:00
Yuri Kuznetsov
074d949d0b addActionHandler support any tag, use native event 2023-07-06 11:12:29 +03:00
Yuri Kuznetsov
0c07c7454d update jquery 2023-07-06 10:27:13 +03:00
Yuri Kuznetsov
82eb456565 update bullbone 2023-07-05 21:16:09 +03:00
Yuri Kuznetsov
285fc8ba8c el => selector 2023-07-05 21:07:47 +03:00
Yuri Kuznetsov
91e971a9ac update packages 2023-07-05 19:53:40 +03:00
Yuri Kuznetsov
a12fcfd6b9 update bullbone 2023-07-05 19:52:40 +03:00
Yuri Kuznetsov
1d2ae3d282 cs 2023-07-05 17:23:15 +03:00
Yuri Kuznetsov
2a42cc8542 ajax refactoring 2023-07-05 17:15:18 +03:00
Yuri Kuznetsov
e512f272a5 jsdoc 2023-07-05 10:49:18 +03:00
Yuri Kuznetsov
8a795dea4d cleanup 2023-07-05 10:29:52 +03:00
Yuri Kuznetsov
bc1333c5c5 remore pre-loader 2023-07-05 10:06:12 +03:00
Yuri Kuznetsov
ab378f4a59 Element support 2023-07-05 10:03:29 +03:00
Yuri Kuznetsov
a0bb78bc58 update composer dependencies 2023-07-05 09:38:23 +03:00
Yuri Kuznetsov
8d7a64587c style fix 2023-07-04 21:24:01 +03:00
Yuri Kuznetsov
860dd74748 fix 2023-07-04 21:21:51 +03:00
Yuri Kuznetsov
a8c4bb1a25 ref 2023-07-04 21:18:52 +03:00
Yuri Kuznetsov
b4f73192ae fix types 2023-07-04 21:07:26 +03:00
Yuri Kuznetsov
bd48715737 style fix 2023-07-04 19:28:14 +03:00
Yuri Kuznetsov
9a31099821 comment 2023-07-04 18:36:25 +03:00
Yuri Kuznetsov
1dba594fa4 fix jsdocs 2023-07-04 18:25:40 +03:00
Yuri Kuznetsov
e1f5f7b713 fix schema description 2023-07-04 17:36:34 +03:00
Yuri Kuznetsov
a5ed3f1d0c fix timeline delay 2023-07-04 16:44:06 +03:00
Yuri Kuznetsov
86ddf9d7cf history fix 2023-07-04 16:10:02 +03:00
Yuri Kuznetsov
6bd0a0ee1f update vis 2023-07-04 16:00:42 +03:00
Yuri Kuznetsov
05084c67db ref 2023-07-04 12:10:20 +03:00
Yuri Kuznetsov
cbb51c92e9 update build tools 2023-07-03 21:55:25 +03:00
Yuri Kuznetsov
ae7b42c3b0 lib ids fix 2023-07-03 21:53:34 +03:00
Yuri Kuznetsov
3da75e9dd0 lib rename 2023-07-03 21:16:20 +03:00
Yuri Kuznetsov
e7561911cc schema addition 2023-07-03 21:03:29 +03:00
Yuri Kuznetsov
0d727e74c0 update fullcalendar 2023-07-03 20:46:13 +03:00
Yuri Kuznetsov
36dbe5556f ref 2023-07-02 14:11:00 +03:00
Yuri Kuznetsov
8b8d08afaa deprecations 2023-07-01 22:09:41 +03:00
Yuri Kuznetsov
dda95dd741 ref 2023-07-01 21:50:50 +03:00
Yuri Kuznetsov
50cc43a742 deprecation remove and todo 2023-07-01 21:46:00 +03:00
Yuri Kuznetsov
d4ab9850f2 orm metadata fields => attributes 2023-07-01 20:22:21 +03:00
Yuri Kuznetsov
f561bb57f4 fetch search null 2023-07-01 18:52:11 +03:00
Yuri Kuznetsov
99d8681e46 fix docs 2023-07-01 18:48:04 +03:00
Yuri Kuznetsov
ecd28f6c2d todo 2023-07-01 18:21:32 +03:00
Yuri Kuznetsov
5a5f8845ca deprecation removal 2023-07-01 18:18:30 +03:00
Yuri Kuznetsov
8ac34018ab change todo 2023-07-01 17:54:10 +03:00
Yuri Kuznetsov
95b1560c8a ref 2023-07-01 17:49:06 +03:00
Yuri Kuznetsov
a7efea44d1 ref 2023-07-01 16:37:15 +03:00
Yuri Kuznetsov
42d8d2256d template fix 2023-07-01 14:59:25 +03:00
Yuri Kuznetsov
6063a295a0 ref 2023-07-01 14:18:08 +03:00
Yuri Kuznetsov
3c8bdeb539 ref 2023-07-01 13:10:19 +03:00
Yuri Kuznetsov
08ecfe58d5 cleanup 2023-06-30 14:59:00 +03:00
Yuri Kuznetsov
dd1bde9830 navbar fix 2023-06-30 14:55:36 +03:00
Yuri Kuznetsov
5861923f3b generate stub password for new users 2023-06-30 14:41:07 +03:00
Yuri Kuznetsov
5aef00dfaa ref 2023-06-30 14:23:17 +03:00
Yuri Kuznetsov
ea4790eb6f formula find many 2023-06-30 11:10:24 +03:00
Yuri Kuznetsov
6de98fd652 ref 2023-06-30 10:37:02 +03:00
Yuri Kuznetsov
2d180f5a07 fix 2023-06-30 10:27:25 +03:00
Yuri Kuznetsov
0ec3991b15 panel less ref, fix hover colors 2023-06-30 10:05:37 +03:00
Yuri Kuznetsov
d1a6a17c88 bg color fix 2023-06-30 09:07:41 +03:00
Yuri Kuznetsov
f1f1d1506d user select none 2023-06-29 23:14:23 +03:00
Yuri Kuznetsov
d8af0ee835 ref 2023-06-29 21:51:50 +03:00
Yuri Kuznetsov
164f96e30a kanban order first on create 2023-06-29 18:39:55 +03:00
Yuri Kuznetsov
b444f8b0fb kanban ref, read only status on create 2023-06-29 18:32:59 +03:00
Yuri Kuznetsov
3bac29828c id text filter 2023-06-29 17:54:08 +03:00
Yuri Kuznetsov
1c60d0b314 text filter fields skip attachment link 2023-06-29 17:52:41 +03:00
Yuri Kuznetsov
e7f4b555e9 ref 2023-06-29 16:14:33 +03:00
Yuri Kuznetsov
5347d994ef cleanup 2023-06-29 16:12:09 +03:00
Yuri Kuznetsov
bb20fe1929 ref 2023-06-29 16:11:21 +03:00
Yuri Kuznetsov
b282110d40 import file style fix 2023-06-29 15:44:54 +03:00
Yuri Kuznetsov
87fd4e7f57 ref 2023-06-29 15:12:57 +03:00
Yuri Kuznetsov
025b312a2e default tab list 2023-06-29 14:33:23 +03:00
Yuri Kuznetsov
b08813f9b6 kanban seprate less file and style changes 2023-06-29 13:18:22 +03:00
Yuri Kuznetsov
415199d814 post permission 2023-06-29 12:31:35 +03:00
Yuri Kuznetsov
6f7869784a style changes 2023-06-29 10:58:03 +03:00
Yuri Kuznetsov
ebe9784b23 stick bar fixes 2023-06-29 09:59:36 +03:00
Yuri Kuznetsov
645b891a1d stick-sub css fix 2023-06-28 21:41:58 +03:00
Yuri Kuznetsov
34fbf2df6d style fix 2023-06-28 20:55:55 +03:00
Yuri Kuznetsov
9ad2cfc855 auth provider no create from link 2023-06-28 20:54:24 +03:00
Yuri Kuznetsov
e4c7bd1baa stick bar style fixes 2023-06-28 18:13:54 +03:00
Yuri Kuznetsov
1ee8037d2b Merge branch 'fix' 2023-06-28 16:53:44 +03:00
Yuri Kuznetsov
275ee96750 fix id autoincrement conversion to varchar 2023-06-28 16:52:53 +03:00
Yuri Kuznetsov
8efff2f795 Merge branch 'fix' 2023-06-28 12:56:54 +03:00
Yuri Kuznetsov
b66cb676a1 7.5.5 2023-06-28 12:47:42 +03:00
Yuri Kuznetsov
975b7b72c3 dont apply default encoding 2023-06-28 12:31:13 +03:00
Yuri Kuznetsov
a520b9e57f settings ui tabs 2023-06-28 11:31:33 +03:00
Yuri Kuznetsov
65b9fabfd7 style changes 2023-06-28 11:11:40 +03:00
Yuri Kuznetsov
4be63cb75e navbar dividers 2023-06-28 10:44:59 +03:00
Yuri Kuznetsov
910ed80ae2 ref 2023-06-27 14:05:21 +03:00
Yuri Kuznetsov
8677a8354d libs rename key to amdId 2023-06-27 11:56:59 +03:00
Yuri Kuznetsov
d902417b21 ref 2023-06-26 21:07:36 +03:00
Yuri Kuznetsov
338e2e9089 gridstack with amd 2023-06-26 18:09:56 +03:00
Yuri Kuznetsov
fa88ba1583 / language=Handlebars 2023-06-26 14:10:11 +03:00
Yuri Kuznetsov
619c14ef65 update bullbone 2023-06-26 12:07:59 +03:00
Yuri Kuznetsov
3012967384 discard lib!espo 2023-06-25 19:30:25 +03:00
Yuri Kuznetsov
295aa8861b cleanup 2023-06-24 19:32:38 +03:00
Yuri Kuznetsov
447d949537 discard lib!espo usage 2023-06-24 19:27:40 +03:00
Yuri Kuznetsov
c87fa9463e discard expose param 2023-06-24 19:21:49 +03:00
Yuri Kuznetsov
f7f424bad7 discard amdId 2023-06-24 19:11:33 +03:00
Yuri Kuznetsov
e0606e20d8 aliases 2023-06-24 17:20:20 +03:00
Yuri Kuznetsov
91a2216d27 gridstack name usage 2023-06-24 15:06:07 +03:00
Yuri Kuznetsov
d2643a3372 lib renamings 2023-06-24 13:48:49 +03:00
Yuri Kuznetsov
fa01e80386 ref 2023-06-24 09:21:05 +03:00
Yuri Kuznetsov
31caa27d39 cs 2023-06-24 08:53:46 +03:00
Yuri Kuznetsov
c78962f5f6 fix tests init libs 2023-06-24 08:04:00 +03:00
Yuri Kuznetsov
4a1bdae913 Merge branch 'fix' 2023-06-24 07:55:11 +03:00
Yuri Kuznetsov
2dfd14f3c5 fix follower list 2023-06-23 23:25:32 +03:00
Yuri Kuznetsov
023400e84e fix imports 2023-06-23 20:02:26 +03:00
Yuri Kuznetsov
05fd772d46 update frontend tools 2023-06-23 19:54:45 +03:00
Yuri Kuznetsov
d409075b6c libs ids with prefix 2023-06-23 19:53:32 +03:00
Yuri Kuznetsov
8267c48aad ref 2023-06-23 16:08:16 +03:00
Yuri Kuznetsov
afbaf931eb ref 2023-06-23 15:26:18 +03:00
Yuri Kuznetsov
12105fb25f qr code fix 2023-06-23 15:21:45 +03:00
Yuri Kuznetsov
0bd9dbdba2 try catch qrcode 2023-06-23 14:56:38 +03:00
Yuri Kuznetsov
f48fbc58b5 style fix 2023-06-23 14:08:44 +03:00
Yuri Kuznetsov
c287d0ff5a fixes 2023-06-23 11:16:07 +03:00
Yuri Kuznetsov
bac2240c0b fix 2023-06-23 10:24:45 +03:00
Yuri Kuznetsov
300ed327df update tools 2023-06-22 20:55:22 +03:00
Yuri Kuznetsov
e14ca3ab77 rename 2023-06-22 20:50:42 +03:00
Yuri Kuznetsov
2b5ba6049f transpiled custom module support 2023-06-22 16:58:48 +03:00
Yuri Kuznetsov
caf6217b9a fix currency validation 2023-06-22 14:17:23 +03:00
Yuri Kuznetsov
716976c078 frontend build tools usage 2023-06-22 12:49:28 +03:00
Yuri Kuznetsov
00cf85abd4 Merge branch 'fix' 2023-06-22 09:47:39 +03:00
Yuri Kuznetsov
8ef2ed4144 7.5.4 2023-06-22 09:35:19 +03:00
Yuri Kuznetsov
befec82120 fix history 2023-06-22 09:27:11 +03:00
Yuri Kuznetsov
eb7923f4bc side navbar changes 2023-06-21 19:59:57 +03:00
Yuri Kuznetsov
d70687ff1e ref 2023-06-21 17:31:07 +03:00
Yuri Kuznetsov
e7f0c461c6 fix schema 2023-06-21 16:42:25 +03:00
Yuri Kuznetsov
0bbf3f5f0d duration not mergeable 2023-06-21 16:28:10 +03:00
Yuri Kuznetsov
321fd40355 ref 2023-06-21 16:26:33 +03:00
Yuri Kuznetsov
add5bcbe6a merge success notification fix 2023-06-21 16:20:57 +03:00
Yuri Kuznetsov
f39e59ba5b ref 2023-06-21 16:19:47 +03:00
Yuri Kuznetsov
a65bdbece5 cs 2023-06-21 15:23:35 +03:00
Yuri Kuznetsov
184fa6fd9b ref 2023-06-21 15:18:47 +03:00
Yuri Kuznetsov
897a14d07f noinspection 2023-06-21 15:07:46 +03:00
Yuri Kuznetsov
36d09b923e ref 2023-06-21 15:02:56 +03:00
Yuri Kuznetsov
0928c52100 fixes 2023-06-21 14:27:04 +03:00
Yuri Kuznetsov
daa1fcdcba cleanup 2023-06-21 13:52:27 +03:00
Yuri Kuznetsov
6bea09f246 Merge branch 'fix' 2023-06-21 13:19:38 +03:00
Yuri Kuznetsov
ec664163e5 ref 2023-06-21 12:57:06 +03:00
Yuri Kuznetsov
b130076313 ref 2023-06-21 12:47:20 +03:00
Yuri Kuznetsov
372a9c9640 cancel render list view 2023-06-21 12:31:13 +03:00
Yuri Kuznetsov
958a1e6634 ref 2023-06-21 11:52:17 +03:00
Yuri Kuznetsov
306a8728b4 ref 2023-06-21 11:39:47 +03:00
Yuri Kuznetsov
e9527a6bbf ref 2023-06-21 11:28:24 +03:00
Yuri Kuznetsov
d261a019bd jsdocs 2023-06-21 10:38:55 +03:00
Yuri Kuznetsov
6465277fb0 jsdoc, types 2023-06-20 23:03:31 +03:00
Yuri Kuznetsov
fc8be6d56f ref 2023-06-20 21:02:15 +03:00
Yuri Kuznetsov
cc32089a51 ref 2023-06-20 17:50:09 +03:00
Rabii Brahimi
56dd0aa594 Update detail.js (#2774)
Update detail.js - add condition check for dateEnd
2023-06-20 11:12:52 +03:00
Yuri Kuznetsov
71d8327b32 ref 2023-06-20 10:33:08 +03:00
Yuri Kuznetsov
e322e036a4 types 2023-06-20 10:31:58 +03:00
Yuri Kuznetsov
4f2651dd2b ref 2023-06-20 10:24:12 +03:00
Yuri Kuznetsov
f53553e301 Merge branch 'fix' 2023-06-20 09:29:57 +03:00
David
4c346bebff currency export fix (#2773)
Co-authored-by: David Moškoř <david.moskor@apertia.cz>
2023-06-19 22:52:47 +03:00
Yuri Kuznetsov
083a2c5235 Merge branch 'fix' 2023-06-19 19:41:59 +03:00
Yuri Kuznetsov
7adfb6c1c0 fix 2023-06-19 17:05:21 +03:00
Yuri Kuznetsov
190330c204 change templates location 2023-06-19 16:50:36 +03:00
Yuri Kuznetsov
56f975c65e template bundle 2023-06-19 15:03:39 +03:00
Yuri Kuznetsov
a79892028a number-util rename 2023-06-18 18:04:16 +03:00
Yuri Kuznetsov
3086b75616 js-doc fix 2023-06-18 17:55:59 +03:00
Yuri Kuznetsov
9955da3521 fix docs 2023-06-18 17:21:20 +03:00
Yuri Kuznetsov
0ba9130e96 cleanup 2023-06-17 21:56:50 +03:00
Yuri Kuznetsov
0af14b93c5 ref 2023-06-17 21:56:23 +03:00
Yuri Kuznetsov
191884d5af support relative import 2023-06-17 15:58:41 +03:00
Yuri Kuznetsov
dd1aac89ce using modules/{mod}/ naming 2023-06-17 15:48:28 +03:00
Yuri Kuznetsov
24203e8d07 docs 2023-06-17 15:47:49 +03:00
Yuri Kuznetsov
e53f79be03 more detail error msg 2023-06-16 18:12:48 +03:00
Yuri Kuznetsov
435dc2d818 merge unset not actual attributes 2023-06-16 17:20:45 +03:00
Yuri Kuznetsov
39e446ecf2 fix role permission tooltips 2023-06-16 16:14:30 +03:00
Yuri Kuznetsov
5a013ddc88 tooltip reference to another scope 2023-06-16 16:14:16 +03:00
Yuri Kuznetsov
14b595940f cs 2023-06-16 15:24:53 +03:00
Yuri Kuznetsov
b6d4b96aa8 fix installer, ref 2023-06-16 14:09:53 +03:00
Yuri Kuznetsov
de4c5d641d ref 2023-06-16 13:34:37 +03:00
Yuri Kuznetsov
f731419f86 ref 2023-06-16 12:48:49 +03:00
Yuri Kuznetsov
0e2f00665c update bullbone 2023-06-16 12:36:16 +03:00
Yuri Kuznetsov
cae0f541b5 bundler change 2023-06-16 12:33:59 +03:00
Yuri Kuznetsov
88dec452f6 cleanup 2023-06-16 12:33:16 +03:00
Yuri Kuznetsov
28072ad24f fix loader error response 2023-06-16 12:26:40 +03:00
Yuri Kuznetsov
857c252b14 merge 2023-06-16 10:54:16 +03:00
Yuri Kuznetsov
19e9abb7c4 object assign usage 2023-06-15 21:15:58 +03:00
Yuri Kuznetsov
89fa2f0523 bundler map dependencies 2023-06-15 18:50:55 +03:00
Yuri Kuznetsov
6686e78069 bundler mainChunk param 2023-06-15 17:51:12 +03:00
Yuri Kuznetsov
928ee586a0 ref 2023-06-15 15:52:50 +03:00
Yuri Kuznetsov
08d661d275 ref 2023-06-15 15:05:01 +03:00
Yuri Kuznetsov
f7709207d2 ref 2023-06-15 14:42:44 +03:00
Yuri Kuznetsov
6174171cab ref 2023-06-15 14:34:48 +03:00
Yuri Kuznetsov
6242e7c8b8 ref 2023-06-15 14:11:56 +03:00
Yuri Kuznetsov
8a4fd72261 ref 2023-06-15 14:09:38 +03:00
Yuri Kuznetsov
80588d0f5f ref 2023-06-15 13:32:13 +03:00
Yuri Kuznetsov
377f977f79 ref 2023-06-15 13:18:03 +03:00
Yuri Kuznetsov
68393c778b ref 2023-06-15 12:00:48 +03:00
Yuri Kuznetsov
8b8458cf78 ref 2023-06-15 11:29:24 +03:00
Yuri Kuznetsov
44e8dcf680 fix 2023-06-15 11:15:32 +03:00
Yuri Kuznetsov
b83e6170a4 notify error support options 2023-06-15 11:08:29 +03:00
Yuri Kuznetsov
dde0e24e0e fix collection doc and test 2023-06-15 11:00:17 +03:00
Yuri Kuznetsov
2d31c9bfb6 notification suppress 2023-06-15 10:57:56 +03:00
Yuri Kuznetsov
c0bdd74837 fix docs 2023-06-15 10:51:57 +03:00
Yuri Kuznetsov
3ccf2f4a00 fix 2023-06-15 10:29:37 +03:00
Yuri Kuznetsov
b9d81b7994 ref 2023-06-15 10:28:04 +03:00
Yuri Kuznetsov
fe8af42814 ref 2023-06-15 10:22:23 +03:00
Yuri Kuznetsov
03f2c33601 docs 2023-06-15 10:11:24 +03:00
Yuri Kuznetsov
1613398492 fix toJSON usage 2023-06-15 09:57:10 +03:00
Yuri Kuznetsov
87ad554531 fix transpiler 2023-06-15 09:57:04 +03:00
Yuri Kuznetsov
d74363baea fix jsdoc 2023-06-15 09:41:03 +03:00
Yuri Kuznetsov
535baff2aa cleanup 2023-06-14 23:35:37 +03:00
Yuri Kuznetsov
dcad84aeff cs 2023-06-14 20:55:07 +03:00
Yuri Kuznetsov
df3e0bf956 ref 2023-06-14 20:17:26 +03:00
Yuri Kuznetsov
4215ad6e88 ref 2023-06-14 19:17:41 +03:00
Yuri Kuznetsov
5c6a4ae7b2 loader use fetch 2023-06-14 19:00:47 +03:00
Yuri Kuznetsov
33cece0cd1 fix naming 2023-06-14 18:51:32 +03:00
Yuri Kuznetsov
4b3b48c981 change date 2023-06-14 17:44:26 +03:00
Yuri Kuznetsov
99eec81983 ref 2023-06-14 17:10:37 +03:00
Yuri Kuznetsov
e1b5e2db33 fix schema 2023-06-14 16:19:26 +03:00
Yuri Kuznetsov
cd76087c2f fix bool filter legacy schema 2023-06-14 16:19:18 +03:00
Yuri Kuznetsov
ff34df3e42 ref 2023-06-14 16:04:32 +03:00
Yuri Kuznetsov
db43fd3a12 transpiler changes 2023-06-14 15:28:06 +03:00
Yuri Kuznetsov
de42e7e4c1 fix schema desc 2023-06-14 14:58:38 +03:00
Yuri Kuznetsov
5baeea2b5d loader fix fetch object null 2023-06-14 14:39:50 +03:00
Yuri Kuznetsov
6d7be12c60 ref 2023-06-14 14:39:25 +03:00
Yuri Kuznetsov
98fb897785 loader changes 2023-06-14 14:22:57 +03:00
Yuri Kuznetsov
7bdd9b25ce ref 2023-06-14 13:44:04 +03:00
Yuri Kuznetsov
70b995e6f7 events static usage fix 2023-06-14 12:49:37 +03:00
Yuri Kuznetsov
ecb6ec3adf fix frontend tests, error on loader loader second time 2023-06-14 12:05:13 +03:00
Yuri Kuznetsov
431f7e6fd5 rename bundles 2023-06-14 11:34:58 +03:00
Yuri Kuznetsov
e0f8688cb9 ref 2023-06-14 11:09:39 +03:00
Yuri Kuznetsov
6fcd6bc6e4 loader bundler fixes 2023-06-14 10:57:51 +03:00
Yuri Kuznetsov
56d72946bc ref 2023-06-13 21:10:48 +03:00
Yuri Kuznetsov
9099f84441 changes 2023-06-13 18:42:26 +03:00
Yuri Kuznetsov
544e8e9d3f ref loader 2023-06-12 13:22:09 +03:00
Yuri Kuznetsov
a032a41935 discard es-promise lib 2023-06-12 13:12:57 +03:00
Yuri Kuznetsov
e43215cdf1 cs 2023-06-11 21:02:23 +03:00
Yuri Kuznetsov
781aab0c1d ref 2023-06-11 20:48:56 +03:00
Yuri Kuznetsov
197aade0d1 ref 2023-06-11 15:30:54 +03:00
Yuri Kuznetsov
5de9f545b0 ref 2023-06-11 14:57:47 +03:00
Yuri Kuznetsov
43cf24d8d6 ref 2023-06-11 13:13:28 +03:00
Yuri Kuznetsov
b3858b977a ref 2023-06-10 22:49:46 +03:00
Yuri Kuznetsov
1a38c79d94 jsdoc 2023-06-10 19:34:26 +03:00
Yuri Kuznetsov
7ea46bb370 ref 2023-06-10 19:30:06 +03:00
Yuri Kuznetsov
2b8120811c fix 2023-06-10 19:29:51 +03:00
Yuri Kuznetsov
3498ebb978 test fixes 2023-06-10 16:31:34 +03:00
Yuri Kuznetsov
5f71c2c1b6 ref 2023-06-10 15:58:48 +03:00
Yuri Kuznetsov
c68141d4bb discard callback usage 2023-06-10 12:52:27 +03:00
Yuri Kuznetsov
70b6f456af update jasmine 2023-06-10 10:00:59 +03:00
Yuri Kuznetsov
0b86592d5c fix tests 2023-06-10 10:00:38 +03:00
Yuri Kuznetsov
91ee5af643 ref 2023-06-09 19:26:43 +03:00
Yuri Kuznetsov
9df03c1119 ref 2023-06-09 19:10:42 +03:00
Yuri Kuznetsov
80fb01fd7d ref 2023-06-09 18:50:52 +03:00
Yuri Kuznetsov
febbfa2126 ref 2023-06-09 18:35:39 +03:00
Yuri Kuznetsov
c9a959d548 ref 2023-06-09 17:26:29 +03:00
Yuri Kuznetsov
24473783e9 ref 2023-06-09 17:12:52 +03:00
Yuri Kuznetsov
cd429f9556 ref 2023-06-09 16:47:46 +03:00
Yuri Kuznetsov
c9b9023f38 ref 2023-06-09 16:21:03 +03:00
Yuri Kuznetsov
c8adc8c654 update bullbone fix 2023-06-09 15:56:25 +03:00
Yuri Kuznetsov
08501ce207 ref 2023-06-09 15:55:27 +03:00
Yuri Kuznetsov
1e3a9cc07d ref 2023-06-09 14:48:27 +03:00
Yuri Kuznetsov
d9dc87f9ab ref 2023-06-09 14:36:46 +03:00
Yuri Kuznetsov
31f8cf94da ref 2023-06-09 14:00:01 +03:00
Yuri Kuznetsov
c9c5913845 ref 2023-06-09 13:44:52 +03:00
Yuri Kuznetsov
95e3276394 ref 2023-06-09 12:56:40 +03:00
Yuri Kuznetsov
8d06c82338 fix 2023-06-09 12:05:55 +03:00
Yuri Kuznetsov
5da31f76f4 update bullbone 2023-06-09 11:57:37 +03:00
Yuri Kuznetsov
8c3933e60e ref 2023-06-09 11:42:19 +03:00
Yuri Kuznetsov
c67ea8309a Merge branch 'master' into version/7.6 2023-06-08 20:08:37 +03:00
Yuri Kuznetsov
b7c3b32b49 bull.events 2023-06-08 19:16:00 +03:00
Yuri Kuznetsov
f6f32fbaa0 list select attributes helper 2023-06-08 15:13:36 +03:00
Yuri Kuznetsov
06f807bb4d schema 2023-06-08 14:04:14 +03:00
Yuri Kuznetsov
20975628b2 grunt clean addition 2023-06-08 14:02:13 +03:00
Yuri Kuznetsov
c6b7337317 es modules first migration 2023-06-08 13:31:27 +03:00
Yuri Kuznetsov
4e040700ab Merge branch 'master' into version/7.6 2023-06-08 13:30:36 +03:00
Yuri Kuznetsov
673fcfc9fe Merge branch 'master' into version/7.6 2023-06-04 15:00:45 +03:00
Yuri Kuznetsov
ed1ac27f75 fix enum position 2023-06-04 15:00:05 +03:00
Yuri Kuznetsov
d202d53ce2 fix 2023-06-04 14:29:02 +03:00
Yuri Kuznetsov
8485e7b436 my inbox dashlet folders 2023-06-04 14:22:20 +03:00
Yuri Kuznetsov
e85c110697 fix schema 2023-06-04 13:24:56 +03:00
Yuri Kuznetsov
a9c0aefa58 add array item quick search 2023-06-04 13:12:07 +03:00
Yuri Kuznetsov
3a5c840ce8 cleanup 2023-06-04 13:08:26 +03:00
Yuri Kuznetsov
bbf7f8a98b ref 2023-06-04 11:24:51 +03:00
Yuri Kuznetsov
24dee6aa62 logout clear stored auth if fail on app start 2023-06-03 21:37:30 +03:00
Yuri Kuznetsov
c33452fd0b discard template bundle 2023-06-03 20:40:36 +03:00
Yuri Kuznetsov
ad099dff27 bundle dependency 2023-06-03 11:06:31 +03:00
Yuri Kuznetsov
d31e90fb42 use app timestamp 2023-06-03 09:47:57 +03:00
Yuri Kuznetsov
c150ee66af loader bundle promise map and prevent multiple define same module 2023-06-03 08:38:04 +03:00
Yuri Kuznetsov
dac6d74fd1 change 2023-06-02 19:50:59 +03:00
Yuri Kuznetsov
598a87c349 cleanup 2023-06-02 19:28:56 +03:00
Yuri Kuznetsov
34ec6cfc8d bundle chunks 2023-06-02 19:26:38 +03:00
Yuri Kuznetsov
39865b4c09 bundler changes 2023-06-01 16:35:25 +03:00
Yuri Kuznetsov
116a605053 1 chunk 2023-06-01 14:12:22 +03:00
Yuri Kuznetsov
a1da459343 Merge branch 'master' into version/7.6 2023-06-01 13:32:40 +03:00
Yuri Kuznetsov
12440e7ab6 add dashlet quick search 2023-06-01 13:25:35 +03:00
Yuri Kuznetsov
39af37f20c dashlet options impr 2023-06-01 13:07:51 +03:00
Yuri Kuznetsov
0b9abdd70d schema autoload 2023-06-01 12:14:32 +03:00
Yuri Kuznetsov
29c9b0fc78 cs 2023-06-01 11:10:39 +03:00
Yuri Kuznetsov
aee977ac36 installer ref 2023-06-01 11:05:46 +03:00
Yuri Kuznetsov
8b03a82480 preload icon fonts 2023-06-01 11:05:30 +03:00
Yuri Kuznetsov
95f9946476 cleanup 2023-06-01 10:16:19 +03:00
Yuri Kuznetsov
a18866a900 fix 2023-05-31 18:23:22 +03:00
Yuri Kuznetsov
ab65f99a44 discard original/espo 2023-05-31 17:48:10 +03:00
Yuri Kuznetsov
2a4158a130 fix 2023-05-31 17:47:56 +03:00
Yuri Kuznetsov
0befa49f1a CORS middleware (#2754)
* cors

* bind helper

* ref
2023-05-31 17:03:39 +03:00
Yuri Kuznetsov
d4aa9745ca ORM: Join sub-query (#2757)
* orm join sub-query

* tests
2023-05-31 17:01:43 +03:00
Yuri Kuznetsov
260a5c89ef discard pre-load config 2023-05-31 16:44:02 +03:00
Yuri Kuznetsov
b19aec81b4 layout template bundle 2023-05-31 16:23:01 +03:00
Yuri Kuznetsov
9eea792fb1 bundle chunks 3 2023-05-31 14:56:10 +03:00
Yuri Kuznetsov
d05a084509 template precompile 2023-05-31 14:48:23 +03:00
Yuri Kuznetsov
1722d3b69f fix 2023-05-31 13:23:43 +03:00
Yuri Kuznetsov
53a6384515 update bullbone 2023-05-31 13:18:16 +03:00
Yuri Kuznetsov
5e492d3e3f bundler impr 2023-05-31 11:34:39 +03:00
Yuri Kuznetsov
1daac4cfad foreign field copy params 2023-05-30 12:37:26 +03:00
Yuri Kuznetsov
646939bcf8 binding value conflict handling 2023-05-29 18:26:11 +03:00
1288 changed files with 98802 additions and 83529 deletions

View File

@@ -1,6 +1,6 @@
---
name: Bug report
about: Create a bug report. Not to be used for help requests or server configuration issues. We appreciate if you prefer posting bug reports on weekdays rather than weekends.
about: Create a bug report. Not to be used for help requests or server configuration issues. Only for issues related to open source EspoCRM. Issues related to extensions should not to be posted here.
title: ''
labels: ''
assignees: ''
@@ -11,7 +11,7 @@ assignees: ''
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
Explicit steps to reproduce the behavior:
1. ...
2. ...
3. ...

View File

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

View File

@@ -1,5 +1,8 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JSCodeStyleSettings version="0">
<option name="USE_DOUBLE_QUOTES" value="false" />
</JSCodeStyleSettings>
<PHPCodeStyleSettings>
<option name="GROUP_USE_WRAP" value="2" />
<option name="VARIABLE_NAMING_STYLE" value="CAMEL_CASE" />

View File

@@ -2,6 +2,7 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="ES6ConvertVarToLetConst" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />

60
.idea/jsonSchemas.xml generated
View File

@@ -22,6 +22,9 @@
<option name="path" value="schema/metadata" />
<option name="mappingKind" value="Directory" />
</Item>
<Item>
<option name="path" value="schema/autoload.json" />
</Item>
<Item>
<option name="path" value="schema/module.json" />
</Item>
@@ -33,6 +36,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="autoload">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="autoload" />
<option name="relativePathToSchema" value="schema/autoload.json" />
<option name="schemaVersion" value="JSON Schema version 7" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/Resources/autoload.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="layouts/detail">
<value>
<SchemaInfo>
@@ -566,6 +588,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/entityTemplates">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/app/entityTemplates" />
<option name="relativePathToSchema" value="schema/metadata/app/entityTemplates.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/entityTemplates.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/export">
<value>
<SchemaInfo>
@@ -756,6 +797,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/entityManager">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/app/entityManager" />
<option name="relativePathToSchema" value="schema/metadata/app/entityManager.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/entityManager.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/massActions">
<value>
<SchemaInfo>

18
.vscode/settings.json vendored
View File

@@ -1,5 +1,11 @@
{
"json.schemas": [
{
"fileMatch": [
"*/Resources/autoload.json"
],
"url": "./schema/autoload.json"
},
{
"fileMatch": [
"*/Resources/routes.json"
@@ -262,6 +268,12 @@
],
"url": "./schema/metadata/app/entityTemplateList.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/entityTemplates.json"
],
"url": "./schema/metadata/app/entityTemplates.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/export.json"
@@ -322,6 +334,12 @@
],
"url": "./schema/metadata/app/linkManager.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/entityManager.json"
],
"url": "./schema/metadata/app/entityManager.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/massActions.json"

View File

@@ -33,16 +33,32 @@ const fs = require('fs');
const cp = require('child_process');
const path = require('path');
const buildUtils = require('./js/build-utils');
const {TemplateBundler, Bundler} = require('espo-frontend-build-tools');
const LayoutTypeBundler = require('./js/layout-template-bundler');
const bundleConfig = require('./frontend/bundle-config.json');
const libs = require('./frontend/libs.json');
module.exports = grunt => {
const pkg = grunt.file.readJSON('package.json');
const bundleConfig = require('./frontend/bundle-config.json');
const libs = require('./frontend/libs.json');
const originalLibDir = 'client/lib/original';
let bundleJsFileList = buildUtils.getPreparedBundleLibList(libs).concat(originalLibDir + '/espo.js');
let libsBundleFileList = [
'client/src/namespace.js',
'client/src/loader.js',
...buildUtils.getPreparedBundleLibList(libs),
];
let bundleFileMap = {'client/lib/espo.js': libsBundleFileList};
for (let name in bundleConfig.chunks) {
let namePart = 'espo-' + name;
bundleFileMap[`client/lib/${namePart}.js`] = originalLibDir + `/${namePart}.js`
}
let copyJsFileList = buildUtils.getCopyLibDataList(libs);
let minifyLibFileList = copyJsFileList
@@ -118,6 +134,10 @@ module.exports = grunt => {
'!build/tmp/client/custom/modules',
'build/tmp/client/custom/modules/*',
'!build/tmp/client/custom/modules/dummy.txt',
'build/tmp/client/lib/original/espo.js',
'build/tmp/client/lib/original/espo-*.js',
'!build/tmp/client/lib/original/espo-funnel-chart.js',
'build/tmp/client/lib/transpiled',
]
},
},
@@ -132,19 +152,19 @@ module.exports = grunt => {
uglify: {
options: {
mangle: true,
sourceMap: true,
output: {
comments: /^!/,
},
beautify: false,
mangle: true,
compress: true
},
bundle: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
},
files: {
'client/lib/espo.min.js': bundleJsFileList,
},
files: bundleFileMap,
},
lib: {
files: minifyLibFileList,
@@ -162,7 +182,6 @@ module.exports = grunt => {
'src/**',
'res/**',
'fonts/**',
'cfg/**',
'modules/**',
'img/**',
'css/**',
@@ -249,16 +268,43 @@ module.exports = grunt => {
},
});
grunt.registerTask('espo-bundle', () => {
const Bundler = require('./js/bundler');
let contents = (new Bundler()).bundle(bundleConfig.jsFiles);
const writeOriginalLib = (name, contents) => {
if (!fs.existsSync(originalLibDir)) {
fs.mkdirSync(originalLibDir);
}
fs.writeFileSync(originalLibDir + '/espo.js', contents, 'utf8');
let file = originalLibDir + `/${name}.js`;
fs.writeFileSync(file, contents, 'utf8');
};
grunt.registerTask('bundle', () => {
let bundler = new Bundler(bundleConfig, libs);
let result = bundler.bundle();
for (let name in result) {
let contents = result[name];
let key = 'espo-' + name;
if (name === 'main') {
contents += '\n' + (new LayoutTypeBundler()).bundle();
}
writeOriginalLib(key, contents);
}
});
grunt.registerTask('bundle-templates', () => {
let templateBundler = new TemplateBundler({
dirs: [
'client/res/templates',
'client/modules/crm/res/templates',
],
});
templateBundler.process();
});
grunt.registerTask('prepare-lib-original', () => {
@@ -270,6 +316,10 @@ module.exports = grunt => {
cp.execSync("node js/scripts/prepare-lib");
});
grunt.registerTask('transpile', () => {
cp.execSync("node js/transpile");
});
grunt.registerTask('chmod-folders', () => {
cp.execSync(
"find . -type d -exec chmod 755 {} +",
@@ -443,8 +493,10 @@ module.exports = grunt => {
grunt.registerTask('internal', [
'less',
'cssmin',
'espo-bundle',
'prepare-lib-original',
'transpile',
'bundle',
'bundle-templates',
'uglify:bundle',
'copy:frontendLib',
'prepare-lib',

View File

@@ -22,6 +22,7 @@ You can try the CRM on the online [demo](https://www.espocrm.com/demo/).
* PHP 8.0 and later;
* MySQL 5.7 (and later), or MariaDB 10.2 (and later).
* PostgreSQL 15 (and later) (yet experimental, officially supported soon)
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
@@ -60,7 +61,7 @@ Branches:
### Language
If you want to improve existing translation or add a language that is not available yet, you can contribute on our [POEditor](https://poeditor.com/join/project/gLDKZtUF4i) project. See instructions [here](https://www.espocrm.com/blog/how-to-use-poeditor-to-translate-espocrm/).
If you want to improve existing translation or add a language that is not available yet, you can contribute on our [POEditor](https://poeditor.com/join/project/gLDKZtUF4i) project. See instructions [here](https://www.espocrm.com/blog/how-to-use-poeditor-to-translate-espocrm/). It may be reasonable to let us know about your intention to join the POEditor project by posting on our forum or via the contact form on our website.
Changes on POEditor are usually merged to the GitHub repository before minor releases.

View File

@@ -240,6 +240,11 @@ class Binding implements BindingProcessor
'Espo\\ORM\\PDO\\PDOProvider',
'Espo\\ORM\\PDO\\DefaultPDOProvider'
);
$binder->bindImplementation(
'Espo\\Core\\Utils\\Database\\ConfigDataProvider',
'Espo\\Core\\Utils\\Database\\DefaultConfigDataProvider'
);
}
private function bindMisc(Binder $binder): void
@@ -282,6 +287,16 @@ class Binding implements BindingProcessor
'Espo\\Core\\Mail\\Importer\\DuplicateFinder',
'Espo\\Core\\Mail\\Importer\\DefaultDuplicateFinder'
);
$binder->bindImplementation(
'Espo\\Tools\\Api\\Cors\\Helper',
'Espo\\Tools\\Api\\Cors\\DefaultHelper'
);
$binder->bindImplementation(
'Espo\\Core\\Record\\ActionHistory\\ActionLogger',
'Espo\\Core\\Record\\ActionHistory\\DefaultActionLogger'
);
}
private function bindAcl(Binder $binder): void

View File

@@ -29,85 +29,5 @@
namespace Espo\Classes\DuplicateWhereBuilders;
use Espo\Core\Duplicate\WhereBuilder;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\ORM\Entity;
use Espo\ORM\Query\Part\Condition as Cond;
use Espo\ORM\Query\Part\Where\OrGroup;
use Espo\ORM\Query\Part\WhereItem;
/**
* @implements WhereBuilder<CoreEntity>
*/
class Company implements WhereBuilder
{
/**
* @param CoreEntity $entity
*/
public function build(Entity $entity): ?WhereItem
{
$orBuilder = OrGroup::createBuilder();
$toCheck = false;
if ($entity->get('name')) {
$orBuilder->add(
Cond::equal(
Cond::column('name'),
$entity->get('name')
),
);
$toCheck = true;
}
if (
($entity->get('emailAddress') || $entity->get('emailAddressData')) &&
(
$entity->isNew() ||
$entity->isAttributeChanged('emailAddress') ||
$entity->isAttributeChanged('emailAddressData')
)
) {
foreach ($this->getEmailAddressList($entity) as $emailAddress) {
$orBuilder->add(
Cond::equal(
Cond::column('emailAddress'),
$emailAddress
)
);
$toCheck = true;
}
}
if (!$toCheck) {
return null;
}
return $orBuilder->build();
}
/**
* @return string[]
*/
private function getEmailAddressList(CoreEntity $entity): array
{
if ($entity->get('emailAddressData')) {
/** @var EmailAddressGroup $eaGroup */
$eaGroup = $entity->getValueObject('emailAddress');
return $eaGroup->getAddressList();
}
if ($entity->get('emailAddress')) {
return [
$entity->get('emailAddress')
];
}
return [];
}
}
class Company extends General
{}

View File

@@ -0,0 +1,262 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\DuplicateWhereBuilders;
use Espo\Core\Duplicate\WhereBuilder;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\Field\PhoneNumberGroup;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs;
use Espo\ORM\Entity;
use Espo\ORM\Query\Part\Condition as Cond;
use Espo\ORM\Query\Part\Where\OrGroup;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereItem;
use Espo\ORM\Type\AttributeType;
/**
* @implements WhereBuilder<CoreEntity>
*/
class General implements WhereBuilder
{
public function __construct(
private Metadata $metadata,
private Defs $ormDefs
) {}
/**
* @param CoreEntity $entity
*/
public function build(Entity $entity): ?WhereItem
{
/** @var string[] $fieldList */
$fieldList = $this->metadata->get(['scopes', $entity->getEntityType(), 'duplicateCheckFieldList']) ?? [];
$orBuilder = OrGroup::createBuilder();
$toCheck = false;
foreach ($fieldList as $field) {
$toCheckItem = $this->applyField($field, $entity, $orBuilder);
if ($toCheckItem) {
$toCheck = true;
}
}
if (!$toCheck) {
return null;
}
return $orBuilder->build();
}
private function applyField(
string $field,
CoreEntity $entity,
OrGroupBuilder $orBuilder
): bool {
$type = $this->ormDefs
->getEntity($entity->getEntityType())
->tryGetField($field)
?->getType();
if ($type === 'personName') {
return $this->applyFieldPersonName($field, $entity, $orBuilder);
}
if ($type === 'email') {
return $this->applyFieldEmail($field, $entity, $orBuilder);
}
if ($type === 'phone') {
return $this->applyFieldPhone($field, $entity, $orBuilder);
}
if ($entity->getAttributeType($field) === AttributeType::VARCHAR) {
return $this->applyFieldVarchar($field, $entity, $orBuilder);
}
return false;
}
private function applyFieldPersonName(
string $field,
CoreEntity $entity,
OrGroupBuilder $orBuilder
): bool {
$first = 'first' . ucfirst($field);
$last = 'last' . ucfirst($field);
if (!$entity->get($first) && !$entity->get($last)) {
return false;
}
$orBuilder->add(
Cond::and(
Cond::equal(
Cond::column($first),
$entity->get($first)
),
Cond::equal(
Cond::column($last),
$entity->get($last)
)
)
);
return true;
}
private function applyFieldEmail(
string $field,
CoreEntity $entity,
OrGroupBuilder $orBuilder
): bool {
$toCheck = false;
if (
($entity->get($field) || $entity->get($field . 'Data')) &&
(
$entity->isNew() ||
$entity->isAttributeChanged($field) ||
$entity->isAttributeChanged($field . 'Data')
)
) {
foreach ($this->getEmailAddressList($entity) as $emailAddress) {
$orBuilder->add(
Cond::equal(
Cond::column($field),
$emailAddress
)
);
$toCheck = true;
}
}
return $toCheck;
}
private function applyFieldPhone(
string $field,
CoreEntity $entity,
OrGroupBuilder $orBuilder
): bool {
$toCheck = false;
if (
($entity->get($field) || $entity->get($field . 'Data')) &&
(
$entity->isNew() ||
$entity->isAttributeChanged($field) ||
$entity->isAttributeChanged($field . 'Data')
)
) {
foreach ($this->getPhoneNumberList($entity) as $phoneNumber) {
$orBuilder->add(
Cond::equal(
Cond::column($field),
$phoneNumber
)
);
$toCheck = true;
}
}
return $toCheck;
}
private function applyFieldVarchar(
string $field,
CoreEntity $entity,
OrGroupBuilder $orBuilder
): bool {
if (!$entity->get($field)) {
return false;
}
$orBuilder->add(
Cond::equal(
Cond::column($field),
$entity->get($field)
),
);
return true;
}
/**
* @return string[]
*/
private function getEmailAddressList(CoreEntity $entity): array
{
if ($entity->get('emailAddressData')) {
/** @var EmailAddressGroup $eaGroup */
$eaGroup = $entity->getValueObject('emailAddress');
return $eaGroup->getAddressList();
}
if ($entity->get('emailAddress')) {
return [
$entity->get('emailAddress')
];
}
return [];
}
/**
* @return string[]
*/
private function getPhoneNumberList(CoreEntity $entity): array
{
if ($entity->get('phoneNumberData')) {
/** @var PhoneNumberGroup $eaGroup */
$eaGroup = $entity->getValueObject('phoneNumber');
return $eaGroup->getNumberList();
}
if ($entity->get('phoneNumber')) {
return [$entity->get('phoneNumber')];
}
return [];
}
}

View File

@@ -29,92 +29,5 @@
namespace Espo\Classes\DuplicateWhereBuilders;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Duplicate\WhereBuilder;
use Espo\Core\Field\EmailAddressGroup;
use Espo\ORM\Entity;
use Espo\ORM\Query\Part\Condition as Cond;
use Espo\ORM\Query\Part\Where\OrGroup;
use Espo\ORM\Query\Part\WhereItem;
/**
* @implements WhereBuilder<CoreEntity>
*/
class Person implements WhereBuilder
{
/**
* @param CoreEntity $entity
*/
public function build(Entity $entity): ?WhereItem
{
$orBuilder = OrGroup::createBuilder();
$toCheck = false;
if ($entity->get('firstName') || $entity->get('lastName')) {
$orBuilder->add(
Cond::and(
Cond::equal(
Cond::column('firstName'),
$entity->get('firstName')
),
Cond::equal(
Cond::column('lastName'),
$entity->get('lastName')
)
)
);
$toCheck = true;
}
if (
($entity->get('emailAddress') || $entity->get('emailAddressData')) &&
(
$entity->isNew() ||
$entity->isAttributeChanged('emailAddress') ||
$entity->isAttributeChanged('emailAddressData')
)
) {
foreach ($this->getEmailAddressList($entity) as $emailAddress) {
$orBuilder->add(
Cond::equal(
Cond::column('emailAddress'),
$emailAddress
)
);
$toCheck = true;
}
}
if (!$toCheck) {
return null;
}
return $orBuilder->build();
}
/**
* @return string[]
*/
private function getEmailAddressList(CoreEntity $entity): array
{
if ($entity->get('emailAddressData')) {
/** @var EmailAddressGroup $eaGroup */
$eaGroup = $entity->getValueObject('emailAddress');
return $eaGroup->getAddressList();
}
if ($entity->get('emailAddress')) {
return [
$entity->get('emailAddress')
];
}
return [];
}
}
class Person extends General
{}

View File

@@ -0,0 +1,199 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldConverters;
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
use Espo\Core\Utils\Database\Orm\FieldConverter;
use Espo\ORM\Defs\FieldDefs;
use Espo\ORM\Type\AttributeType;
use RuntimeException;
class RelationshipRole implements FieldConverter
{
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
{
$name = $fieldDefs->getName();
$attributeDefs = AttributeDefs::create($name)
->withType(AttributeType::VARCHAR)
->withNotStorable();
$attributeDefs = $this->addWhere($attributeDefs, $fieldDefs, $entityType);
return EntityDefs::create()
->withAttribute($attributeDefs);
}
private function addWhere(AttributeDefs $attributeDefs, FieldDefs $fieldDefs, string $entityType): AttributeDefs
{
$data = $fieldDefs->getParam('converterData');
if (!is_array($data)) {
throw new RuntimeException("No `converterData` in field defs.");
}
/** @var ?string $column */
$column = $data['column'] ?? null;
/** @var ?string $link */
$link = $data['link'] ?? null;
/** @var ?string $relationName */
$relationName = $data['relationName'] ?? null;
/** @var ?string $nearKey */
$nearKey = $data['nearKey'] ?? null;
if (!$column || !$link || !$relationName || !$nearKey) {
throw new RuntimeException("Bad `converterData`.");
}
$midTable = ucfirst($relationName);
return $attributeDefs->withParamsMerged([
'where' => [
'=' => [
'whereClause' => [
'id=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
$column => '{value}',
],
],
],
],
'<>' => [
'whereClause' => [
'id!=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
$column => '{value}',
],
],
],
],
'IN' => [
'whereClause' => [
'id=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
$column => '{value}',
],
],
],
],
'NOT IN' => [
'whereClause' => [
'id!=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
$column => '{value}',
],
],
],
],
'LIKE' => [
'whereClause' => [
'id=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
"$column*" => '{value}',
],
],
],
],
'NOT LIKE' => [
'whereClause' => [
'id!=s' => [
'from' => $midTable,
'select' => [$nearKey],
'whereClause' => [
'deleted' => false,
"$column*" => '{value}',
],
],
],
],
'IS NULL' => [
'whereClause' => [
'NOT' => [
'EXISTS' => [
'from' => $entityType,
'fromAlias' => 'sq',
'select' => ['id'],
'leftJoins' => [
[
$link,
'm',
null,
['onlyMiddle' => true]
]
],
'whereClause' => [
"m.$column!=" => null,
'sq.id:' => lcfirst($entityType) . '.id',
],
],
],
],
],
'IS NOT NULL' => [
'whereClause' => [
'EXISTS' => [
'from' => $entityType,
'fromAlias' => 'sq',
'select' => ['id'],
'leftJoins' => [
[
$link,
'm',
null,
['onlyMiddle' => true]
]
],
'whereClause' => [
"m.$column!=" => null,
'sq.id:' => lcfirst($entityType) . '.id',
],
],
],
],
],
]);
}
}

View File

@@ -186,11 +186,11 @@ class ArrayType
{
$maxLength = $validationValue ?? self::DEFAULT_MAX_ITEM_LENGTH;
/** @var string[] $value */
/** @var mixed[] $value */
$value = $entity->get($field) ?? [];
foreach ($value as $item) {
if (mb_strlen($item) > $maxLength) {
if (is_string($item) && mb_strlen($item) > $maxLength) {
return false;
}
}

View File

@@ -115,6 +115,14 @@ class CurrencyType extends FloatType
$currency = $entity->get($attribute);
$currencyList = $this->config->get('currencyList') ?? [$this->config->get('defaultCurrency')];
if (
$currency === null &&
!$entity->has($field) &&
$entity->isNew()
) {
return true;
}
if (
$currency === null &&
$entity->has($field) &&

View File

@@ -95,7 +95,7 @@ class EnumType
$value = $entity->get($field);
// For bc.
// @todo Remove in v8.0.
// @todo Remove in v9.0.
if ($value === '') {
$value = null;
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Classes\FieldValidators;
use Espo\ORM\Entity;
use stdClass;
class IntType
{
@@ -40,6 +41,7 @@ class IntType
/**
* @param mixed $validationValue
* @noinspection PhpUnused
*/
public function checkMax(Entity $entity, string $field, $validationValue): bool
{
@@ -56,6 +58,7 @@ class IntType
/**
* @param mixed $validationValue
* @noinspection PhpUnused
*/
public function checkMin(Entity $entity, string $field, $validationValue): bool
{
@@ -70,6 +73,26 @@ class IntType
return true;
}
/** @noinspection PhpUnused */
public function rawCheckValid(stdClass $data, string $field): bool
{
if (!isset($data->$field)) {
return true;
}
$value = $data->$field;
if ($value === '') {
return true;
}
if (is_numeric($value)) {
return true;
}
return false;
}
protected function isNotEmpty(Entity $entity, string $field): bool
{
return $entity->has($field) && $entity->get($field) !== null;

View File

@@ -258,7 +258,7 @@ class LinkMultipleType
);
// For bc.
// @todo Remove in v8.0.
// @todo Remove in v9.0.
if ($value === '') {
$value = null;
}

View File

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

View File

@@ -37,12 +37,8 @@ use Espo\Entities\User;
class Mandatory implements Filter
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function __construct(private User $user)
{}
public function apply(SelectBuilder $queryBuilder): void
{

View File

@@ -29,9 +29,9 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Container;
use Espo\Core\DataManager;
use Espo\Core\Api\Request;
@@ -44,6 +44,9 @@ use Espo\Entities\User;
class Admin
{
/**
* @throws Forbidden
*/
public function __construct(
private Container $container,
private Config $config,
@@ -53,12 +56,14 @@ class Admin
private ScheduledJob $scheduledJob,
private DataManager $dataManager
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
}
/**
* @throws Error
*/
public function postActionRebuild(): bool
{
$this->dataManager->rebuild();
@@ -66,6 +71,9 @@ class Admin
return true;
}
/**
* @throws Error
*/
public function postActionClearCache(): bool
{
$this->dataManager->clearCache();
@@ -81,24 +89,28 @@ class Admin
return $this->scheduledJob->getAvailableList();
}
/**
* @param array<string, mixed> $params
* @param string $data
* @return array{
* @return object{
* id: string,
* version: string,
* }
* @throws Forbidden
* @throws Error
* @todo Use Request.
* @throws BadRequest
*/
public function postActionUploadUpgradePackage($params, $data): array
public function postActionUploadUpgradePackage(Request $request): object
{
if ($this->config->get('restrictedMode')) {
if (!$this->user->isSuperAdmin()) {
throw new Forbidden();
}
if (
$this->config->get('restrictedMode') &&
!$this->user->isSuperAdmin()
) {
throw new Forbidden();
}
$data = $request->getBodyContents();
if (!$data) {
throw new BadRequest();
}
$upgradeManager = new UpgradeManager($this->container);
@@ -106,7 +118,7 @@ class Admin
$upgradeId = $upgradeManager->upload($data);
$manifest = $upgradeManager->getManifest();
return [
return (object) [
'id' => $upgradeId,
'version' => $manifest['version'],
];
@@ -120,10 +132,11 @@ class Admin
{
$data = $request->getParsedBody();
if ($this->config->get('restrictedMode')) {
if (!$this->user->isSuperAdmin()) {
throw new Forbidden();
}
if (
$this->config->get('restrictedMode') &&
!$this->user->isSuperAdmin()
) {
throw new Forbidden();
}
$upgradeManager = new UpgradeManager($this->container);
@@ -134,33 +147,37 @@ class Admin
}
/**
* @return array{
* message: string,
* command: string,
* @return object{
* message: string,
* command: string,
* }
*/
public function actionCronMessage(): array
public function getActionCronMessage(): object
{
return $this->scheduledJob->getSetupMessage();
return (object) $this->scheduledJob->getSetupMessage();
}
/**
* @return array<int, array{id: string, type: string, message: string}>
* @return array<int, array{
* id: string,
* type: string,
* message: string,
* }>
*/
public function actionAdminNotificationList(): array
public function getActionAdminNotificationList(): array
{
return $this->adminNotificationManager->getNotificationList();
}
/**
* @return array{
* php: array<string, array<string, mixed>>,
* database: array<string, array<string, mixed>>,
* permission: array<string, array<string, mixed>>,
* @return object{
* php: array<string, array<string, mixed>>,
* database: array<string, array<string, mixed>>,
* permission: array<string, array<string, mixed>>,
* }
*/
public function actionSystemRequirementList(): array
public function getActionSystemRequirementList(): object
{
return $this->systemRequirements->getAllRequiredList();
return (object) $this->systemRequirements->getAllRequiredList();
}
}

View File

@@ -78,9 +78,15 @@ class EmailFolder extends RecordBase
return true;
}
public function getActionListAll(): stdClass
/**
* @throws Forbidden
* @throws NotFound
*/
public function getActionListAll(Request $request): stdClass
{
$list = $this->getEmailFolderService()->listAll();
$userId = $request->getQueryParam('userId');
$list = $this->getEmailFolderService()->listAll($userId);
return (object) ['list' => $list];
}

View File

@@ -31,12 +31,19 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\Entities\User;
use Espo\Tools\EntityManager\EntityManager as EntityManagerTool;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Tools\ExportCustom\ExportCustom;
use Espo\Tools\ExportCustom\Params as ExportCustomParams;
use Espo\Tools\ExportCustom\Service as ExportCustomService;
use Espo\Tools\LinkManager\LinkManager;
use stdClass;
use const FILTER_SANITIZE_STRING;
class EntityManager
{
@@ -45,9 +52,10 @@ class EntityManager
*/
public function __construct(
private User $user,
private EntityManagerTool $entityManagerTool
private EntityManagerTool $entityManagerTool,
private LinkManager $linkManager,
private InjectableFactory $injectableFactory
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
@@ -71,8 +79,8 @@ class EntityManager
$name = $data['name'];
$type = $data['type'];
$name = filter_var($name, \FILTER_SANITIZE_STRING);
$type = filter_var($type, \FILTER_SANITIZE_STRING);
$name = filter_var($name, FILTER_SANITIZE_STRING);
$type = filter_var($type, FILTER_SANITIZE_STRING);
if (!is_string($name) || !is_string($type)) {
throw new BadRequest();
@@ -155,7 +163,7 @@ class EntityManager
$name = $data['name'];
$name = filter_var($name, \FILTER_SANITIZE_STRING);
$name = filter_var($name, FILTER_SANITIZE_STRING);
if (!is_string($name)) {
throw new BadRequest();
@@ -183,7 +191,7 @@ class EntityManager
$name = $data['name'];
$name = filter_var($name, \FILTER_SANITIZE_STRING);
$name = filter_var($name, FILTER_SANITIZE_STRING);
if (!is_string($name)) {
throw new BadRequest();
@@ -226,11 +234,11 @@ class EntityManager
throw new BadRequest();
}
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
}
foreach ($additionalParamList as $item) {
$params[$item] = filter_var($data[$item] ?? null, \FILTER_SANITIZE_STRING);
$params[$item] = filter_var($data[$item] ?? null, FILTER_SANITIZE_STRING);
}
$params['labelForeign'] = $params['labelForeign'] ?? $params['linkForeign'];
@@ -259,6 +267,14 @@ class EntityManager
$params['foreignLinkEntityTypeList'] = $data['foreignLinkEntityTypeList'];
}
if (array_key_exists('layout', $data)) {
$params['layout'] = $data['layout'];
}
if (array_key_exists('layoutForeign', $data)) {
$params['layoutForeign'] = $data['layoutForeign'];
}
/** @var array{
* linkType: string,
* entity: string,
@@ -272,14 +288,20 @@ class EntityManager
* linkMultipleFieldForeign?: bool,
* audited?: bool,
* auditedForeign?: bool,
* layout?: string,
* layoutForeign?: string,
* } $params
*/
$this->entityManagerTool->createLink($params);
$this->linkManager->create($params);
return true;
}
/**
* @throws BadRequest
* @throws Error
*/
public function postActionUpdateLink(Request $request): bool
{
$data = $request->getParsedBody();
@@ -299,7 +321,7 @@ class EntityManager
foreach ($paramList as $item) {
if (array_key_exists($item, $data)) {
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
}
}
@@ -326,6 +348,14 @@ class EntityManager
$params['foreignLinkEntityTypeList'] = $data['foreignLinkEntityTypeList'];
}
if (array_key_exists('layout', $data)) {
$params['layout'] = $data['layout'];
}
if (array_key_exists('auditedForeign', $data)) {
$params['layoutForeign'] = $data['layoutForeign'];
}
/**
* @var array{
* entity: string,
@@ -340,14 +370,20 @@ class EntityManager
* auditedForeign?: bool,
* parentEntityTypeList?: string[],
* foreignLinkEntityTypeList?: string[],
* layout?: string,
* layoutForeign?: string,
* } $params
*/
$this->entityManagerTool->updateLink($params);
$this->linkManager->update($params);
return true;
}
/**
* @throws BadRequest
* @throws Error
*/
public function postActionRemoveLink(Request $request): bool
{
$data = $request->getParsedBody();
@@ -362,7 +398,7 @@ class EntityManager
$params = [];
foreach ($paramList as $item) {
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
$params[$item] = filter_var($data[$item], FILTER_SANITIZE_STRING);
}
/**
@@ -372,7 +408,7 @@ class EntityManager
* } $params
*/
$this->entityManagerTool->deleteLink($params);
$this->linkManager->delete($params);
return true;
}
@@ -400,6 +436,29 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
*/
public function postActionResetFormulaToDefault(Request $request): bool
{
$data = $request->getParsedBody();
$scope = $data->scope ?? null;
$type = $data->type ?? null;
if (!$scope || !$type) {
throw new BadRequest();
}
$this->entityManagerTool->resetFormulaToDefault($scope, $type);
return true;
}
/**
* @throws BadRequest
* @throws Error
*/
public function postActionResetToDefault(Request $request): bool
{
$data = $request->getParsedBody();
@@ -412,4 +471,45 @@ class EntityManager
return true;
}
/**
* @throws BadRequest
*/
public function postActionExportCustom(Request $request): stdClass
{
$data = $request->getParsedBody();
$name = $data->name ?? null;
$version = $data->version ?? null;
$author = $data->author ?? null;
$module = $data->module ?? null;
$description = $data->description ?? null;
if (
!is_string($name) ||
!is_string($version) ||
!is_string($author) ||
!is_string($module) ||
!is_string($description) && !is_null($description)
) {
throw new BadRequest();
}
$params = new ExportCustomParams(
name: $name,
module: $module,
version: $version,
author: $author,
description: $description
);
$export = $this->injectableFactory->create(ExportCustom::class);
$service = $this->injectableFactory->create(ExportCustomService::class);
$service->storeToConfig($params);
$result = $export->process($params);
return (object) ['id' => $result->getAttachmentId()];
}
}

View File

@@ -29,25 +29,26 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\InjectableFactory;
use Espo\Tools\Layout\CustomLayoutService;
use Espo\Tools\Layout\LayoutDefs;
use Espo\Tools\Layout\Service as Service;
use Espo\Entities\User;
use stdClass;
class Layout
{
private User $user;
private Service $service;
public function __construct(User $user, Service $service)
{
$this->user = $user;
$this->service = $service;
}
public function __construct(
private User $user,
private Service $service,
private InjectableFactory $injectableFactory
) {}
/**
* @return mixed
@@ -103,7 +104,7 @@ class Layout
}
/**
* @return mixed
* @return array<int, mixed>|stdClass|null
* @throws Forbidden
* @throws BadRequest
* @throws NotFound
@@ -125,7 +126,7 @@ class Layout
}
/**
* @return mixed
* @return array<int, mixed>|stdClass|null
* @throws BadRequest
* @throws Forbidden
* @throws NotFound
@@ -147,4 +148,75 @@ class Layout
return $this->service->getOriginal($scope, $name, $setId);
}
/**
* @throws Forbidden
* @throws BadRequest
* @throws Conflict
*/
public function postActionCreate(Request $request): bool
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
$body = $request->getParsedBody();
$scope = $body->scope ?? null;
$name = $body->name ?? null;
$type = $body->type ?? null;
$label = $body->label ?? null;
if (
!is_string($scope) ||
!is_string($name) ||
!is_string($type) ||
!is_string($label) ||
!$scope ||
!$name ||
!$type ||
!$label
) {
throw new BadRequest();
}
$defs = new LayoutDefs($scope, $name, $type, $label);
$service = $this->injectableFactory->create(CustomLayoutService::class);
$service->create($defs);
return true;
}
/**
* @throws Forbidden
* @throws BadRequest
*/
public function postActionDelete(Request $request): bool
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
$body = $request->getParsedBody();
$scope = $body->scope ?? null;
$name = $body->name ?? null;
if (
!is_string($scope) ||
!is_string($name) ||
!$scope ||
!$name
) {
throw new BadRequest();
}
$service = $this->injectableFactory->create(CustomLayoutService::class);
$service->delete($scope, $name);
return true;
}
}

View File

@@ -31,6 +31,7 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\TemplateFileManager;
use Espo\Core\ApplicationState;
@@ -38,16 +39,20 @@ use Espo\Core\Api\Request;
use stdClass;
/**
* @noinspection PhpUnused
* @todo Move to a service class.
*/
class TemplateManager
{
/**
* @throws Forbidden
*/
public function __construct(
private Metadata $metadata,
private TemplateFileManager $templateFileManager,
private ApplicationState $applicationState
private ApplicationState $applicationState,
private Config $config
) {
if (!$this->applicationState->isAdmin()) {
@@ -55,6 +60,9 @@ class TemplateManager
}
}
/**
* @throws BadRequest
*/
public function getActionGetTemplate(Request $request): stdClass
{
$name = $request->getQueryParam('name');
@@ -66,7 +74,6 @@ class TemplateManager
$scope = $request->getQueryParam('scope');
$module = $this->metadata->get(['app', 'templates', $name, 'module']);
$hasSubject = !$this->metadata->get(['app', 'templates', $name, 'noSubject']);
$templateFileManager = $this->templateFileManager;
@@ -82,6 +89,10 @@ class TemplateManager
return $returnData;
}
/**
* @throws BadRequest
* @throws Forbidden
*/
public function postActionSaveTemplate(Request $request): bool
{
$data = $request->getParsedBody();
@@ -89,9 +100,18 @@ class TemplateManager
$scope = null;
if (empty($data->name)) {
/** @noinspection PhpUnhandledExceptionInspection */
throw new BadRequest();
}
if (
$data->name === 'passwordChangeLink' &&
$this->config->get('restrictedMode') &&
!$this->applicationState->getUser()->isSuperAdmin()
) {
throw new Forbidden();
}
if (!empty($data->scope)) {
$scope = $data->scope;
}
@@ -109,6 +129,9 @@ class TemplateManager
return true;
}
/**
* @throws BadRequest
*/
public function postActionResetTemplate(Request $request): stdClass
{
$data = $request->getParsedBody();
@@ -124,7 +147,6 @@ class TemplateManager
}
$module = $this->metadata->get(['app', 'templates', $data->name, 'module']);
$hasSubject = !$this->metadata->get(['app', 'templates', $data->name, 'noSubject']);
$templateFileManager = $this->templateFileManager;

View File

@@ -27,9 +27,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\EntityManager\Hooks;
namespace Espo\Core\Acl\Exceptions;
class PersonType extends BasePlusType
{
use RuntimeException;
}
class NotAvailable extends RuntimeException {}

View File

@@ -35,10 +35,10 @@ use Espo\Core\Action\Params;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Record\ActionHistory\Action;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\ObjectUtil;
use Espo\Entities\ActionHistoryRecord;
use Espo\ORM\Entity;
use Espo\Entities\EmailAddress;
use Espo\Entities\PhoneNumber;
@@ -82,6 +82,8 @@ class Merger
$entity->set($clonedData);
$this->unsetNotActualAttributes($entity);
if (!$service->checkAssignment($entity)) {
throw new Forbidden("Assignment permission failure.");
}
@@ -135,7 +137,7 @@ class Merger
foreach ($sourceEntityList as $sourceEntity) {
$this->entityManager->removeEntity($sourceEntity);
$service->processActionHistoryRecord(ActionHistoryRecord::ACTION_DELETE, $sourceEntity);
$service->processActionHistoryRecord(Action::DELETE, $sourceEntity);
}
if ($hasPhoneNumber) {
@@ -150,7 +152,7 @@ class Merger
$this->entityManager->saveEntity($entity);
$service->processActionHistoryRecord(ActionHistoryRecord::ACTION_UPDATE, $entity);
$service->processActionHistoryRecord(Action::UPDATE, $entity);
}
/**
@@ -364,4 +366,20 @@ class Merger
$data->emailAddressData = $emailAddressData;
}
private function unsetNotActualAttributes(Entity $entity): void
{
$fieldDefsList = $this->entityManager
->getDefs()
->getEntity($entity->getEntityType())
->getFieldList();
foreach ($fieldDefsList as $fieldDefs) {
$field = $fieldDefs->getName();
if ($fieldDefs->getType() === 'link' && $entity->isAttributeChanged($field . 'Id')) {
$entity->clear($field . 'Name');
}
}
}
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Core\Api;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
@@ -47,6 +48,7 @@ interface Action
* @throws BadRequest
* @throws Forbidden
* @throws NotFound
* @throws Conflict
* @throws Error
*/
public function process(Request $request): Response;

View File

@@ -80,7 +80,7 @@ class ControllerActionProcessor
if (!method_exists($controller, $primaryActionMethodName)) {
throw new NotFoundSilent(
"Action {$requestMethod} '{$actionName}' does not exist in controller '{$controllerName}'.");
"Action $requestMethod '$actionName' does not exist in controller '$controllerName'.");
}
if ($this->useShortParamList($controller, $primaryActionMethodName)) {
@@ -187,11 +187,11 @@ class ControllerActionProcessor
$className = $this->classFinder->find('Controllers', $name);
if (!$className) {
throw new NotFound("Controller '{$name}' does not exist.");
throw new NotFound("Controller '$name' does not exist.");
}
if (!class_exists($className)) {
throw new NotFound("Class not found for controller '{$name}'.");
throw new NotFound("Class not found for controller '$name'.");
}
return $className;

View File

@@ -144,14 +144,17 @@ class Utils
* Normalize options to LDAP client format
*
* @param array<string, mixed> $options
*
* @return array<string, mixed>
*/
public function normalizeOptions(array $options): array
{
$options['useSsl'] = (bool) ($options['useSsl'] == 'SSL');
$options['useStartTls'] = (bool) ($options['useStartTls'] == 'TLS');
$options['accountCanonicalForm'] = $this->accountCanonicalFormMap[$options['accountCanonicalForm']];
$useSsl = ($options['useSsl'] ?? null) == 'SSL';
$useStartTls = ($options['useStartTls'] ?? null) == 'TLS';
$accountCanonicalFormKey = $options['accountCanonicalForm'] ?? 'Dn';
$options['useSsl'] = $useSsl;
$options['useStartTls'] = $useStartTls;
$options['accountCanonicalForm'] = $this->accountCanonicalFormMap[$accountCanonicalFormKey] ?? 1;
return $options;
}

View File

@@ -112,6 +112,7 @@ class KeysProvider
CURLOPT_TIMEOUT => self::REQUEST_TIMEOUT,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
]);
/** @var string|false $response */

View File

@@ -229,6 +229,7 @@ class Login implements LoginInterface
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_HTTPHEADER => ['content-type: application/x-www-form-urlencoded'],
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
]);
/** @var string|false $response */

View File

@@ -121,11 +121,9 @@ class BindingContainer
$key &&
$this->data->hasContext($className, $key)
) {
// @todo For v7.6. Uncomment, then remove the return statement below.
/*$binding = $this->data->getContext($className, $key);
$binding = $this->data->getContext($className, $key);
$notMatching =
$type &&
$type instanceof ReflectionNamedType &&
!$type->isBuiltin() &&
$binding->getType() === Binding::VALUE &&
@@ -133,15 +131,12 @@ class BindingContainer
if (!$notMatching) {
return $binding;
}*/
return $this->data->getContext($className, $key);
}
}
$dependencyClassName = null;
if (
$type &&
$type instanceof ReflectionNamedType &&
!$type->isBuiltin()
) {

View File

@@ -53,7 +53,7 @@ class Help implements Command
$commandList = array_filter(
$fullCommandList,
function ($item): bool {
return (bool) $this->metadata->get(['app', 'consoleCommands', $item]);
return (bool) $this->metadata->get(['app', 'consoleCommands', $item, 'listed']);
}
);

View File

@@ -66,7 +66,7 @@ class ContainerConfiguration implements Configuration
if (!$className) {
/** @deprecated */
/** @todo Remove in 8.0. */
/** @todo Remove in v9.0. */
$className = $this->metadata->get(['app', 'loaders', ucfirst($name)]);
}
} catch (Exception) {}

View File

@@ -48,6 +48,8 @@ class Record extends RecordBase
* @throws BadRequest
* @throws NotFound
* @throws Forbidden
* @throws Error
* @noinspection PhpUnused
*/
public function getActionListLinked(Request $request): stdClass
{
@@ -167,6 +169,7 @@ class Record extends RecordBase
* @throws BadRequest
* @throws NotFoundSilent
* @throws Forbidden
* @noinspection PhpUnused
*/
public function putActionFollow(Request $request): bool
{
@@ -186,6 +189,7 @@ class Record extends RecordBase
*
* @throws NotFoundSilent
* @throws BadRequest
* @noinspection PhpUnused
*/
public function deleteActionUnfollow(Request $request): bool
{

View File

@@ -40,6 +40,7 @@ use Espo\Core\Api\ErrorOutput;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Api\ResponseWrapper;
use Espo\Core\Api\AuthBuilderFactory;
use Espo\Core\Portal\Utils\Url;
use Espo\Core\Utils\Route;
use Espo\Core\Utils\ClientManager;
use Espo\Core\ApplicationRunners\EntryPoint as EntryPointRunner;
@@ -87,6 +88,14 @@ class Starter
throw new BadRequest("No 'entryPoint' param.");
}
$portalId = Url::getPortalIdFromEnv();
if ($portalId && !$final) {
$this->runThroughPortal($portalId, $entryPoint);
return;
}
$responseWrapped = new ResponseWrapper(new Response());
try {
@@ -206,7 +215,11 @@ class Starter
{
$app = new PortalApplication($portalId);
$app->setClientBasePath($this->clientManager->getBasePath());
$clientManager = $app->getContainer()
->getByClass(ClientManager::class);
$clientManager->setBasePath($this->clientManager->getBasePath());
$clientManager->setApiUrl('api/v1/portal-access/' . $portalId);
$params = RunnerParams::fromArray([
'entryPoint' => $entryPoint,

View File

@@ -30,6 +30,7 @@
namespace Espo\Core\Field\Currency;
/**
* @deprecated Since v7.1.0. Use `Espo\Core\Currency\Converter`.
* @deprecated As of v7.1.0. Use `Espo\Core\Currency\Converter`.
* @todo Remove in v9.0.
*/
class CurrencyConverter extends \Espo\Core\Currency\Converter {}

View File

@@ -416,9 +416,8 @@ class Saver implements SaverInterface
if ($emailAddressOld) {
$this->entityManager
->getRDBRepository($entity->getEntityType())
->unrelate($entity, 'emailAddresses', $emailAddressOld, [
SaveOption::SKIP_HOOKS => true,
]);
->getRelation($entity, 'emailAddresses')
->unrelate($emailAddressOld, [SaveOption::SKIP_HOOKS => true]);
}
}
}
@@ -490,15 +489,15 @@ class Saver implements SaverInterface
$emailAddressOld = $this->getByAddress($emailAddressValueOld);
if ($emailAddressOld) {
$entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld, [
SaveOption::SKIP_HOOKS => true,
]);
$entityRepository
->getRelation($entity, 'emailAddresses')
->unrelate($emailAddressOld, [SaveOption::SKIP_HOOKS => true]);
}
}
$entityRepository->relate($entity, 'emailAddresses', $emailAddressNew, null, [
SaveOption::SKIP_HOOKS => true,
]);
$entityRepository
->getRelation($entity, 'emailAddresses')
->relate($emailAddressNew, null, [SaveOption::SKIP_HOOKS => true]);
if ($entity->has('emailAddressIsOptedOut')) {
$this->markAddressOptedOut($emailAddressValue, (bool) $entity->get('emailAddressIsOptedOut'));

View File

@@ -411,7 +411,7 @@ class Saver implements SaverInterface
return;
}
$phoneNumberValue = trim($entity->get('phoneNumber'));
$phoneNumberValue = trim($entity->get('phoneNumber') ?? '');
$entityRepository = $this->entityManager->getRDBRepository($entity->getEntityType());
@@ -425,8 +425,6 @@ class Saver implements SaverInterface
])
->findOne();
$isNewPhoneNumber = false;
if (!$phoneNumberNew) {
$phoneNumberNew = $this->entityManager->getNewEntity(PhoneNumber::ENTITY_TYPE);
@@ -446,8 +444,6 @@ class Saver implements SaverInterface
$phoneNumberNew->set('type', $defaultType);
$this->entityManager->saveEntity($phoneNumberNew);
$isNewPhoneNumber = true;
}
$phoneNumberValueOld = $entity->getFetched('phoneNumber');
@@ -456,15 +452,15 @@ class Saver implements SaverInterface
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
if ($phoneNumberOld) {
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld, [
SaveOption::SKIP_HOOKS => true,
]);
$entityRepository
->getRelation($entity, 'phoneNumbers')
->unrelate($phoneNumberOld, [SaveOption::SKIP_HOOKS => true]);
}
}
$entityRepository->relate($entity, 'phoneNumbers', $phoneNumberNew, null, [
SaveOption::SKIP_HOOKS => true,
]);
$entityRepository
->getRelation($entity, 'phoneNumbers')
->relate($phoneNumberNew, null, [SaveOption::SKIP_HOOKS => true]);
if ($entity->has('phoneNumberIsOptedOut')) {
$this->markNumberOptedOut($phoneNumberValue, (bool) $entity->get('phoneNumberIsOptedOut'));
@@ -527,9 +523,9 @@ class Saver implements SaverInterface
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
if ($phoneNumberOld) {
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld, [
SaveOption::SKIP_HOOKS => true,
]);
$entityRepository
->getRelation($entity, 'phoneNumbers')
->unrelate($phoneNumberOld, [SaveOption::SKIP_HOOKS => true]);
}
}
}

View File

@@ -138,11 +138,12 @@ class FieldValidationManager
*/
private function getMandatoryValidationList(string $entityType, string $field): array
{
/** @var ?string $fieldType */
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, 'type');
return
$this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'mandatoryValidationList']) ??
$this->metadata->get(['fields', $fieldType, 'mandatoryValidationList']) ?? [];
$this->metadata->get(['fields', $fieldType ?? '', 'mandatoryValidationList']) ?? [];
}
/**
@@ -150,11 +151,12 @@ class FieldValidationManager
*/
private function getValidationList(string $entityType, string $field): array
{
/** @var ?string $fieldType */
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, 'type');
return
$this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'validationList']) ??
$this->metadata->get(['fields', $fieldType, 'validationList']) ?? [];
$this->metadata->get(['fields', $fieldType ?? '', 'validationList']) ?? [];
}
/**
@@ -248,17 +250,7 @@ class FieldValidationManager
bool $throw
): array {
$entityType = $entity->getEntityType();
$validationList = array_unique(array_merge(
$this->getValidationList($entityType, $field),
$this->getMandatoryValidationList($entityType, $field)
));
$validationList = array_filter(
$validationList,
fn ($type) => !in_array($field, $params->getTypeSkipFieldList($type))
);
$validationList = $this->getAllValidationList($entity->getEntityType(), $field, $params);
$failureList = [];
@@ -269,7 +261,7 @@ class FieldValidationManager
continue;
}
$failure = new Failure($entityType, $field, $type);
$failure = new Failure($entity->getEntityType(), $field, $type);
$failureList[] = $failure;
@@ -287,6 +279,32 @@ class FieldValidationManager
return array_merge($failureList, $additionalFailureList);
}
/**
* @return string[]
*/
private function getAllValidationList(string $entityType, string $field, FieldValidationParams $params): array
{
$validationList = array_unique(array_merge(
$this->getValidationList($entityType, $field),
$this->getMandatoryValidationList($entityType, $field)
));
/** @var string[] $suppressList */
$suppressList = $this->metadata->get("entityDefs.$entityType.fields.$field.suppressValidationList") ?? [];
$validationList = array_filter(
$validationList,
fn ($type) => !in_array($type, $suppressList)
);
$validationList = array_filter(
$validationList,
fn ($type) => !in_array($field, $params->getTypeSkipFieldList($type))
);
return array_values($validationList);
}
/**
* @param mixed $validationValue
*/

View File

@@ -31,6 +31,7 @@ namespace Espo\Core\FieldValidation;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\FieldUtil;
use Espo\ORM\Entity;
use RuntimeException;
@@ -40,7 +41,8 @@ class ValidatorFactory
public function __construct(
private InjectableFactory $injectableFactory,
private Metadata $metadata
private Metadata $metadata,
private FieldUtil $fieldUtil
) {}
public function isCreatable(string $entityType, string $field, string $type): bool
@@ -67,9 +69,12 @@ class ValidatorFactory
*/
private function getClassName(string $entityType, string $field, string $type): ?string
{
/** @var ?string $fieldType */
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, 'type');
return
$this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'validatorClassNameMap', $type]) ??
$this->metadata->get(['fields', $field, 'validatorClassNameMap', $type]);
$this->metadata->get(['fields', $fieldType ?? '', 'validatorClassNameMap', $type]);
}
/**

View File

@@ -30,7 +30,7 @@
namespace Espo\Core\Formula\Exceptions;
/**
* Too few function arguments passsed.
* Too few function arguments passed.
*/
class TooFewArguments extends Error
{

View File

@@ -29,26 +29,31 @@
namespace Espo\Core\Formula\Functions\JsonGroup;
use Espo\Core\Formula\{
Functions\BaseFunction,
ArgumentList,
};
use Espo\Core\Formula\ArgumentList;
use Espo\Core\Formula\Exceptions\Error;
use Espo\Core\Formula\Exceptions\ExecutionException;
use Espo\Core\Formula\Exceptions\TooFewArguments;
use Espo\Core\Formula\Functions\BaseFunction;
class RetrieveType extends BaseFunction
{
/**
* @return mixed
* @throws \Espo\Core\Formula\Exceptions\TooFewArguments
* @throws \Espo\Core\Formula\Exceptions\Error
* @throws TooFewArguments
* @throws Error
* @throws ExecutionException
*/
public function process(ArgumentList $args)
{
if (count($args) < 2) {
if (count($args) < 1) {
$this->throwTooFewArguments();
}
$jsonString = $this->evaluate($args[0]);
$path = $this->evaluate($args[1]);
$path = count($args) > 1 ?
$this->evaluate($args[1]) :
'';
if (!is_string($jsonString)) {
$this->throwBadArgumentType(1, 'string');
@@ -58,10 +63,6 @@ class RetrieveType extends BaseFunction
$this->throwBadArgumentType(2, 'string');
}
if ($path === '') {
$this->throwBadArgumentValue(2);
}
$item = json_decode($jsonString);
$pathArray = $this->splitPath($path);
@@ -75,6 +76,10 @@ class RetrieveType extends BaseFunction
*/
private function splitPath(string $path): array
{
if ($path === '') {
return [];
}
/** @var string[] $pathArray */
$pathArray = preg_split('/(?<!\\\)\./', $path);

View File

@@ -0,0 +1,146 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Formula\Functions\RecordGroup;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Formula\EvaluatedArgumentList;
use Espo\Core\Formula\Exceptions\BadArgumentType;
use Espo\Core\Formula\Exceptions\Error as FormulaError;
use Espo\Core\Formula\Exceptions\TooFewArguments;
use Espo\Core\Formula\Func;
use Espo\Core\Select\SelectBuilderFactory;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Order;
class FindManyType implements Func
{
public function __construct(
private EntityManager $entityManager,
private SelectBuilderFactory $selectBuilderFactory
) {}
/**
* @return string[]
* @inheritDoc
*/
public function process(EvaluatedArgumentList $arguments): array
{
if (count($arguments) < 4) {
throw TooFewArguments::create(4);
}
$entityType = $arguments[0];
$limit = $arguments[1];
$orderBy = $arguments[2];
$order = $arguments[3] ?? Order::ASC;
if (!is_string($entityType)) {
throw BadArgumentType::create(1, 'string');
}
if (!is_int($limit)) {
throw BadArgumentType::create(2, 'int');
}
if ($orderBy !== null && !is_string($orderBy)) {
throw BadArgumentType::create(3, 'string|null');
}
if (!is_bool($order) && !is_string($orderBy)) {
throw BadArgumentType::create(4, 'string|bool');
}
$builder = $this->selectBuilderFactory
->create()
->from($entityType);
$whereClause = [];
if (count($arguments) <= 5) {
$filter = null;
if (count($arguments) === 5) {
$filter = $arguments[4];
}
if ($filter && !is_string($filter)) {
throw BadArgumentType::create(5, 'string');
}
if ($filter) {
$builder->withPrimaryFilter($filter);
}
}
else {
$i = 4;
while ($i < count($arguments) - 1) {
$key = $arguments[$i];
$value = $arguments[$i + 1];
$whereClause[] = [$key => $value];
$i = $i + 2;
}
}
try {
$queryBuilder = $builder->buildQueryBuilder();
}
catch (BadRequest|Error|Forbidden $e) {
throw new FormulaError($e->getMessage(), $e->getCode(), $e);
}
if (!empty($whereClause)) {
$queryBuilder->where($whereClause);
}
if ($orderBy) {
$queryBuilder->order($orderBy, $order);
}
$queryBuilder
->select(['id'])
->limit(0, $limit);
$collection = $this->entityManager
->getRDBRepository($entityType)
->clone($queryBuilder->build())
->find();
return array_map(
fn (Entity $entity) => $entity->getId(),
iterator_to_array($collection)
);
}
}

View File

@@ -29,11 +29,12 @@
namespace Espo\Core\Formula\Functions\RecordGroup;
use Espo\Core\Formula\{
Functions\BaseFunction,
ArgumentList,
};
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Formula\ArgumentList;
use Espo\Core\Formula\Exceptions\Error as FormulaError;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\Core\Di;
class FindOneType extends BaseFunction implements
@@ -87,7 +88,12 @@ class FindOneType extends BaseFunction implements
}
}
$queryBuilder = $builder->buildQueryBuilder();
try {
$queryBuilder = $builder->buildQueryBuilder();
}
catch (BadRequest|Error|Forbidden $e) {
throw new FormulaError($e->getMessage(), $e->getCode(), $e);
}
if (!empty($whereClause)) {
$queryBuilder->where($whereClause);

View File

@@ -30,12 +30,8 @@
namespace Espo\Core\Formula\Functions\RecordGroup;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Formula\{
Functions\BaseFunction,
ArgumentList,
};
use Espo\Core\Formula\ArgumentList;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\Core\Di;
class FindRelatedManyType extends BaseFunction implements
@@ -98,7 +94,7 @@ class FindRelatedManyType extends BaseFunction implements
$entity = $entityManager->getEntity($entityType, $id);
if (!$entity) {
$this->log("record\\findRelatedMany: Entity {$entity} {$id} not found.", 'notice');
$this->log("record\\findRelatedMany: Entity $entityType $id not found.", 'notice');
return [];
}
@@ -123,19 +119,19 @@ class FindRelatedManyType extends BaseFunction implements
$relationType = $entity->getRelationParam($link, 'type');
if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) {
$this->throwError("Not supported link type '{$relationType}'.");
$this->throwError("Not supported link type '$relationType'.");
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
$this->throwError("Bad or not supported link '{$link}'.");
$this->throwError("Bad or not supported link '$link'.");
}
$foreignLink = $entity->getRelationParam($link, 'foreign');
if (!$foreignLink) {
$this->throwError("Not supported link '{$link}'.");
$this->throwError("Not supported link '$link'.");
}
$builder = $this->selectBuilderFactory

View File

@@ -171,7 +171,7 @@ class InjectableFactory
$obj = $class->newInstanceArgs($injectionList);
// @todo Remove in 8.0.
// @todo Remove in v9.0.
if ($class->implementsInterface(Injectable::class)) {
$this->applyInjectable($class, $obj);
@@ -476,7 +476,7 @@ class InjectableFactory
/**
* @deprecated
* @param ReflectionClass<object> $class
* @todo Remove in 8.0.
* @todo Remove in v9.0.
*/
private function applyInjectable(ReflectionClass $class, object $obj): void
{

View File

@@ -212,9 +212,12 @@ class JobRunner
private function runJobWithClassName(JobEntity $jobEntity): void
{
/** @var class-string<Job|JobDataLess> $className */
$className = $jobEntity->getClassName();
if (!$className) {
throw new RuntimeException("No className in job {$jobEntity->getId()}.");
}
$job = $this->jobFactory->createByClassName($className);
$this->runJob($job, $jobEntity);
@@ -222,9 +225,16 @@ class JobRunner
/**
* @param Job|JobDataLess $job
* @internal Native type is not used for bc.
*/
private function runJob($job, JobEntity $jobEntity): void
{
if ($job instanceof JobDataLess) {
$job->run();
return;
}
$data = Data::create($jobEntity->getData())
->withTargetId($jobEntity->getTargetId())
->withTargetType($jobEntity->getTargetType());

View File

@@ -60,7 +60,7 @@ class JobScheduler
/**
* A class name of the job. Should implement the `Job` interface.
*
* @param class-string<Job> $className
* @param class-string<Job|JobDataLess> $className
*/
public function setClassName(string $className): self
{
@@ -70,8 +70,11 @@ class JobScheduler
$class = new ReflectionClass($className);
if (!$class->implementsInterface(Job::class)) {
throw new RuntimeException("Class '{$className}' does not implement 'Job' interface.");
if (
!$class->implementsInterface(Job::class) &&
!$class->implementsInterface(JobDataLess::class)
) {
throw new RuntimeException("Class '{$className}' does not implement 'Job' or 'JobDataLess' interface.");
}
$this->className = $className;

View File

@@ -29,17 +29,15 @@
namespace Espo\Core\Mail\Parsers;
use Psr\Http\Message\StreamInterface;
use Espo\Entities\Email;
use Espo\Entities\Attachment;
use Espo\ORM\EntityManager;
use Espo\Core\Mail\Message;
use Espo\Core\Mail\Parser;
use Espo\Core\Mail\Message\Part;
use Espo\Core\Mail\Message\MailMimeParser\Part as WrapperPart;
use Espo\ORM\EntityManager;
use Psr\Http\Message\StreamInterface;
use ZBateson\MailMimeParser\Header\AddressHeader;
use ZBateson\MailMimeParser\MailMimeParser as WrappeeParser;
@@ -63,20 +61,20 @@ class MailMimeParser implements Parser
'webp' => 'image/webp',
];
private EntityManager $entityManager;
private ?WrappeeParser $parser = null;
/**
* @var array<string, ParserMessage>
*/
private const FIELD_BODY = 'body';
private const FIELD_ATTACHMENTS = 'attachments';
private const DISPOSITION_INLINE = 'inline';
/** @var array<string, ParserMessage> */
private array $messageHash = [];
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function __construct(private EntityManager $entityManager)
{}
protected function getParser(): WrappeeParser
private function getParser(): WrappeeParser
{
if (!$this->parser) {
$this->parser = new WrappeeParser();
@@ -85,7 +83,7 @@ class MailMimeParser implements Parser
return $this->parser;
}
protected function loadContent(Message $message): void
private function loadContent(Message $message): void
{
$raw = $message->getFullRawContent();
@@ -98,7 +96,7 @@ class MailMimeParser implements Parser
/**
* @return ParserMessage
*/
protected function getMessage(Message $message)
private function getMessage(Message $message)
{
$key = spl_object_hash($message);
@@ -291,7 +289,7 @@ class MailMimeParser implements Parser
$attachmentPartList = $this->getMessage($message)->getAllAttachmentParts();
$inlineIds = [];
$inlineAttachmentMap = [];
foreach ($attachmentPartList as $attachmentPart) {
if (!$attachmentPart instanceof MimePart) {
@@ -334,54 +332,73 @@ class MailMimeParser implements Parser
$contentId = trim($contentId, '<>');
}
if ($disposition === 'inline') {
if ($disposition === self::DISPOSITION_INLINE) {
$attachment->setRole(Attachment::ROLE_INLINE_ATTACHMENT);
$attachment->setTargetField('body');
$attachment->setTargetField(self::FIELD_BODY);
}
else {
$disposition = 'attachment';
$attachment->setRole(Attachment::ROLE_ATTACHMENT);
$attachment->setTargetField('attachments');
$attachment->setTargetField(self::FIELD_ATTACHMENTS);
}
$attachment->setContents($content);
$this->entityManager->saveEntity($attachment);
if ($disposition === 'attachment') {
$email->addLinkMultipleId('attachments', $attachment->getId());
if ($attachment->getRole() === Attachment::ROLE_ATTACHMENT) {
$email->addLinkMultipleId(self::FIELD_ATTACHMENTS, $attachment->getId());
if ($contentId) {
$inlineIds[$contentId] = $attachment->getId();
$inlineAttachmentMap[$contentId] = $attachment;
}
continue;
}
// inline disposition
// Inline disposition.
if ($contentId) {
$inlineIds[$contentId] = $attachment->getId();
$inlineAttachmentMap[$contentId] = $attachment;
$inlineAttachmentList[] = $attachment;
continue;
}
$email->addLinkMultipleId('attachments', $attachment->getId());
// No ID found, fallback to attachment.
$attachment
->setRole(Attachment::ROLE_ATTACHMENT)
->setTargetField(self::FIELD_ATTACHMENTS);
$this->entityManager->saveEntity($attachment);
$email->addLinkMultipleId(self::FIELD_ATTACHMENTS, $attachment->getId());
}
$body = $email->getBody();
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
if ($body) {
foreach ($inlineAttachmentMap as $cid => $attachment) {
if (str_contains($body, 'cid:' . $cid)) {
$body = str_replace('cid:' . $cid, '?entryPoint=attachment&amp;id=' . $attachmentId, $body);
$body = str_replace(
'cid:' . $cid,
'?entryPoint=attachment&amp;id=' . $attachment->getId(),
$body
);
continue;
}
$email->addLinkMultipleId('attachments', $attachmentId);
// Fallback to attachment.
if ($attachment->getRole() === Attachment::ROLE_INLINE_ATTACHMENT) {
$attachment
->setRole(Attachment::ROLE_ATTACHMENT)
->setTargetField(self::FIELD_ATTACHMENTS);
$this->entityManager->saveEntity($attachment);
$email->addLinkMultipleId(self::FIELD_ATTACHMENTS, $attachment->getId());
}
}
$email->setBody($body);

View File

@@ -85,6 +85,7 @@ class Sender
private FileStorageManager $fileStorageManager
) {
/** @noinspection PhpDeprecationInspection */
$this->useGlobal();
}
@@ -149,6 +150,7 @@ class Sender
throw new InvalidArgumentException();
}
/** @noinspection PhpDeprecationInspection */
return $this->useSmtp($params);
}
@@ -171,6 +173,7 @@ class Sender
*/
public function withEnvelopeOptions(array $options): self
{
/** @noinspection PhpDeprecationInspection */
return $this->setEnvelopeOptions($options);
}
@@ -252,8 +255,9 @@ class Sender
$authMechanism = $params['authMechanism'] ?? $params['smtpAuthMechanism'] ?? null;
if ($authMechanism) {
$authMechanism = preg_replace("([\.]{2,})", '', $authMechanism);
$authMechanism = preg_replace("([.]{2,})", '', $authMechanism);
/** @noinspection SpellCheckingInspection */
if (in_array($authMechanism, ['login', 'crammd5', 'plain'])) {
$options['connectionClass'] = $authMechanism;
}
@@ -571,7 +575,7 @@ class Sender
empty($messageId) ||
!is_string($messageId) ||
strlen($messageId) < 4 ||
strpos($messageId, 'dummy:') === 0
str_starts_with($messageId, 'dummy:')
) {
$messageId = $this->generateMessageId($email);
@@ -597,13 +601,17 @@ class Sender
$email->set('dateSent', DateTime::createNow()->getString());
}
catch (Exception $e) {
/** @noinspection PhpDeprecationInspection */
$this->resetParams();
/** @noinspection PhpDeprecationInspection */
$this->useGlobal();
$this->handleException($e);
}
/** @noinspection PhpDeprecationInspection */
$this->resetParams();
/** @noinspection PhpDeprecationInspection */
$this->useGlobal();
}
@@ -642,14 +650,14 @@ class Sender
private function handleException(Exception $e): void
{
if ($e instanceof ProtocolRuntimeException) {
$message = "Unknown error.";
$message = "unknownError";
if (
stripos($e->getMessage(), 'password') !== false ||
stripos($e->getMessage(), 'credentials') !== false ||
stripos($e->getMessage(), '5.7.8') !== false
) {
$message = 'Invalid credentials.';
$message = 'invalidCredentials';
}
$this->log->error("Email sending error: " . $e->getMessage());
@@ -671,7 +679,7 @@ class Sender
}
else {
$messageId =
'' . md5($email->get('name')) . '/' .time() . '/' .
md5($email->get('name')) . '/' .time() . '/' .
$rand . '@espo';
}

View File

@@ -29,6 +29,7 @@
namespace Espo\Core\MassAction\Actions;
use Espo\Core\Record\ActionHistory\Action;
use Espo\Entities\ActionHistoryRecord;
use Espo\Entities\User;
use Espo\Core\Acl;
@@ -97,7 +98,7 @@ class MassDelete implements MassAction
$count++;
$service->processActionHistoryRecord(ActionHistoryRecord::ACTION_DELETE, $entity);
$service->processActionHistoryRecord(Action::DELETE, $entity);
}
$result = [

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Notificators;
/**
* @deprecated As of v6.0.
* @todo Remove in v8.0.
* @todo Remove in v9.0.
*/
class Base extends DefaultNotificator
{

View File

@@ -32,22 +32,18 @@ namespace Espo\Core\Notificators;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Notification\AssignmentNotificator\Params;
use Espo\Core\Notification\DefaultAssignmentNotificator;
use Espo\Core\ORM\EntityManager;
/**
* @deprecated As of v7.0. Use plain classes that implement `Espo\Core\Notification\AssignmentNotificator`.
* @todo Remove in v8.0.
* @todo Remove in v9.0.
*/
class DefaultNotificator
{
protected $entityType; /** @phpstan-ignore-line */
protected $user; /** @phpstan-ignore-line */
protected $entityManager; /** @phpstan-ignore-line */
private $base; /** @phpstan-ignore-line */
public function __construct(User $user, EntityManager $entityManager, DefaultAssignmentNotificator $base)

View File

@@ -0,0 +1,138 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\ORM;
use Espo\Core\ORM\Entity as BaseEntity;
use Espo\Core\Repositories\Database as DatabaseRepository;
use Espo\Core\Utils\ClassFinder;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity as Entity;
use Espo\ORM\Repository\Repository as Repository;
class ClassNameProvider
{
/** @var class-string<Entity> */
private const DEFAULT_ENTITY_CLASS_NAME = BaseEntity::class;
/** @var class-string<Repository<Entity>> */
private const DEFAULT_REPOSITORY_CLASS_NAME = DatabaseRepository::class;
/** @var array<string, class-string<Entity>> */
private array $entityCache = [];
/** @var array<string, class-string<Repository<Entity>>> */
private array $repositoryCache = [];
public function __construct(
private Metadata $metadata,
private ClassFinder $classFinder
) {}
/**
* @param string $entityType
* @return class-string<Entity>
*/
public function getEntityClassName(string $entityType): string
{
if (!array_key_exists($entityType, $this->entityCache)) {
$this->entityCache[$entityType] = $this->findEntityClassName($entityType);
}
return $this->entityCache[$entityType];
}
/**
* @param string $entityType
* @return class-string<Repository<Entity>>
*/
public function getRepositoryClassName(string $entityType): string
{
if (!array_key_exists($entityType, $this->entityCache)) {
$this->repositoryCache[$entityType] = $this->findRepositoryClassName($entityType);
}
return $this->repositoryCache[$entityType];
}
/**
* @param string $entityType
* @return class-string<Entity>
*/
private function findEntityClassName(string $entityType): string
{
/** @var ?class-string<Entity> $className */
$className = $this->classFinder->find('Entities', $entityType);
if ($className) {
return $className;
}
/** @var ?string $template */
$template = $this->metadata->get(['scopes', $entityType, 'type']);
if ($template) {
/** @var ?class-string<Entity> $className */
$className = $this->metadata->get(['app', 'entityTemplates', $template, 'entityClassName']);
}
if ($className) {
return $className;
}
return self::DEFAULT_ENTITY_CLASS_NAME;
}
/**
* @param string $entityType
* @return class-string<Repository<Entity>>
*/
private function findRepositoryClassName(string $entityType): string
{
/** @var ?class-string<Repository<Entity>> $className */
$className = $this->classFinder->find('Repositories', $entityType);
if ($className) {
return $className;
}
/** @var ?string $template */
$template = $this->metadata->get(['scopes', $entityType, 'type']);
if ($template) {
/** @var ?class-string<Repository<Entity>> $className */
$className = $this->metadata->get(['app', 'entityTemplates', $template, 'repositoryClassName']);
}
if ($className) {
return $className;
}
return self::DEFAULT_REPOSITORY_CLASS_NAME;
}
}

View File

@@ -37,7 +37,6 @@ use RuntimeException;
class DatabaseParamsFactory
{
private const DEFAULT_PLATFORM = 'Mysql';
private const DEFAULT_CHARSET = 'utf8';
public function __construct(private Config $config) {}
@@ -55,7 +54,7 @@ class DatabaseParamsFactory
->withName($config->get('database.dbname'))
->withUsername($config->get('database.user'))
->withPassword($config->get('database.password'))
->withCharset($config->get('database.charset') ?? self::DEFAULT_CHARSET)
->withCharset($config->get('database.charset'))
->withPlatform($config->get('database.platform'))
->withSslCa($config->get('database.sslCA'))
->withSslCert($config->get('database.sslCert'))

View File

@@ -33,9 +33,6 @@ use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\ORM\Entity as BaseEntity;
use Espo\Core\Utils\ClassFinder;
use Espo\ORM\Entity;
use Espo\ORM\EntityFactory as EntityFactoryInterface;
use Espo\ORM\EntityManager;
@@ -49,20 +46,11 @@ class EntityFactory implements EntityFactoryInterface
private ?ValueAccessorFactory $valueAccessorFactory = null;
public function __construct(
private ClassFinder $classFinder,
private ClassNameProvider $classNameProvider,
private Helper $helper,
private InjectableFactory $injectableFactory
) {}
/**
* @return ?class-string<Entity>
*/
private function getClassName(string $entityType): ?string
{
/** @var ?class-string<Entity> */
return $this->classFinder->find('Entities', $entityType);
}
public function setEntityManager(EntityManager $entityManager): void
{
if ($this->entityManager) {
@@ -85,10 +73,6 @@ class EntityFactory implements EntityFactoryInterface
{
$className = $this->getClassName($entityType);
if (!$className) {
$className = BaseEntity::class;
}
if (!$this->entityManager) {
throw new RuntimeException();
}
@@ -96,7 +80,7 @@ class EntityFactory implements EntityFactoryInterface
$defs = $this->entityManager->getMetadata()->get($entityType);
if (is_null($defs)) {
throw new RuntimeException("Entity '{$entityType}' is not defined in metadata.");
throw new RuntimeException("Entity '$entityType' is not defined in metadata.");
}
$bindingContainer = $this->getBindingContainer($className, $entityType, $defs);
@@ -104,6 +88,15 @@ class EntityFactory implements EntityFactoryInterface
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
}
/**
* @return class-string<Entity>
*/
private function getClassName(string $entityType): string
{
/** @var class-string<Entity> */
return $this->classNameProvider->getEntityClassName($entityType);
}
/**
* @param class-string<Entity> $className
* @param array<string, mixed> $defs

View File

@@ -45,7 +45,7 @@ class MetadataDataProvider implements MetadataDataProviderInterface
$data = $this->ormMetadataData->getData();
foreach (array_keys($data) as $entityType) {
$data[$entityType]['vFields'] = $this->metadata->get(['entityDefs', $entityType, 'fields']) ?? [];
$data[$entityType]['fields'] = $this->metadata->get(['entityDefs', $entityType, 'fields']) ?? [];
}
return $data;

View File

@@ -44,6 +44,11 @@ class SaveOption
* Import. Boolean.
*/
public const IMPORT = 'import';
/**
* Called from a Record service.
* @since 8.0.1
*/
public const API = 'api';
/**
* Skip all additional processing. Boolean.
*/

View File

@@ -32,41 +32,23 @@ namespace Espo\Core\ORM;
use Espo\Core\Binding\BindingContainerBuilder;
use Espo\Core\Binding\ContextualBinder;
use Espo\Core\InjectableFactory;
use Espo\Core\Repositories\Database as DatabaseRepository;
use Espo\Core\Utils\ClassFinder;
use Espo\ORM\Entity as OrmEntity;
use Espo\ORM\Entity as Entity;
use Espo\ORM\EntityFactory as EntityFactoryInterface;
use Espo\ORM\Repository\Repository;
use Espo\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
class RepositoryFactory implements RepositoryFactoryInterface
{
/** @var class-string<Repository<OrmEntity>> */
protected $defaultClassName = DatabaseRepository::class;
public function __construct(
protected EntityFactoryInterface $entityFactory,
protected InjectableFactory $injectableFactory,
protected ClassFinder $classFinder
private EntityFactoryInterface $entityFactory,
private InjectableFactory $injectableFactory,
private ClassNameProvider $classNameProvider
) {}
/**
* @return ?class-string<Repository<OrmEntity>>
*/
protected function getClassName(string $entityType): ?string
{
/** @var ?class-string<Repository<OrmEntity>> */
return $this->classFinder->find('Repositories', $entityType);
}
public function create(string $entityType): Repository
{
$className = $this->getClassName($entityType);
if (!$className || !class_exists($className)) {
$className = $this->defaultClassName;
}
return $this->injectableFactory->createWithBinding(
$className,
BindingContainerBuilder::create()
@@ -81,4 +63,13 @@ class RepositoryFactory implements RepositoryFactoryInterface
->build()
);
}
/**
* @return class-string<Repository<Entity>>
*/
private function getClassName(string $entityType): string
{
/** @var class-string<Repository<Entity>> */
return $this->classNameProvider->getRepositoryClassName($entityType);
}
}

View File

@@ -47,9 +47,14 @@ class Url
return explode('/', $url)[count(explode('/', $scriptNameModified)) - 1] ?? null;
}
public static function getPortalIdFromEnv(): ?string
{
return $_SERVER['ESPO_PORTAL_ID'] ?? null;
}
public static function detectPortalId(): ?string
{
$portalId = $_SERVER['ESPO_PORTAL_ID'] ?? null;
$portalId = self::getPortalIdFromEnv();
if ($portalId) {
return $portalId;

View File

@@ -0,0 +1,52 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Rebuild\Actions;
use Espo\Core\Rebuild\RebuildAction;
use Espo\Entities\SystemData;
use Espo\ORM\EntityManager;
class AddSystemData implements RebuildAction
{
public function __construct(
private EntityManager $entityManager
) {}
public function process(): void
{
$entity = $this->entityManager->getEntityById(SystemData::ENTITY_TYPE, SystemData::ONLY_ID);
if ($entity) {
return;
}
$this->entityManager->createEntity(SystemData::ENTITY_TYPE, ['id' => SystemData::ONLY_ID]);
}
}

View File

@@ -41,9 +41,11 @@ use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\Defs;
use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\ORM\Type\RelationType;
/**
* Check access for record linking.
@@ -57,6 +59,7 @@ class LinkCheck
* @param string[] $noEditAccessRequiredLinkList
*/
public function __construct(
private Defs $ormDefs,
private EntityManager $entityManager,
private Acl $acl,
private Metadata $metadata,
@@ -71,7 +74,7 @@ class LinkCheck
*
* @throws Forbidden
*/
public function process(Entity $entity): void
public function processFields(Entity $entity): void
{
$this->processLinkMultiple($entity);
}
@@ -133,7 +136,7 @@ class LinkCheck
in_array($name, $this->acl->getScopeForbiddenLinkList($entityType, AclTable::ACTION_EDIT))
) {
throw ForbiddenSilent::createWithBody(
"No access to link {$name}.",
"No access to link $name.",
ErrorBody::create()
->withMessageTranslation('cannotRelateForbiddenLink', null, ['link' => $name])
->encode()
@@ -172,7 +175,7 @@ class LinkCheck
if (!$foreignEntity) {
throw ForbiddenSilent::createWithBody(
"Can't relate with non-existing record.",
"Can't relate with non-existing record. entity type: $entityType, link: $link.",
ErrorBody::create()
->withMessageTranslation(
'cannotRelateNonExisting', null, ['foreignEntityType' => $foreignEntityType])
@@ -206,7 +209,7 @@ class LinkCheck
if (!$this->acl->check($entity, $action)) {
throw ForbiddenSilent::createWithBody(
"No record access for link operation ({$entityType}:{$link}).",
"No record access for link operation ($entityType:$link).",
ErrorBody::create()
->withMessageTranslation('noAccessToRecord', null, ['action' => $action])
->encode()
@@ -214,6 +217,16 @@ class LinkCheck
}
}
/**
* Check unlink access to a specific link.
*
* @throws Forbidden
*/
public function processUnlink(Entity $entity, string $link): void
{
$this->processLink($entity, $link);
}
/**
* Check link access for a specific foreign entity.
* @throws Forbidden
@@ -224,6 +237,16 @@ class LinkCheck
$this->linkEntityAccessCheck($entity, $foreignEntity, $link);
}
/**
* Check unlink access for a specific foreign entity.
* @throws Forbidden
*/
public function processUnlinkForeign(Entity $entity, string $link, Entity $foreignEntity): void
{
$this->processLinkForeign($entity, $link, $foreignEntity);
$this->processUnlinkForeignRequired($entity, $link, $foreignEntity);
}
/**
* @throws Forbidden
*/
@@ -306,7 +329,7 @@ class LinkCheck
$body->withMessageTranslation('noAccessToForeignRecord', null, ['action' => $action]);
throw ForbiddenSilent::createWithBody(
"No foreign record access for link operation ({$entityType}:{$link}).",
"No foreign record access for link operation ($entityType:$link).",
$body->encode()
);
}
@@ -329,7 +352,7 @@ class LinkCheck
}
throw ForbiddenSilent::createWithBody(
"No access for link operation ({$entityType}:{$link}).",
"No access for link operation ($entityType:$link).",
ErrorBody::create()
->withMessageTranslation('noLinkAccess')
->encode()
@@ -359,4 +382,64 @@ class LinkCheck
return $checker;
}
/**
* @throws Forbidden
*/
private function processUnlinkForeignRequired(Entity $entity, string $link, Entity $foreignEntity): void
{
$relationDefs = $this->ormDefs
->getEntity($entity->getEntityType())
->tryGetRelation($link);
if (!$relationDefs) {
return;
}
if (
!$relationDefs->hasForeignEntityType() ||
!$relationDefs->hasForeignRelationName()
) {
return;
}
$foreignLink = $relationDefs->getForeignRelationName();
$foreignRelationDefs = $this->ormDefs
->getEntity($foreignEntity->getEntityType())
->tryGetRelation($foreignLink);
if (!$foreignRelationDefs) {
return;
}
if (
!in_array($foreignRelationDefs->getType(), [
RelationType::BELONGS_TO,
RelationType::HAS_ONE,
RelationType::BELONGS_TO_PARENT,
])
) {
return;
}
$foreignFieldDefs = $this->ormDefs
->getEntity($foreignEntity->getEntityType())
->tryGetField($foreignLink);
if (!$foreignFieldDefs) {
return;
}
if (!$foreignFieldDefs->getParam('required')) {
return;
}
throw ForbiddenSilent::createWithBody(
"Can't unlink required field ({$foreignEntity->getEntityType()}:$foreignLink}).",
ErrorBody::create()
->withMessageTranslation('cannotUnrelateRequiredLink')
->encode()
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Record\ActionHistory;
class Action
{
public const CREATE = 'create';
public const READ = 'read';
public const UPDATE = 'update';
public const DELETE = 'delete';
}

View File

@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Record\ActionHistory;
use Espo\ORM\Entity;
/**
* Logs actions users do with records.
*/
interface ActionLogger
{
/**
* Log an action.
*
* @param Action::* $action
*/
public function log(string $action, Entity $entity): void;
}

View File

@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Record\ActionHistory;
use Espo\Core\Field\LinkParent;
use Espo\Entities\ActionHistoryRecord;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class DefaultActionLogger implements ActionLogger
{
public function __construct(
private EntityManager $entityManager,
private User $user
) {}
/**
* @inheritDoc
*/
public function log(string $action, Entity $entity): void
{
$historyRecord = $this->entityManager
->getRepositoryByClass(ActionHistoryRecord::class)
->getNew();
$historyRecord
->setAction($action)
->setUserId($this->user->getId())
->setAuthTokenId($this->user->get('authTokenId'))
->setAuthLogRecordId($this->user->get('authLogRecordId'))
->setIpAddress($this->user->get('ipAddress'))
->setTarget(LinkParent::createFromEntity($entity));
$this->entityManager->saveEntity($historyRecord);
}
}

View File

@@ -29,6 +29,7 @@
namespace Espo\Core\Record\Duplicator;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\ORM\Defs;
use Espo\ORM\Defs\FieldDefs;
@@ -44,7 +45,8 @@ class EntityDuplicator
public function __construct(
private Defs $defs,
private FieldDuplicatorFactory $fieldDuplicatorFactory,
private FieldUtil $fieldUtil
private FieldUtil $fieldUtil,
private Metadata $metadata
) {}
public function duplicate(Entity $entity): stdClass
@@ -68,7 +70,7 @@ class EntityDuplicator
$entityType = $entity->getEntityType();
$field = $fieldDefs->getName();
if ($fieldDefs->getParam('duplicateIgnore')) {
if ($this->toIgnoreField($entityType, $fieldDefs)) {
$attributeList = $this->fieldUtil->getAttributeList($entityType, $field);
foreach ($attributeList as $attribute) {
@@ -90,4 +92,20 @@ class EntityDuplicator
$valueMap->$attribute = $value;
}
}
private function toIgnoreField(string $entityType, FieldDefs $fieldDefs): bool
{
$type = $fieldDefs->getType();
// @todo Use FieldType constants.
if (in_array($type, ['autoincrement', 'number'])) {
return true;
}
if ($this->metadata->get(['scopes', $entityType, 'statusField']) === $fieldDefs->getName()) {
return true;
}
return (bool) $fieldDefs->getParam('duplicateIgnore');
}
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Record\Hook;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\ORM\Entity;
use Espo\Core\Record\CreateParams;
@@ -39,6 +42,9 @@ interface CreateHook
{
/**
* @param TEntity $entity
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function process(Entity $entity, CreateParams $params): void;
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Record\Hook;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\ORM\Entity;
use Espo\Core\Record\DeleteParams;
@@ -39,6 +42,9 @@ interface DeleteHook
{
/**
* @param TEntity $entity
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function process(Entity $entity, DeleteParams $params): void;
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Record\Hook;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\ORM\Entity;
use Espo\Core\Record\UpdateParams;
@@ -40,6 +43,9 @@ interface UpdateHook
{
/**
* @param TEntity $entity
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function process(Entity $entity, UpdateParams $params): void;
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Record;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\CreateHook;
use Espo\Core\Record\Hook\DeleteHook;
use Espo\Core\Record\Hook\LinkHook;
@@ -44,6 +47,11 @@ class HookManager
public function __construct(private Provider $provider)
{}
/**
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function processBeforeCreate(Entity $entity, CreateParams $params): void
{
foreach ($this->getBeforeCreateHookList($entity->getEntityType()) as $hook) {
@@ -58,6 +66,11 @@ class HookManager
}
}
/**
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function processBeforeUpdate(Entity $entity, UpdateParams $params): void
{
foreach ($this->getBeforeUpdateHookList($entity->getEntityType()) as $hook) {
@@ -65,6 +78,11 @@ class HookManager
}
}
/**
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function processBeforeDelete(Entity $entity, DeleteParams $params): void
{
foreach ($this->getBeforeDeleteHookList($entity->getEntityType()) as $hook) {

View File

@@ -40,9 +40,11 @@ use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\NotFoundSilent;
use Espo\Core\Field\LinkParent;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Record\Access\LinkCheck;
use Espo\Core\Record\ActionHistory\Action;
use Espo\Core\Record\ActionHistory\ActionLogger;
use Espo\Core\Record\Formula\Processor as FormulaProcessor;
use Espo\Core\Utils\Json;
use Espo\Core\Acl;
@@ -66,10 +68,13 @@ use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\WhereClause;
use Espo\Tools\Stream\Service as StreamService;
use Espo\Entities\User;
use Espo\Entities\ActionHistoryRecord;
use stdClass;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
use const E_USER_DEPRECATED;
/**
* The layer between a controller and ORM repository. For CRUD and other operations with records.
@@ -139,8 +144,6 @@ class Service implements Crud,
protected $nonAdminReadOnlyLinkList = [];
/** @var string[] */
protected $onlyAdminLinkList = [];
/** @var array<string, array<string, mixed>> */
protected $linkParams = [];
/** @var array<string, string[]> */
protected $linkMandatorySelectAttributeList = [];
/** @var string[] */
@@ -166,14 +169,13 @@ class Service implements Crud,
/** @var bool */
protected $forceSelectAllAttributes = false;
/** @var string[] */
protected $validateSkipFieldList = [];
/**
* @todo Move to metadata.
* @var string[]
*/
protected $validateRequiredSkipFieldList = [];
/** @var string[] */
protected $duplicateIgnoreAttributeList = [];
/**
* @var string[]
* @deprecated As of v8.0. Use `suppressValidationList` metadata parameter.
* @todo Remove in v9.0.
*/
protected $validateSkipFieldList = [];
/** @var Acl */
protected $acl = null;
@@ -188,6 +190,7 @@ class Service implements Crud,
private ?ListLoadProcessor $listLoadProcessor = null;
private ?DuplicateFinder $duplicateFinder = null;
private ?LinkCheck $linkCheck = null;
private ?ActionLogger $actionLogger = null;
protected const MAX_SELECT_TEXT_ATTRIBUTE_LENGTH = 10000;
@@ -207,7 +210,7 @@ class Service implements Crud,
/**
* Add an action-history record.
*
* @param ActionHistoryRecord::ACTION_* $action
* @param Action::* $action
*/
public function processActionHistoryRecord(string $action, Entity $entity): void
{
@@ -219,18 +222,16 @@ class Service implements Crud,
return;
}
/** @var ActionHistoryRecord $historyRecord */
$historyRecord = $this->entityManager->getNewEntity(ActionHistoryRecord::ENTITY_TYPE);
$this->getActionLogger()->log($action, $entity);
}
$historyRecord
->setAction($action)
->setUserId($this->user->getId())
->setAuthTokenId($this->user->get('authTokenId'))
->setAuthLogRecordId($this->user->get('authLogRecordId'))
->setIpAddress($this->user->get('ipAddress'))
->setTarget(LinkParent::createFromEntity($entity));
private function getActionLogger(): ActionLogger
{
if (!$this->actionLogger) {
$this->actionLogger = $this->injectableFactory->createResolved(ActionLogger::class);
}
$this->entityManager->saveEntity($historyRecord);
return $this->actionLogger;
}
/**
@@ -239,7 +240,7 @@ class Service implements Crud,
* @param non-empty-string $id
* @return TEntity
* @throws NotFoundSilent If not found.
* @throws ForbiddenSilent If no read access.
* @throws Forbidden If no read access.
*/
public function read(string $id, ReadParams $params): Entity
{
@@ -258,7 +259,7 @@ class Service implements Crud,
}
$this->recordHookManager->processBeforeRead($entity, $params);
$this->processActionHistoryRecord(ActionHistoryRecord::ACTION_READ, $entity);
$this->processActionHistoryRecord(Action::READ, $entity);
return $entity;
}
@@ -266,12 +267,33 @@ class Service implements Crud,
/**
* Get an entity by ID. Access control check is performed.
*
* @throws ForbiddenSilent If no read access.
* @throws Forbidden If no read access.
* @return ?TEntity
*/
public function getEntity(string $id): ?Entity
{
$entity = $this->getRepository()->getById($id);
try {
$query = $this->selectBuilderFactory
->create()
->from($this->entityType)
->withSearchParams(
SearchParams::create()
->withSelect(['*'])
->withPrimaryFilter('one')
)
->withAdditionalApplierClassNameList(
$this->createSelectApplierClassNameListProvider()->get($this->entityType)
)
->build();
}
catch (BadRequest|Error $e) {
throw new RuntimeException($e->getMessage());
}
$entity = $this->getRepository()
->clone($query)
->where(['id' => $id])
->findOne();
if (!$entity && $this->user->isAdmin()) {
$entity = $this->getEntityEvenDeleted($id);
@@ -354,8 +376,14 @@ class Service implements Crud,
{
$params = FieldValidationParams
::create()
->withSkipFieldList($this->validateSkipFieldList)
->withTypeSkipFieldList('required', $this->validateRequiredSkipFieldList);
->withSkipFieldList($this->validateSkipFieldList);
if (!empty($this->validateSkipFieldList)) {
trigger_error(
'$validateSkipFieldList is deprecated and will be removed in v9.0.',
E_USER_DEPRECATED
);
}
$this->fieldValidationManager->process($entity, $data, $params);
}
@@ -497,6 +525,7 @@ class Service implements Crud,
/**
* @deprecated As of v7.0. Use filterCreateInput or filterUpdateInput. Or better don't extend the class.
* Use entityAcl, app > acl, roles to restrict write access for specific fields.
* @todo Remove in v9.0.
* @param stdClass $data
* @return void
*/
@@ -507,6 +536,7 @@ class Service implements Crud,
/**
* @deprecated As of v7.0. Use filterCreateInput or filterUpdateInput. Or better don't extend the class.
* Use entityAcl, app > acl, roles to restrict write access for specific fields.
* @todo Remove in v9.0.
* @param stdClass $data
* @return void
*/
@@ -577,6 +607,7 @@ class Service implements Crud,
/**
* @param TEntity $entity
* @todo Move the logic to a class. Make customizable (recordDefs)?
*/
public function populateDefaults(Entity $entity, stdClass $data): void
{
@@ -664,7 +695,7 @@ class Service implements Crud,
$this->processValidation($entity, $data);
$this->processAssignmentCheck($entity);
$this->getLinkCheck()->process($entity);
$this->getLinkCheck()->processFields($entity);
if (!$params->skipDuplicateCheck()) {
$this->processDuplicateCheck($entity);
@@ -675,13 +706,13 @@ class Service implements Crud,
$this->beforeCreateEntity($entity, $data);
$this->entityManager->saveEntity($entity);
$this->entityManager->saveEntity($entity, [SaveOption::API => true]);
$this->afterCreateEntity($entity, $data);
$this->afterCreateProcessDuplicating($entity, $params);
$this->loadAdditionalFields($entity);
$this->prepareEntityForOutput($entity);
$this->processActionHistoryRecord(ActionHistoryRecord::ACTION_CREATE, $entity);
$this->processActionHistoryRecord(Action::CREATE, $entity);
return $entity;
}
@@ -731,7 +762,7 @@ class Service implements Crud,
$this->processValidation($entity, $data);
$this->processAssignmentCheck($entity);
$this->getLinkCheck()->process($entity);
$this->getLinkCheck()->processFields($entity);
$checkForDuplicates =
$this->metadata->get(['recordDefs', $this->entityType, 'updateDuplicateCheck']) ??
@@ -745,11 +776,16 @@ class Service implements Crud,
$this->recordHookManager->processBeforeUpdate($entity, $params);
$this->beforeUpdateEntity($entity, $data);
$this->entityManager->saveEntity($entity);
$this->entityManager->saveEntity($entity, [SaveOption::API => true]);
$this->afterUpdateEntity($entity, $data);
if ($this->metadata->get(['recordDefs', $this->entityType, 'loadAdditionalFieldsAfterUpdate'])) {
$this->loadAdditionalFields($entity);
}
$this->prepareEntityForOutput($entity);
$this->processActionHistoryRecord(ActionHistoryRecord::ACTION_UPDATE, $entity);
$this->processActionHistoryRecord(Action::UPDATE, $entity);
return $entity;
}
@@ -785,7 +821,7 @@ class Service implements Crud,
$this->beforeDeleteEntity($entity);
$this->getRepository()->remove($entity);
$this->afterDeleteEntity($entity);
$this->processActionHistoryRecord(ActionHistoryRecord::ACTION_DELETE, $entity);
$this->processActionHistoryRecord(Action::DELETE, $entity);
}
/**
@@ -851,7 +887,7 @@ class Service implements Crud,
return RecordCollection::create($collection, $total);
}
protected function createSelectApplierClassNameListProvider(): ApplierClassNameListProvider
private function createSelectApplierClassNameListProvider(): ApplierClassNameListProvider
{
return $this->injectableFactory->create(ApplierClassNameListProvider::class);
}
@@ -859,7 +895,7 @@ class Service implements Crud,
/**
* @return TEntity|null
*/
protected function getEntityEvenDeleted(string $id): ?Entity
private function getEntityEvenDeleted(string $id): ?Entity
{
$query = $this->entityManager
->getQueryBuilder()
@@ -957,14 +993,11 @@ class Service implements Crud,
->getRelation($link)
->getForeignEntityType();
$linkParams = $this->linkParams[$link] ?? [];
$skipAcl = $this->metadata
->get("recordDefs.$this->entityType.relationships.$link.selectAccessControlDisabled") ?? false;
$skipAcl = $linkParams['skipAcl'] ?? false;
if (!$skipAcl) {
if (!$this->acl->check($foreignEntityType, AclTable::ACTION_READ)) {
throw new Forbidden();
}
if (!$skipAcl && !$this->acl->check($foreignEntityType, AclTable::ACTION_READ)) {
throw new Forbidden();
}
$recordService = $this->recordServiceContainer->get($foreignEntityType);
@@ -990,7 +1023,10 @@ class Service implements Crud,
$selectBuilder
->from($foreignEntityType)
->withSearchParams($preparedSearchParams);
->withSearchParams($preparedSearchParams)
->withAdditionalApplierClassNameList(
$this->createSelectApplierClassNameListProvider()->get($foreignEntityType)
);
if (!$skipAcl) {
$selectBuilder->withStrictAccessControl();
@@ -1133,7 +1169,7 @@ class Service implements Crud,
throw new LogicException("Only core entities are supported.");
}
$this->getLinkCheck()->processLink($entity, $link);
$this->getLinkCheck()->processUnlink($entity, $link);
if ($this->processUnlinkMethod($id, $link, $foreignId)) {
return;
@@ -1151,7 +1187,7 @@ class Service implements Crud,
throw new NotFound();
}
$this->getLinkCheck()->processLinkForeign($entity, $link, $foreignEntity);
$this->getLinkCheck()->processUnlinkForeign($entity, $link, $foreignEntity);
$this->recordHookManager->processBeforeUnlink($entity, $link, $foreignEntity);
@@ -1351,7 +1387,7 @@ class Service implements Crud,
}
if (!$id || !$link) {
throw new BadRequest;
throw new BadRequest();
}
$this->processForbiddenLinkEditCheck($link);
@@ -1362,6 +1398,7 @@ class Service implements Crud,
throw new NotFound();
}
// Not used link-check deliberately. Only edit access.
if (!$this->acl->check($entity, AclTable::ACTION_EDIT)) {
throw new Forbidden();
}
@@ -1373,7 +1410,7 @@ class Service implements Crud,
}
if (!$entity instanceof CoreEntity) {
throw new LogicException("Only core entities are supported");
throw new LogicException("Only core entities are supported.");
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
@@ -1739,21 +1776,15 @@ class Service implements Crud,
}
}
/**
* @deprecated
* @todo Remove in v7.6.
* @param string $type
* @return string[]
*/
protected function getFieldByTypeList($type)
{
return $this->fieldUtil->getFieldByTypeList($this->entityType, $type);
}
public function prepareSearchParams(SearchParams $searchParams): SearchParams
{
return $this
->prepareSearchParamsSelect($searchParams)
$searchParams = $this->prepareSearchParamsSelect($searchParams);
if ($searchParams->getSelect() === null) {
$searchParams = $searchParams->withSelect(['*']);
}
return $searchParams
->withMaxTextAttributeLength(
$this->getMaxSelectTextAttributeLength()
);

View File

@@ -31,6 +31,6 @@ namespace Espo\Core\Select\AccessControl;
/**
* @deprecated Use `\Espo\Core\Select\AccessControl\FilterResolvers\Bypass` instead.
* @todo Remove in v8.0.
* @todo Remove in v9.0.
*/
class BypassFilterResolver extends \Espo\Core\Select\AccessControl\FilterResolvers\Bypass {}

View File

@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Select\Primary\Filters;
use Espo\Core\Select\Primary\Filter;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
/**
* A dummy filter 'one'. Applied only when reading a single record (from the detail view).
* Can be detected in a custom AdditionalApplier to distinguish a read request from a find request.
*/
class One implements Filter
{
public const NAME = 'one';
public function apply(QueryBuilder $queryBuilder): void
{}
}

View File

@@ -115,6 +115,10 @@ class Applier
return null;
}
if ($passedAttributeList === ['*']) {
return ['*'];
}
$attributeList = [];
if (!in_array('id', $passedAttributeList)) {

View File

@@ -373,9 +373,7 @@ class SelectManager
return;
}
$relDefs = $this->getSeed()->getRelations();
$relationType = $seed->getRelationType($link);
$relDefs = $this->entityManager->getMetadata()->get($this->entityType, ['relations']);
$defs = $relDefs[$link];
@@ -455,7 +453,7 @@ class SelectManager
public function applyInCategory(string $link, $value, array &$result)
{
$relDefs = $this->getSeed()->getRelations();
$relDefs = $this->entityManager->getMetadata()->get($this->entityType, ['relations']);
if (empty($relDefs[$link])) {
throw new Error("Can't apply inCategory for link {$link}.");

View File

@@ -48,6 +48,7 @@ class Type
public const STARTS_WITH = 'startsWith';
public const ENDS_WITH = 'endsWith';
public const CONTAINS = 'contains';
public const NOT_CONTAINS = 'notContains';
public const GREATER_THAN = 'greaterThan';
public const LESS_THAN = 'lessThan';
public const GREATER_THAN_OR_EQUALS = 'greaterThanOrEquals';
@@ -55,6 +56,7 @@ class Type
public const AFTER = 'after';
public const BEFORE = 'before';
public const BETWEEN = 'between';
public const EVER = 'ever';
public const ANY = 'any';
public const NONE = 'none';
public const IS_NULL = 'isNull';
@@ -64,6 +66,7 @@ class Type
public const TODAY = 'today';
public const PAST = 'past';
public const FUTURE = 'future';
public const LAST_SEVEN_DAYS = 'lastSevenDays';
public const LAST_X_DAYS = 'lastXDays';
public const NEXT_X_DAYS = 'nextXDays';
public const OLDER_THAN_X_DAYS = 'olderThanXDays';

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Templates\Controllers;
/**
* Do not remove. Used by exported custom modules.
*/
class Base extends \Espo\Core\Controllers\Record
{
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Templates\Entities;
class Base extends \Espo\Core\ORM\Entity
use Espo\Core\ORM\Entity;
class Base extends Entity
{
public const TEMPLATE_TYPE = 'Base';
}

View File

@@ -29,6 +29,9 @@
namespace Espo\Core\Templates\Entities;
class BasePlus extends \Espo\Core\ORM\Entity
use Espo\Core\ORM\Entity;
class BasePlus extends Entity
{
public const TEMPLATE_TYPE = 'BasePlus';
}

View File

@@ -0,0 +1,3 @@
{
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\General"
}

View File

@@ -0,0 +1,3 @@
{
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\General"
}

View File

@@ -40,7 +40,7 @@
"childList": {
"type": "jsonArray",
"notStorable": true,
"disabled": true
"utility": true
}
},
"links": {
@@ -85,7 +85,7 @@
},
"additionalTables": {
"{entityType}Path": {
"fields": {
"attributes": {
"id": {
"type": "id",
"dbType": "integer",

View File

@@ -1,3 +1,3 @@
{
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\Company"
}
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\General"
}

View File

@@ -7,5 +7,6 @@
"aclPortalLevelList": ["all", "account", "contact", "own", "no"],
"customizable": true,
"importable": true,
"notifications": true
}
"notifications": true,
"duplicateCheckFieldList": ["name", "emailAddress"]
}

View File

@@ -25,7 +25,8 @@
"type": "datetimeOptional",
"view": "crm:views/meeting/fields/date-end",
"required": true,
"after": "dateStart"
"after": "dateStart",
"suppressValidationList": ["required"]
},
"isAllDay": {
"type": "bool",

View File

@@ -1,3 +1,3 @@
{
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\Person"
}
"duplicateWhereBuilderClassName": "Espo\\Classes\\DuplicateWhereBuilders\\General"
}

View File

@@ -8,5 +8,6 @@
"customizable": true,
"importable": true,
"notifications": true,
"hasPersonalData": true
}
"hasPersonalData": true,
"duplicateCheckFieldList": ["name", "emailAddress"]
}

View File

@@ -33,10 +33,7 @@ use Espo\Services\Record;
/**
* @extends Record<\Espo\Core\Templates\Entities\Event>
* @deprecated Left for backward compatibility.
*/
class Event extends Record
{
protected $validateRequiredSkipFieldList = [
'dateEnd'
];
}
{}

View File

@@ -29,14 +29,17 @@
namespace Espo\Core\Utils\Acl;
use Espo\Core\Acl\Exceptions\NotAvailable;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\Core\AclManager;
use Espo\Core\Portal\AclManagerContainer as PortalAclManagerContainer;
use Espo\Core\ApplicationState;
use RuntimeException;
/**
* @todo Use WeakMap (User as a key).
*/
class UserAclManagerProvider
{
/** @var array<string, AclManager> */
@@ -49,6 +52,9 @@ class UserAclManagerProvider
private ApplicationState $applicationState
) {}
/**
* @throws NotAvailable
*/
public function get(User $user): AclManager
{
$key = $user->hasId() ? $user->getId() : spl_object_hash($user);
@@ -60,6 +66,9 @@ class UserAclManagerProvider
return $this->map[$key];
}
/**
* @throws NotAvailable
*/
private function load(User $user): AclManager
{
$aclManager = $this->aclManager;
@@ -72,7 +81,7 @@ class UserAclManagerProvider
->findOne();
if (!$portal) {
throw new RuntimeException("No portal for portal user '" . $user->getId() . "'.");
throw new NotAvailable("No portal for portal user '" . $user->getId() . "'.");
}
$aclManager = $this->portalAclManagerContainer->get($portal);

View File

@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Client;
use Espo\Core\Utils\Metadata;
class LoaderParamsProvider
{
public function __construct(
private Metadata $metadata
) {}
public function getLibsConfig(): object
{
return (object) $this->metadata->get(['app', 'jsLibs'], []);
}
public function getAliasMap(): object
{
$map = (object) [];
/** @var array<string, array<string, mixed>> $libs */
$libs = $this->metadata->get(['app', 'jsLibs'], []);
foreach ($libs as $name => $item) {
/** @var ?string[] $aliases */
$aliases = $item['aliases'] ?? null;
$map->$name = 'lib!' . $name;
if ($aliases) {
foreach ($aliases as $alias) {
$map->$alias = 'lib!' . $name;
}
}
}
return $map;
}
}

View File

@@ -32,6 +32,7 @@ namespace Espo\Core\Utils;
use Espo\Core\Api\Response;
use Espo\Core\Api\ResponseWrapper;
use Espo\Core\Utils\Client\DevModeJsFileListProvider;
use Espo\Core\Utils\Client\LoaderParamsProvider;
use Espo\Core\Utils\File\Manager as FileManager;
use Slim\Psr7\Response as Psr7Response;
@@ -42,14 +43,16 @@ use Slim\ResponseEmitter;
*/
class ClientManager
{
protected string $mainHtmlFilePath = 'html/main.html';
protected string $runScript = "app.start();";
private string $mainHtmlFilePath = 'html/main.html';
private string $runScript = 'app.start();';
private string $favicon = 'client/img/favicon.ico';
private string $favicon196 = 'client/img/favicon196x196.png';
private string $basePath = '';
private string $libsConfigPath = 'client/cfg/libs.json';
private string $apiUrl = 'api/v1';
private string $nonce;
private const APP_DESCRIPTION = "EspoCRM - Open Source CRM application.";
private const APP_DESCRIPTION = "EspoCRM Open Source CRM application.";
public function __construct(
private Config $config,
@@ -57,9 +60,9 @@ class ClientManager
private Metadata $metadata,
private FileManager $fileManager,
private DevModeJsFileListProvider $devModeJsFileListProvider,
private Module $module
private Module $module,
private LoaderParamsProvider $loaderParamsProvider
) {
$this->nonce = Util::generateKey();
}
@@ -73,15 +76,6 @@ class ClientManager
return $this->basePath;
}
protected function getCacheTimestamp(): int
{
if (!$this->config->get('useCache')) {
return time();
}
return $this->config->get('cacheTimestamp', 0);
}
/**
* @todo Move to a separate class.
*/
@@ -113,7 +107,7 @@ class ClientManager
return;
}
$scriptSrc = "script-src 'self' 'nonce-{$this->nonce}' 'unsafe-eval'";
$scriptSrc = "script-src 'self' 'nonce-$this->nonce' 'unsafe-eval'";
$scriptSourceList = $this->config->get('clientCspScriptSourceList') ?? [];
@@ -157,86 +151,51 @@ class ClientManager
*/
public function render(?string $runScript = null, ?string $htmlFilePath = null, array $vars = []): string
{
if (is_null($runScript)) {
$runScript = $this->runScript;
}
if (is_null($htmlFilePath)) {
$htmlFilePath = $this->mainHtmlFilePath;
}
$runScript ??= $this->runScript;
$htmlFilePath ??= $this->mainHtmlFilePath;
$cacheTimestamp = $this->getCacheTimestamp();
$jsFileList = $this->getJsFileList();
$appTimestamp = $this->getAppTimestamp();
if ($this->config->get('isDeveloperMode')) {
$useCache = $this->config->get('useCacheInDeveloperMode');
$loaderCacheTimestamp = 'null';
if ($this->isDeveloperMode()) {
$useCache = $this->useCacheInDeveloperMode();
$loaderCacheTimestamp = null;
}
else {
$useCache = $this->config->get('useCache');
$loaderCacheTimestamp = $cacheTimestamp;
$useCache = $this->useCache();
$loaderCacheTimestamp = $appTimestamp;
}
$cssFileList = $this->metadata->get(['app', 'client', 'cssList'], []);
$linkList = $this->metadata->get(['app', 'client', 'linkList'], []);
$favicon196Path = $this->metadata->get(['app', 'client', 'favicon196']) ?? $this->favicon196;
$faviconPath = $this->metadata->get(['app', 'client', 'favicon']) ?? $this->favicon;
$scriptsHtml = '';
$scriptsHtml = implode('',
array_map(fn ($file) => $this->getScriptItemHtml($file, $appTimestamp), $jsFileList)
);
foreach ($jsFileList as $jsFile) {
$src = $this->basePath . $jsFile . '?r=' . $cacheTimestamp;
$additionalStyleSheetsHtml = implode('',
array_map(fn ($file) => $this->getCssItemHtml($file, $appTimestamp), $cssFileList)
);
$scriptsHtml .= "\n " .
"<script type=\"text/javascript\" src=\"{$src}\" data-base-path=\"{$this->basePath}\"></script>";
}
$additionalStyleSheetsHtml = '';
foreach ($cssFileList as $cssFile) {
$src = $this->basePath . $cssFile . '?r=' . $cacheTimestamp;
$additionalStyleSheetsHtml .= "\n <link rel=\"stylesheet\" href=\"{$src}\">";
}
$linksHtml = '';
foreach ($linkList as $item) {
$href = $this->basePath . $item['href'];
if (empty($item['noTimestamp'])) {
$href .= '?r=' . $cacheTimestamp;
}
$as = $item['as'] ?? '';
$rel = $item['rel'] ?? '';
$type = $item['type'] ?? '';
$additionalPlaceholder = '';
if (!empty($item['crossorigin'])) {
$additionalPlaceholder .= ' crossorigin';
}
$linksHtml .= "\n " .
"<link rel=\"{$rel}\" href=\"{$href}\" as=\"{$as}\" as=\"{$type}\"{$additionalPlaceholder}>";
}
$favicon196Path = $this->metadata->get(['app', 'client', 'favicon196']) ??
'client/img/favicon196x196.png';
$faviconPath = $this->metadata->get(['app', 'client', 'favicon']) ?? 'client/img/favicon.ico';
$linksHtml = implode('',
array_map(fn ($item) => $this->getLinkItemHtml($item, $appTimestamp), $linkList)
);
$internalModuleList = array_map(
function (string $moduleName): string {
return Util::fromCamelCase($moduleName, '-');
},
fn ($moduleName) => Util::fromCamelCase($moduleName, '-'),
$this->module->getInternalList()
);
$data = [
'applicationId' => 'espocrm-application-id',
'apiUrl' => 'api/v1',
'apiUrl' => $this->apiUrl,
'applicationName' => $this->config->get('applicationName', 'EspoCRM'),
'cacheTimestamp' => $cacheTimestamp,
'loaderCacheTimestamp' => $loaderCacheTimestamp,
'appTimestamp' => $appTimestamp,
'loaderCacheTimestamp' => Json::encode($loaderCacheTimestamp),
'stylesheet' => $this->themeManager->getStylesheet(),
'runScript' => $runScript,
'basePath' => $this->basePath,
@@ -248,16 +207,24 @@ class ClientManager
'favicon196Path' => $favicon196Path,
'faviconPath' => $faviconPath,
'ajaxTimeout' => $this->config->get('ajaxTimeout') ?? 60000,
'libsConfigPath' => $this->libsConfigPath,
'internalModuleList' => Json::encode($internalModuleList),
'bundledModuleList' => Json::encode($this->getBundledModuleList()),
'applicationDescription' => $this->config->get('applicationDescription') ?? self::APP_DESCRIPTION,
'nonce' => $this->nonce,
'loaderParams' => Json::encode([
'basePath' => $this->basePath,
'cacheTimestamp' => $loaderCacheTimestamp,
'internalModuleList' => $internalModuleList,
'transpiledModuleList' => $this->getTranspiledModuleList(),
'libsConfig' => $this->loaderParamsProvider->getLibsConfig(),
'aliasMap' => $this->loaderParamsProvider->getAliasMap(),
]),
];
$html = $this->fileManager->getContents($htmlFilePath);
foreach ($vars as $key => $value) {
$html = str_replace('{{'.$key.'}}', $value, $html);
$html = str_replace('{{' . $key . '}}', $value, $html);
}
foreach ($data as $key => $value) {
@@ -265,7 +232,7 @@ class ClientManager
continue;
}
$html = str_replace('{{'.$key.'}}', $value, $html);
$html = str_replace('{{' . $key . '}}', $value, $html);
}
return $html;
@@ -276,10 +243,10 @@ class ClientManager
*/
private function getJsFileList(): array
{
if ($this->config->get('isDeveloperMode')) {
if ($this->isDeveloperMode()) {
return array_merge(
$this->getDeveloperModeBundleLibFileList(),
$this->metadata->get(['app', 'client', 'developerModeScriptList']) ?? [],
$this->getDeveloperModeBundleLibFileList(),
);
}
@@ -293,4 +260,128 @@ class ClientManager
{
return $this->devModeJsFileListProvider->get();
}
private function isDeveloperMode(): bool
{
return (bool) $this->config->get('isDeveloperMode');
}
private function useCache(): bool
{
return (bool) $this->config->get('useCache');
}
private function useCacheInDeveloperMode(): bool
{
return (bool) $this->config->get('useCacheInDeveloperMode');
}
private function getCacheTimestamp(): int
{
if (!$this->useCache()) {
return time();
}
return $this->config->get('cacheTimestamp', 0);
}
private function getAppTimestamp(): int
{
if (!$this->useCache()) {
return time();
}
return $this->config->get('appTimestamp', 0);
}
private function getScriptItemHtml(string $file, int $appTimestamp): string
{
$src = $this->basePath . $file . '?r=' . $appTimestamp;
return $this->getTabHtml() .
"<script type=\"text/javascript\" src=\"$src\" data-base-path=\"$this->basePath\"></script>";
}
private function getCssItemHtml(string $file, int $appTimestamp): string
{
$src = $this->basePath . $file . '?r=' . $appTimestamp;
return $this->getTabHtml() . "<link rel=\"stylesheet\" href=\"$src\">";
}
/**
* @param array{
* href: string,
* noTimestamp?: bool,
* as?: string,
* rel?: string,
* type?: string,
* crossorigin?: bool,
* } $item
*/
private function getLinkItemHtml(array $item, int $appTimestamp): string
{
$href = $this->basePath . $item['href'];
if (empty($item['noTimestamp'])) {
$href .= '?r=' . $appTimestamp;
}
$as = $item['as'] ?? '';
$rel = $item['rel'] ?? '';
$type = $item['type'] ?? '';
$part = '';
if ($item['crossorigin'] ?? false) {
$part .= ' crossorigin';
}
return $this->getTabHtml() .
"<link rel=\"$rel\" href=\"$href\" as=\"$as\" as=\"$type\"$part>";
}
private function getTabHtml(): string
{
return "\n ";
}
/**
* @return string[]
*/
private function getTranspiledModuleList(): array
{
$modules = array_values(array_filter(
$this->module->getList(),
fn ($item) => $this->module->get([$item, 'jsTranspiled'])
));
return array_map(
fn ($item) => Util::fromCamelCase($item, '-'),
$modules
);
}
/**
* @return string[]
*/
private function getBundledModuleList(): array
{
$modules = array_values(array_filter(
$this->module->getList(),
fn ($item) => $this->module->get([$item, 'bundled'])
));
return array_map(
fn ($item) => Util::fromCamelCase($item, '-'),
$modules
);
}
/**
* @since 8.0.0
*/
public function setApiUrl(string $apiUrl): void
{
$this->apiUrl = $apiUrl;
}
}

View File

@@ -29,16 +29,7 @@
namespace Espo\Core\Utils\Database;
use Espo\Core\Utils\Config;
class ConfigDataProvider
interface ConfigDataProvider
{
private const DEFAULT_PLATFORM = 'Mysql';
public function __construct(private Config $config) {}
public function getPlatform(): string
{
return $this->config->get('database.platform') ?? self::DEFAULT_PLATFORM;
}
public function getPlatform(): string;
}

View File

@@ -0,0 +1,44 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database;
use Espo\Core\Utils\Config;
class DefaultConfigDataProvider implements ConfigDataProvider
{
private const DEFAULT_PLATFORM = 'Mysql';
public function __construct(private Config $config) {}
public function getPlatform(): string
{
return $this->config->get('database.platform') ?? self::DEFAULT_PLATFORM;
}
}

View File

@@ -57,7 +57,7 @@ class PostgresqlDetailsProvider implements DetailsProvider
public function getServerVersion(): string
{
return (string) $this->getParam('version');
return (string) $this->getFullDatabaseVersion();
}
public function getParam(string $name): ?string

View File

@@ -92,15 +92,8 @@ class Converter
/** @var array<string, mixed> */
private array $idParams = [];
/**
* Permitted entityDefs parameters which will be copied to ormMetadata.
*
* @var string[]
*/
private array $permittedEntityOptions = [
'indexes',
'additionalTables',
];
/** @var string[] */
private array $copyEntityProperties = ['indexes'];
private IndexHelper $indexHelper;
@@ -167,11 +160,13 @@ class Converter
$ormMetadata,
$this->createEntityTypesFromRelations($entityType, $entityOrmMetadata)
);
}
foreach ($entityDefs as $entityMetadata) {
/** @var array<string, array<string, mixed>> $ormMetadata */
$ormMetadata = Util::merge(
$ormMetadata,
$this->createAdditionalEntityTypes($entityOrmMetadata)
$this->obtainAdditionalTablesOrmMetadata($entityMetadata)
);
}
@@ -194,17 +189,17 @@ class Converter
$ormMetadata = [];
$ormMetadata[$entityType] = [
'fields' => [],
'attributes' => [],
'relations' => [],
];
foreach ($this->permittedEntityOptions as $optionName) {
foreach ($this->copyEntityProperties as $optionName) {
if (isset($entityMetadata[$optionName])) {
$ormMetadata[$entityType][$optionName] = $entityMetadata[$optionName];
}
}
$ormMetadata[$entityType]['fields'] = $this->convertFields($entityType, $entityMetadata);
$ormMetadata[$entityType]['attributes'] = $this->convertFields($entityType, $entityMetadata);
$ormMetadata = $this->correctFields($entityType, $ormMetadata);
@@ -224,7 +219,7 @@ class Converter
$ormMetadata[$entityType]['collection']['orderBy'] = $collectionDefs['orderByColumn'];
}
else if (array_key_exists('orderBy', $collectionDefs)) {
if (array_key_exists($collectionDefs['orderBy'], $ormMetadata[$entityType]['fields'])) {
if (array_key_exists($collectionDefs['orderBy'], $ormMetadata[$entityType]['attributes'])) {
$ormMetadata[$entityType]['collection']['orderBy'] = $collectionDefs['orderBy'];
}
}
@@ -245,15 +240,18 @@ class Converter
*/
private function afterFieldsProcess(array $ormMetadata): array
{
foreach ($ormMetadata as $entityType => &$entityParams) {
foreach ($entityParams['fields'] as $attribute => &$attributeParams) {
foreach ($ormMetadata as /*$entityType =>*/ &$entityParams) {
if (empty($entityParams['attributes'])) {
print_r($entityParams);
}
foreach ($entityParams['attributes'] as $attribute => &$attributeParams) {
// Remove fields without type.
if (
!isset($attributeParams['type']) &&
(!isset($attributeParams['notStorable']) || $attributeParams['notStorable'] === false)
) {
unset($entityParams['fields'][$attribute]);
unset($entityParams['attributes'][$attribute]);
continue;
}
@@ -316,7 +314,7 @@ class Converter
private function afterProcess(array $ormMetadata): array
{
foreach ($ormMetadata as $entityType => &$entityParams) {
foreach ($entityParams['fields'] as $attribute => &$attributeParams) {
foreach ($entityParams['attributes'] as $attribute => &$attributeParams) {
$attributeType = $attributeParams['type'] ?? null;
switch ($attributeType) {
@@ -337,7 +335,7 @@ class Converter
*/
private function obtainForeignType(array $data, string $entityType, string $attribute): ?string
{
$params = $data[$entityType]['fields'][$attribute] ?? [];
$params = $data[$entityType]['attributes'][$attribute] ?? [];
$foreign = $params['foreign'] ?? null;
$relation = $params['relation'] ?? null;
@@ -354,7 +352,7 @@ class Converter
return null;
}
$foreignParams = $data[$foreignEntityType]['fields'][$foreign] ?? [];
$foreignParams = $data[$foreignEntityType]['attributes'][$foreign] ?? [];
return $foreignParams['type'] ?? null;
}
@@ -397,7 +395,7 @@ class Converter
$fieldTypeMetadata = $this->metadataHelper->getFieldDefsByType($attributeParams);
$fieldDefs = $this->convertField($entityType, $attribute, $attributeParams, $fieldTypeMetadata);
$fieldDefs = $this->convertField($attributeParams, $fieldTypeMetadata);
if ($fieldDefs !== false) {
if (isset($output[$attribute]) && !in_array($attribute, $unmergedFields)) {
@@ -442,23 +440,25 @@ class Converter
{
$entityMetadata = $ormMetadata[$entityType];
foreach ($entityMetadata['fields'] as $field => $fieldParams) {
$fieldType = $fieldParams['type'] ?? null;
foreach ($entityMetadata['attributes'] as $field => $itemParams) {
$type = $itemParams['type'] ?? null;
if (!$fieldType) {
if (!$type) {
continue;
}
/** @var ?class-string<FieldConverter> $className */
$className = $this->metadata->get(['fields', $fieldType, 'converterClassName']);
$className =
$this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'converterClassName']) ??
$this->metadata->get(['fields', $type, 'converterClassName']);
if ($className) {
$toUnset =
!in_array('', $this->metadata->get(['fields', $fieldType, 'actualFields']) ?? []) &&
!in_array('', $this->metadata->get(['fields', $fieldType, 'notActualFields']) ?? []);
!in_array('', $this->metadata->get(['fields', $type, 'actualFields']) ?? []) &&
!in_array('', $this->metadata->get(['fields', $type, 'notActualFields']) ?? []);
if ($toUnset) {
$ormMetadata = Util::unsetInArray($ormMetadata, [$entityType => ['fields.' . $field]]);
$ormMetadata = Util::unsetInArray($ormMetadata, [$entityType => ['attributes.' . $field]]);
}
$converter = $this->injectableFactory->create($className);
@@ -480,7 +480,7 @@ class Converter
if ($defaultAttributes && array_key_exists($field, $defaultAttributes)) {
$defaultMetadataPart = [
$entityType => [
'fields' => [
'attributes' => [
$field => [
'default' => $defaultAttributes[$field],
]
@@ -499,19 +499,19 @@ class Converter
if ($scopeDefs['stream'] ?? false) {
if (!isset($entityMetadata['fields']['isFollowed'])) {
$ormMetadata[$entityType]['fields']['isFollowed'] = [
$ormMetadata[$entityType]['attributes']['isFollowed'] = [
'type' => Entity::VARCHAR,
'notStorable' => true,
'notExportable' => true,
];
$ormMetadata[$entityType]['fields']['followersIds'] = [
$ormMetadata[$entityType]['attributes']['followersIds'] = [
'type' => Entity::JSON_ARRAY,
'notStorable' => true,
'notExportable' => true,
];
$ormMetadata[$entityType]['fields']['followersNames'] = [
$ormMetadata[$entityType]['attributes']['followersNames'] = [
'type' => Entity::JSON_OBJECT,
'notStorable' => true,
'notExportable' => true,
@@ -521,7 +521,7 @@ class Converter
// @todo Refactor.
if ($this->metadata->get(['entityDefs', $entityType, 'optimisticConcurrencyControl'])) {
$ormMetadata[$entityType]['fields']['versionNumber'] = [
$ormMetadata[$entityType]['attributes']['versionNumber'] = [
'type' => Entity::INT,
'dbType' => Types::BIGINT,
'notExportable' => true,
@@ -537,8 +537,6 @@ class Converter
* @return array<string, mixed>|false
*/
private function convertField(
string $entityType,
string $field,
array $fieldParams,
?array $fieldTypeMetadata = null
) {
@@ -742,8 +740,8 @@ class Converter
$defs['indexes'] ??= [];
if (isset($defs['fields'])) {
$indexList = self::getEntityIndexListFromAttributes($defs['fields']);
if (isset($defs['attributes'])) {
$indexList = self::getEntityIndexListFromAttributes($defs['attributes']);
foreach ($indexList as $indexName => $indexParams) {
if (!isset($defs['indexes'][$indexName])) {
@@ -824,7 +822,7 @@ class Converter
* @param array<string, mixed> $defs
* @return array<string, mixed>
*/
private function createAdditionalEntityTypes(array $defs): array
private function obtainAdditionalTablesOrmMetadata(array $defs): array
{
/** @var array<string, array<string, mixed>> $additionalDefs */
$additionalDefs = $defs['additionalTables'] ?? [];
@@ -840,6 +838,17 @@ class Converter
$this->applyIndexes($additionalDefs, $itemEntityType);
}
// For backward compatibility. Actual as of v8.0.
// @todo Remove in v10.0.
// @todo Add deprecation warning in v9.0. If 'fields' is set.
foreach ($additionalDefs as &$entityDefs) {
if (!isset($entityDefs['attributes'])) {
$entityDefs['attributes'] = $entityDefs['fields'] ?? [];
unset($entityDefs['fields']);
}
}
return $additionalDefs;
}
@@ -862,7 +871,7 @@ class Converter
$itemDefs = [
'skipRebuild' => true,
'fields' => [
'attributes' => [
'id' => [
'type' => Entity::ID,
'autoincrement' => true,
@@ -876,7 +885,7 @@ class Converter
if (!$relationDefs->hasMidKey()) {
throw new LogicException(
"Bad manyMany relation {$name} in {$entityType}. Might be not defined on the other side.");
"Bad manyMany relation $name in $entityType. Might be not defined on the other side.");
}
$key1 = $relationDefs->getMidKey();
@@ -885,7 +894,7 @@ class Converter
$midKeys = [$key1, $key2];
foreach ($midKeys as $key) {
$itemDefs['fields'][$key] = [
$itemDefs['attributes'][$key] = [
'type' => Entity::FOREIGN_ID,
];
}
@@ -907,7 +916,7 @@ class Converter
$columnDefs['default'] = $attributeDefs->getParam('default');
}
$itemDefs['fields'][$columnName] = $columnDefs;
$itemDefs['attributes'][$columnName] = $columnDefs;
}
foreach ($relationDefs->getIndexList() as $indexDefs) {

View File

@@ -125,8 +125,7 @@ class EntityDefs
$attributesData[$name] = $attributeDefs->toAssoc();
}
// @todo Change to attributes.
$data['fields'] = $attributesData;
$data['attributes'] = $attributesData;
}
if (count($this->relations)) {

View File

@@ -56,6 +56,13 @@ class LinkMultiple implements FieldConverter
'fieldType' => 'linkMultiple',
]);
/** @var array<string, mixed> $defaults */
$defaults = $fieldDefs->getParam('defaultAttributes') ?? [];
if (array_key_exists($idsName, $defaults)) {
$idsDefs = $idsDefs->withDefault($defaults[$idsName]);
}
$namesDefs = AttributeDefs::create($namesName)
->withType(AttributeType::JSON_OBJECT)
->withNotStorable()

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