Compare commits

...

535 Commits
5.8.0 ... 5.9.4

Author SHA1 Message Date
Yuri Kuznetsov
235d8f2264 package lock version 2020-09-23 11:14:29 +03:00
Yuri Kuznetsov
9cfab5a7ea upgrade fix 2020-09-23 10:17:07 +03:00
Yuri Kuznetsov
86a85ba177 Merge branch 'hotfix/5.9.4' of github.com:espocrm/espocrm into hotfix/5.9.4 2020-09-16 13:45:25 +03:00
Yuri Kuznetsov
36a4cb2451 fix formula int parsing 2020-09-16 13:44:56 +03:00
Eymen Elkum
6f60a73c62 minor fix (#1785) 2020-09-15 14:33:53 +03:00
Yuri Kuznetsov
72fd9184f4 fix link manager ui 2020-09-08 17:42:19 +03:00
Yuri Kuznetsov
0a5ae621b4 fix complex text 2020-08-25 18:11:38 +03:00
Yuri Kuznetsov
4e588f6d0e cs fix 2020-08-25 18:08:06 +03:00
Yuri Kuznetsov
f60c5e29de Merge branch 'hotfix/5.9.4' of https://github.com/espocrm/espocrm into hotfix/5.9.4 2020-08-25 18:04:36 +03:00
Yuri Kuznetsov
1662cfd97d notification form fix 2020-08-25 18:01:15 +03:00
Yuri Kuznetsov
0fd8f9d29f email index change 2020-08-10 13:42:03 +03:00
Yuri Kuznetsov
bf033c31af date fix 2020-08-08 10:33:20 +03:00
Yuri Kuznetsov
aa788a17d9 fix date field issue 2020-08-07 18:00:17 +03:00
Yuri Kuznetsov
036ad99ba6 fix deprecation 2020-08-02 13:45:32 +03:00
Yuri Kuznetsov
7fda7abbe6 attachments fixes 2020-07-23 17:46:00 +03:00
Yuri Kuznetsov
fa0cf9c9fd cleanup 2020-07-18 15:02:25 +03:00
Yuri Kuznetsov
3c651f8633 lang fixes 2020-07-18 14:47:02 +03:00
Yuri Kuznetsov
2b5f1c28e8 lang.js refactoring 2020-07-18 14:41:48 +03:00
Yuri Kuznetsov
7fea881d17 refactor po 2020-07-18 14:21:05 +03:00
Yuri Kuznetsov
ada64bba0c fix dayOfWeek 2020-07-18 12:22:43 +03:00
Yuri Kuznetsov
8fe96b140a tcpdf header 2020-07-16 20:14:48 +03:00
Yuri Kuznetsov
3b25a1a001 error message 4 seconds 2020-07-16 19:59:46 +03:00
Yuri Kuznetsov
f4d98f177c fix typos 2020-07-16 19:57:04 +03:00
Yuri Kuznetsov
9f4f38bb0d webhook verify peer 2020-07-16 19:50:53 +03:00
Yuri Kuznetsov
e76490880a v 2020-07-13 10:48:13 +03:00
Yuri Kuznetsov
e12bb1320c fix cookies 2020-07-13 10:47:57 +03:00
Yuri Kuznetsov
c84abfb542 do not display auth error message 2020-07-08 16:15:30 +03:00
Yuri Kuznetsov
656f66f567 fix menu items 2020-07-08 16:07:27 +03:00
Yuri Kuznetsov
eca5bdea71 fix formula where 2020-07-01 08:38:14 +03:00
Yuri Kuznetsov
2ab4173e85 oauth client fix 2020-06-30 08:51:29 +03:00
Yuri Kuznetsov
195db973dc get image url use ssl verify peer 2020-06-30 08:50:09 +03:00
Yuri Kuznetsov
cd5537cc26 fix user generate new possword hidden if group email account used 2020-06-27 12:36:56 +03:00
Yuri Kuznetsov
94008f5a53 fix metadata service null acl check 2020-06-25 08:35:47 +03:00
Yuri Kuznetsov
bf410b2258 cleanup 2020-06-24 09:02:23 +03:00
Yuri Kuznetsov
0f2a26b744 formula comments fix 2020-06-22 21:26:51 +03:00
Yuri Kuznetsov
c11fd843d0 Merge branch 'hotfix/5.9.3' of github.com:espocrm/espocrm into hotfix/5.9.3 2020-06-22 13:03:18 +03:00
Yuri Kuznetsov
70a374004d fix navbar verical scroll issue 2020-06-22 13:02:08 +03:00
Yuri Kuznetsov
0b218833af fix q0 queue 2020-06-16 15:44:32 +03:00
Yuri Kuznetsov
4a1e5c974c fix import 2020-06-15 16:23:58 +03:00
Yuri Kuznetsov
b08083173a reminder 7 days 2020-06-15 11:07:26 +03:00
Yuri Kuznetsov
c24e7a6939 version 2020-06-15 10:47:00 +03:00
Yuri Kuznetsov
ce99671583 layout set error msg 2020-06-15 10:39:28 +03:00
Yuri Kuznetsov
d36e5cc0c1 fix select create related 2020-06-12 14:44:21 +03:00
Yuri Kuznetsov
13ae2d27c8 fix external account client manager 2020-06-12 13:43:47 +03:00
Yuri Kuznetsov
ea5c76f012 oauth changes 2020-06-11 12:13:39 +03:00
Yuri Kuznetsov
acb9b50d14 stream internal post button red color 2020-06-10 11:06:51 +03:00
Yuri Kuznetsov
968fc7ad30 fix email account form 2020-06-10 10:32:59 +03:00
Yuri Kuznetsov
bb6d7598a1 email account layout change 2020-06-10 10:27:42 +03:00
Yuri Kuznetsov
cd9481670a oauth fix 2020-06-09 15:33:42 +03:00
Yuri Kuznetsov
505e9e278b oauth race condition fix 2020-06-09 13:50:25 +03:00
Yuri Kuznetsov
7e59888fb2 oauth fix 2020-06-06 18:37:34 +03:00
Yuri Kuznetsov
74f2d7f1ea oauth: handle token expiration; factory in client manager 2020-06-06 18:12:39 +03:00
Yuri Kuznetsov
f319be61d2 fix select related 2020-06-06 10:04:02 +03:00
Yuri Kuznetsov
2cc4c8f974 fix mail sender 2020-06-04 14:49:50 +03:00
Yuri Kuznetsov
a3aa74013a oauth storing new refresh token 2020-06-03 19:26:57 +03:00
Yuri Kuznetsov
509b3affd1 email account: imap handler 2020-06-03 10:10:34 +03:00
Yuri Kuznetsov
ad9b2b54dd scheduled job log panel fix 2020-06-02 23:01:50 +03:00
Yuri Kuznetsov
247eb00963 fix field-manager.js 2020-06-02 20:07:49 +03:00
Yuri Kuznetsov
c0b59a49bf fix horizontal navbar 2020-06-02 20:06:18 +03:00
Yuri Kuznetsov
b525afe154 less re-grouping 2 2020-06-02 19:59:57 +03:00
Yuri Kuznetsov
b449263854 less re-grouping 2020-06-02 15:43:03 +03:00
Yuri Kuznetsov
12550c3d0a password recovery improvements 2020-05-31 18:50:54 +03:00
Yuri Kuznetsov
803a4ffb7f scheduling text fix 2020-05-28 13:41:11 +03:00
Yuri Kuznetsov
57bfea4d70 fix email body plain 2020-05-27 16:38:33 +03:00
Yuri Kuznetsov
86f460866a fix tests 2020-05-27 16:16:55 +03:00
Yuri Kuznetsov
8515157916 relationship panel: create from select modal 2020-05-27 12:01:14 +03:00
Yuri Kuznetsov
5bd18dee49 cs fix 2020-05-27 10:46:33 +03:00
Yuri Kuznetsov
88ea5e7d6c mail parser body plain change 2020-05-27 10:45:40 +03:00
Yuri Kuznetsov
3839335508 fix email body plain 2020-05-27 10:45:19 +03:00
Yuri Kuznetsov
b8e94b52aa detail layout panel label changes 2020-05-26 20:08:27 +03:00
Yuri Kuznetsov
eeda450405 pdf: ifMultipleOf 2020-05-26 13:20:22 +03:00
Yuri Kuznetsov
d051bd7df2 googleMapsImage helper name 2020-05-26 12:57:53 +03:00
Yuri Kuznetsov
690a265450 date-picker change 2020-05-26 10:36:12 +03:00
Yuri Kuznetsov
8bec0a3caf change 1 2020-05-25 16:04:53 +03:00
Yuri Kuznetsov
a89463367e v 2020-05-25 12:20:55 +03:00
Yuri Kuznetsov
05dbcbd917 field view fixes 2020-05-25 12:17:21 +03:00
Yuri Kuznetsov
774bde3e20 websocket update fixes 2020-05-25 11:46:51 +03:00
Yuri Kuznetsov
90589e0d26 scheduling color 2020-05-25 11:25:03 +03:00
Yuri Kuznetsov
be199235f1 Merge branch 'hotfix/5.9.2' of https://github.com/espocrm/espocrm into hotfix/5.9.2 2020-05-25 09:47:55 +03:00
Yuri Kuznetsov
1b2d67b027 massEmailSiteUrl admin only 2020-05-25 09:41:09 +03:00
Eymen Elkum
7bc56fc864 add missing import (#1720) 2020-05-24 11:10:23 +03:00
Yuri Kuznetsov
c706ddc809 person name field: fix typo 2020-05-22 21:55:56 +03:00
Yuri Kuznetsov
1b6b2ea140 pdf: google maps 2020-05-22 15:45:06 +03:00
Yuri Kuznetsov
c1db037fc2 htmlzier additions 2020-05-22 14:31:29 +03:00
Yuri Kuznetsov
838e9ea773 post preview: links in new tab 2020-05-22 13:41:46 +03:00
Yuri Kuznetsov
10efe3513c scheduled job: expression validation 2020-05-22 11:27:46 +03:00
Yuri Kuznetsov
6d301092b2 Merge branch 'hotfix/5.9.2' of https://github.com/espocrm/espocrm into hotfix/5.9.2 2020-05-22 11:13:14 +03:00
Yuri Kuznetsov
5acb5da8fa scheduled job: cron description 2020-05-22 11:12:30 +03:00
Eymen Elkum
3ad343b274 add missing use in settings.php service (#1711) 2020-05-22 09:45:34 +03:00
Yuri Kuznetsov
c5fd749b21 formula while 2020-05-21 14:35:06 +03:00
Yuri Kuznetsov
1f8e0f16c7 formula randomInt function 2020-05-21 13:37:17 +03:00
Yuri Kuznetsov
92babe7fbc fix gruntfile 2020-05-21 13:34:43 +03:00
Yuri Kuznetsov
fb684837f8 role table ui improvement 2020-05-21 11:40:59 +03:00
Yuri Kuznetsov
669413b184 mass email site url param 2020-05-20 16:44:10 +03:00
Yuri Kuznetsov
a945a01767 password request send support group email account smtp 2020-05-20 13:04:41 +03:00
Yuri Kuznetsov
b4664eafa5 fix password recovery disabled if smtpServer is empty 2020-05-20 12:53:07 +03:00
Yuri Kuznetsov
34b06b83aa Merge branch 'hotfix/5.9.1' of github.com:espocrm/espocrm into hotfix/5.9.1 2020-05-19 11:55:41 +03:00
Yuri Kuznetsov
da14681077 fix global search 2020-05-19 08:32:39 +03:00
Yuri Kuznetsov
53e91ad683 fix mail sender 2020-05-19 00:47:14 +03:00
Yuri Kuznetsov
06434bef99 fix panel container 2020-05-18 19:27:01 +03:00
Yuri Kuznetsov
16fab0ced0 v 2020-05-18 11:21:49 +03:00
Yuri Kuznetsov
bc3e531447 multi enum match any word prop 2020-05-18 11:19:40 +03:00
Yuri Kuznetsov
af038e3306 record controller forbidden messages 2020-05-18 11:08:46 +03:00
Yuri Kuznetsov
1a06b83d9d external account changes 2020-05-18 09:43:17 +03:00
Yuri Kuznetsov
bcc3cfd143 fix wrong response content type 2020-05-15 15:40:21 +03:00
Yuri Kuznetsov
b73123c137 fix array validation 2020-05-14 22:28:48 +03:00
Yuri Kuznetsov
8c47f94172 stream post field fixes 2020-05-13 16:30:10 +03:00
Yuri Kuznetsov
b03e17ce22 parent to child fix 2020-05-13 15:24:40 +03:00
Yuri Kuznetsov
c0ed352293 wysiwyg: fix insert imege from clipboard 2020-05-12 19:50:12 +03:00
Yuri Kuznetsov
e4eb8794ff stream image paste 2020-05-12 18:01:04 +03:00
Yuri Kuznetsov
830ba03448 relationship panel setup last 2020-05-07 17:15:26 +03:00
Yuri Kuznetsov
45b2f267de orm fix 2020-05-07 16:49:26 +03:00
Yuri Kuznetsov
891c0d6657 contact: has portal user field 2020-05-07 16:32:36 +03:00
Yuri Kuznetsov
8583352033 failed login attempt count optimization 2020-05-07 09:47:25 +03:00
Yuri Kuznetsov
76ea81465e Merge branch 'hotfix/5.8.6' 2020-05-06 12:29:39 +03:00
Yuri Kuznetsov
9f4b558936 fix mass relate 2020-05-06 12:29:24 +03:00
Yuri Kuznetsov
773d69c569 fix mass relate 2020-05-06 12:28:58 +03:00
Yuri Kuznetsov
f03997c927 add field css change 2020-05-06 12:16:55 +03:00
Yuri Kuznetsov
2fc9d405f8 field manager: add field info 2020-05-06 12:14:54 +03:00
Yuri Kuznetsov
4278895995 Merge branch 'hotfix/5.8.6' 2020-05-06 11:02:43 +03:00
Yuri Kuznetsov
af41c263ee fix order by email & phone 2020-05-06 11:02:11 +03:00
Yuri Kuznetsov
0466d6ce83 fix order by email & phone 2020-05-06 10:59:20 +03:00
Yuri Kuznetsov
3a4e1d6b11 hide 2fa field for not internal users 2020-05-05 11:28:16 +03:00
Yuri Kuznetsov
818f697b6b fix activities event from contact 2020-05-04 13:28:36 +03:00
Yuri Kuznetsov
83e0d926ac opted out fields: allow foreign access 2020-05-01 20:03:39 +03:00
Yuri Kuznetsov
782f22325a email filter user autocumplete filter 2020-05-01 15:41:45 +03:00
Yuri Kuznetsov
e896a7c960 assignment permission: allow empty for api user, multiple 2020-05-01 15:32:36 +03:00
Yuri Kuznetsov
1bb0035b34 assignment permission: allow empty for api user 2020-05-01 15:19:13 +03:00
Yuri Kuznetsov
5cdae539a6 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-05-01 14:56:49 +03:00
Taras Machyshyn
bf131b7a0e Added 'dbType' => 'longtext' for text fields 2020-05-01 14:29:28 +03:00
Yuri Kuznetsov
cb08de46b0 email queue link 2020-05-01 11:05:19 +03:00
Yuri Kuznetsov
81f1c3d6b2 menu item access data list 2020-05-01 11:05:07 +03:00
Yuri Kuznetsov
6b7da3b649 fix email queue error 2020-05-01 11:04:45 +03:00
Taras Machyshyn
05888ae2c3 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-30 11:55:33 +03:00
Taras Machyshyn
bc97156569 Displaying upgrading process 2020-04-30 11:54:52 +03:00
Yuri Kuznetsov
e9103eb796 Update README.md 2020-04-30 11:09:04 +03:00
Yuri Kuznetsov
62b8b30e3a Update SECURITY.md 2020-04-29 15:16:47 +03:00
Yuri Kuznetsov
81897253c5 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-29 14:12:06 +03:00
Yuri Kuznetsov
a242c9fd12 Merge branch 'hotfix/5.8.6' 2020-04-29 14:11:54 +03:00
Yuri Kuznetsov
b299220dd0 auth: do not allow another auth token if passwords are the same 2020-04-29 14:11:26 +03:00
Yuri Kuznetsov
3883881afc Create SECURITY.md 2020-04-29 14:09:27 +03:00
Taras Machyshyn
9061853666 DBAL: bug fixes 2020-04-28 17:35:45 +03:00
Yuri Kuznetsov
20c8da511f command version 2020-04-28 13:53:57 +03:00
Yuri Kuznetsov
d7f46e039e htaccess and web.config changes 2020-04-28 13:31:18 +03:00
Yuri Kuznetsov
0532524a50 console command extension 2020-04-28 11:30:16 +03:00
Yuri Kuznetsov
0f0bbff14c Merge branch 'hotfix/5.8.6' 2020-04-27 09:22:06 +03:00
Eymen Elkum
ce7fbe1471 allow custom view for portal navbar (#1687) 2020-04-27 09:19:04 +03:00
Yuri Kuznetsov
b53d96b9df fix email 2020-04-26 12:14:44 +03:00
Yuri Kuznetsov
a184f63c00 Merge branch 'hotfix/5.8.6' 2020-04-24 16:18:38 +03:00
Yuri Kuznetsov
75fb028f1a template fix 2020-04-24 16:18:24 +03:00
Yuri Kuznetsov
190c0d500a email index fix 2020-04-24 13:25:51 +03:00
Eymen Elkum
663fc784c1 fix typo variable (#1686) 2020-04-22 17:35:23 +03:00
Eymen Elkum
38b985defc fix typo variable (#1686) 2020-04-22 17:32:55 +03:00
Yuri Kuznetsov
9d5cdea95c Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-21 19:46:20 +03:00
Yuri Kuznetsov
004c2a4646 Merge branch 'hotfix/5.8.6' 2020-04-21 19:42:12 +03:00
Yuri Kuznetsov
05d88731e7 fix typo 2020-04-21 19:41:49 +03:00
Taras Machyshyn
5b0d244c8a Fix tests 2020-04-21 17:36:41 +03:00
Yuri Kuznetsov
60cf844f63 Merge branch 'hotfix/5.8.6' 2020-04-21 11:59:00 +03:00
Yuri Kuznetsov
02eba06a6d fix scheduling tooltip 2020-04-21 11:58:52 +03:00
Yuri Kuznetsov
c9e148f4d0 zzend to laminas 2020-04-21 11:39:26 +03:00
Yuri Kuznetsov
d7f3e79ce8 showing error message 2020-04-20 14:56:13 +03:00
Yuri Kuznetsov
5549a80e69 record service exception messages 2020-04-20 14:50:03 +03:00
Yuri Kuznetsov
82f7202ec3 merge improvements 2020-04-20 14:37:11 +03:00
Yuri Kuznetsov
77c1db6b25 contact account is inective merge disabled 2020-04-20 14:14:07 +03:00
Yuri Kuznetsov
adb7165807 fix notice 2020-04-18 10:42:12 +03:00
Yuri Kuznetsov
d1cfa84f06 email address autocomplete scopes 2020-04-17 17:30:57 +03:00
Yuri Kuznetsov
f9aa080fc8 Merge branch 'hotfix/5.8.6' 2020-04-17 14:11:54 +03:00
Yuri Kuznetsov
6d47e327cb formula find related many 2020-04-17 14:02:48 +03:00
Yuri Kuznetsov
a2f0d32c5f fix formula find related one filter 2020-04-17 13:41:54 +03:00
Yuri Kuznetsov
4e019a7e84 fix formula find related one filter 2020-04-17 13:40:18 +03:00
Yuri Kuznetsov
d3c7c6bfe4 formula find related one support belongs to 2020-04-17 13:20:39 +03:00
Yuri Kuznetsov
ddc4cfb197 email account fetch since not required 2020-04-17 11:31:55 +03:00
Taras Machyshyn
43696d91fc Modification upgrade script 5.9 2020-04-16 12:00:16 +03:00
Yuri Kuznetsov
69ec7ea23b added tooltip 2020-04-16 09:38:52 +03:00
Yuri Kuznetsov
5b0a58372b Merge branch 'hotfix/5.8.5' 2020-04-16 09:21:23 +03:00
Yuri Kuznetsov
38e2650cde fix foreign email/phone 2020-04-16 09:21:06 +03:00
Yuri Kuznetsov
307ba20f0a formula string replace 2020-04-15 15:52:26 +03:00
Yuri Kuznetsov
1659a731ad Merge branch 'hotfix/5.8.5' 2020-04-15 13:58:07 +03:00
Yuri Kuznetsov
f28fcde90c collection fix 2020-04-15 13:57:46 +03:00
Yuri Kuznetsov
21cda21b2e email template htmlizer support 2020-04-15 10:44:57 +03:00
Yuri Kuznetsov
576d34e9bb htmlizer factory 2020-04-15 10:23:46 +03:00
Yuri Kuznetsov
d6b992ef50 cleanupOrphanAttachments fix 2020-04-15 09:36:19 +03:00
Yuri Kuznetsov
f4c8931d7c Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-14 19:35:34 +03:00
Yuri Kuznetsov
8a17ff2ca3 attachment api: get file 2020-04-14 19:35:21 +03:00
Taras Machyshyn
a5681a75bb Fixed integration tests 2020-04-14 17:21:56 +03:00
Yuri Kuznetsov
e6b1299293 formula: generate pdf 2020-04-14 12:58:36 +03:00
Yuri Kuznetsov
01de38ce97 cleanup 2020-04-14 12:25:32 +03:00
Yuri Kuznetsov
35940f28d2 formula apply template addition 2020-04-14 12:11:16 +03:00
Yuri Kuznetsov
1c1b40ff24 formula: email apply template 2020-04-14 11:58:34 +03:00
Yuri Kuznetsov
31cc9849e4 fix notice 2020-04-14 11:13:48 +03:00
Yuri Kuznetsov
61fb2a0bbe fix notice 2020-04-14 11:07:19 +03:00
Yuri Kuznetsov
0c5af4c6f0 formula send email change 2020-04-13 16:46:18 +03:00
Yuri Kuznetsov
bde04d8a4a manual merge 2020-04-13 15:35:48 +03:00
Yuri Kuznetsov
9bbe2ba80d fix email save to draft subject lost 2020-04-13 15:34:12 +03:00
Yuri Kuznetsov
1a5e9d287d formula: ext\email\send 2020-04-13 15:05:37 +03:00
Yuri Kuznetsov
116f04d42a fix email 2020-04-13 14:57:05 +03:00
Yuri Kuznetsov
92e15f4252 email servise: sending refactoring 2020-04-13 13:40:34 +03:00
Yuri Kuznetsov
192410420f naming fix 2020-04-13 12:56:56 +03:00
Yuri Kuznetsov
5f9db6489a mail parsing attachment fix: fetch binary content 2020-04-13 10:57:41 +03:00
Yuri Kuznetsov
dcc97f4f26 fix typo 2020-04-10 17:39:29 +03:00
Yuri Kuznetsov
513398c984 import: ability to save properties as default 2020-04-10 17:35:00 +03:00
Yuri Kuznetsov
10955e3f56 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-10 16:44:36 +03:00
Yuri Kuznetsov
261f6daf69 mail parsing attachment empty charset fix 2020-04-10 16:43:25 +03:00
Taras Machyshyn
b352a194ea Fixed mb4 collation for MariaDB 2020-04-10 16:15:48 +03:00
Taras Machyshyn
1c9095a9f4 Changed max index length due to MariaDB changes 2020-04-10 12:13:27 +03:00
Yuri Kuznetsov
3b69b83b3d update mail-mime-parser 2020-04-10 09:52:50 +03:00
Yuri Kuznetsov
882e92bbcf import ui refactoring 2020-04-09 19:02:48 +03:00
Yuri Kuznetsov
a5cfaabd13 formula add function text 2020-04-09 14:28:31 +03:00
Yuri Kuznetsov
8323d867fb Merge branch 'hotfix/5.8.5' 2020-04-09 14:00:36 +03:00
Yuri Kuznetsov
d88b99a7f8 preferences ui fix 2020-04-09 13:56:49 +03:00
Yuri Kuznetsov
9bea92f35e fix import tab delimiter 2020-04-09 13:52:41 +03:00
Yuri Kuznetsov
1fe3aad25f export import delimiter fixes and changes 2020-04-09 13:52:01 +03:00
Yuri Kuznetsov
10063eaaeb fix import tab delimiter 2020-04-09 13:51:35 +03:00
Yuri Kuznetsov
7b7c76c8d1 Merge branch 'hotfix/5.8.5' 2020-04-09 12:23:45 +03:00
Yuri Kuznetsov
ebe0e1ade7 fix update page title 2020-04-09 12:23:22 +03:00
Yuri Kuznetsov
b1ac2ebb49 fix import 2020-04-09 12:13:31 +03:00
Yuri Kuznetsov
348e72ac37 autocomplete small text 2020-04-09 11:21:28 +03:00
Yuri Kuznetsov
fb9df5ff0e account role autocomplete fixes 2020-04-09 11:07:02 +03:00
Yuri Kuznetsov
ad9f31c499 account role support autocomplete 2020-04-09 10:56:56 +03:00
Yuri Kuznetsov
4ba0f902ce less changes 2020-04-08 12:45:57 +03:00
Yuri Kuznetsov
6606a7aef1 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-08 10:04:21 +03:00
Yuri Kuznetsov
9b878148cf add phpunit results to gitignore 2020-04-08 10:04:16 +03:00
Yuri Kuznetsov
a1de3a1536 Update README.md 2020-04-08 09:52:48 +03:00
Yuri Kuznetsov
187e007653 formula record relate support mass relate 2020-04-07 16:25:43 +03:00
Yuri Kuznetsov
6fb4f79f80 formula record create update 2020-04-07 12:29:12 +03:00
Yuri Kuznetsov
785eb8ab39 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-07 11:04:20 +03:00
Yuri Kuznetsov
f096bf1949 Update README.md 2020-04-07 11:02:44 +03:00
Yuri Kuznetsov
a406bfe19f Update README.md 2020-04-07 11:02:02 +03:00
Yuri Kuznetsov
664610f283 Update README.md 2020-04-07 10:57:18 +03:00
Yuri Kuznetsov
cdc204710f Merge branch 'hotfix/5.8.5' 2020-04-07 10:22:35 +03:00
Yuri Kuznetsov
b762aafdb2 fix duration field 2020-04-07 10:21:38 +03:00
Taras Machyshyn
4e1b6a00b0 Bug fixes 2020-04-06 16:49:45 +03:00
Taras Machyshyn
1a7a7dd335 Tests fixes 2020-04-06 15:27:12 +03:00
Taras Machyshyn
d16b967b7b Bug fixes 2020-04-06 15:26:51 +03:00
Taras Machyshyn
9ec45a1730 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-06 14:36:42 +03:00
Taras Machyshyn
41448ef7eb Fix tests 2020-04-06 12:58:14 +03:00
Taras Machyshyn
ebbf785701 Bug fixes 2020-04-06 12:57:52 +03:00
Yuri Kuznetsov
82f9351414 mass email faster cleanup 2020-04-06 11:52:33 +03:00
Yuri Kuznetsov
b19f05af19 orm mapper massDeleteFromDb 2020-04-06 11:52:24 +03:00
Taras Machyshyn
8336fe3f66 Bug fixing in Util 2020-04-06 11:25:30 +03:00
Yuri Kuznetsov
af1fc53e10 generate keys ids improvements 2020-04-06 10:36:01 +03:00
Yuri Kuznetsov
8607c95c6e Merge branch 'hotfix/5.8.5' 2020-04-04 11:42:44 +03:00
Yuri Kuznetsov
3e76a51c4b css less border shadow 2020-04-04 11:42:23 +03:00
Yuri Kuznetsov
8aabf3eb6b layout manager confirm leave out 2020-04-04 11:22:54 +03:00
Yuri Kuznetsov
5c339648ba task modal complete in dropdown 2020-04-04 10:39:50 +03:00
Yuri Kuznetsov
cd453664eb css grid sm change 2020-04-04 10:23:52 +03:00
Taras Machyshyn
a7cb7125b2 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-04-03 18:16:50 +03:00
Taras Machyshyn
e8366602f3 Bug fixing in Layout manager 2020-04-03 18:09:59 +03:00
Taras Machyshyn
c25a23ae78 Fix tests 2020-04-03 18:09:06 +03:00
Yuri Kuznetsov
6fbafcfc36 import support history navigation 2020-04-03 17:56:55 +03:00
Yuri Kuznetsov
05940b916f import changes 2020-04-03 16:59:38 +03:00
Yuri Kuznetsov
dbfcf35084 Merge branch 'hotfix/5.8.5' 2020-04-03 16:30:57 +03:00
Taras Machyshyn
2dfe48d4b2 Fix tests 2020-04-03 16:27:51 +03:00
Yuri Kuznetsov
90c2e21788 Merge branch 'hotfix/5.8.5' 2020-04-03 15:53:34 +03:00
Taras Machyshyn
9eb5cd69c5 Merge branch 'hotfix/5.8.5' of https://github.com/espocrm/espocrm into hotfix/5.8.5 2020-04-03 15:50:54 +03:00
Taras Machyshyn
72fee60900 Fix tests 2020-04-03 15:49:52 +03:00
Yuri Kuznetsov
d4fb9fc79a Merge branch 'hotfix/5.8.5' 2020-04-03 14:35:21 +03:00
Yuri Kuznetsov
a4b3be8323 entity maanger loading promises fix 2020-04-03 14:34:56 +03:00
Yuri Kuznetsov
30a39f57ed Merge branch 'hotfix/5.8.5' 2020-04-03 13:54:18 +03:00
Yuri Kuznetsov
3660aa3fc8 fix orm insert update array 2020-04-03 13:54:07 +03:00
Yuri Kuznetsov
f970ac02f2 layout manager improvements 2020-04-03 12:29:34 +03:00
Yuri Kuznetsov
4d114bc2e8 bottom layout fix 2020-04-02 18:08:57 +03:00
Yuri Kuznetsov
ea0d53d0e6 bottom layout order fix 2020-04-02 18:07:46 +03:00
Yuri Kuznetsov
8a2c0853c2 bottom layout compatibility fixes 2020-04-02 17:44:13 +03:00
Yuri Kuznetsov
28c77b2890 add phpunit 2020-04-02 17:13:34 +03:00
Yuri Kuznetsov
4c3b4017eb diff compser no dev 2020-04-02 16:50:15 +03:00
Yuri Kuznetsov
a4240a7d89 merge with github 2020-04-02 16:26:15 +03:00
Yuri Kuznetsov
c3e4f343a4 Merge branch 'hotfix/5.8.5' 2020-04-02 16:22:26 +03:00
Yuri Kuznetsov
168077ad1b version 2020-04-02 16:20:03 +03:00
Yuri Kuznetsov
7679f019f9 notify messages changes 2020-04-02 11:00:35 +03:00
Yuri Kuznetsov
f80ed523bb Merge branch 'hotfix/5.8.5' 2020-04-01 17:27:40 +03:00
Yuri Kuznetsov
e58132b6b3 fixes 2020-04-01 16:34:23 +03:00
Yuri Kuznetsov
f98810d87f code formatting and cleanup 2020-04-01 16:23:02 +03:00
Yuri Kuznetsov
e3e5bf1651 Merge branch 'hotfix/5.8.5' 2020-04-01 13:43:21 +03:00
Yuri Kuznetsov
99af4a6dc7 entity manager message rebuild required 2020-04-01 13:43:02 +03:00
Yuri Kuznetsov
14510f9130 fix notice 2020-04-01 11:24:11 +03:00
Yuri Kuznetsov
f9ae0d266e Merge branch 'hotfix/5.8.5' 2020-04-01 11:21:09 +03:00
Yuri Kuznetsov
2ecc9ac10f mass email fix 2020-04-01 10:23:36 +03:00
Yuri Kuznetsov
2a0b8957fc mass email memory usage improvements 2020-03-31 17:45:42 +03:00
Yuri Kuznetsov
4dc289f2bc css fix 2020-03-31 17:38:49 +03:00
Yuri Kuznetsov
107856f57d target list changes 2020-03-31 17:29:08 +03:00
Yuri Kuznetsov
5eb4d27a83 fix mass email smtp account field 2020-03-31 16:31:28 +03:00
unitorzero
8a4302a8d9 Implementing feature 'Skip rebuild' (#1652)
* Implementing feature "Skip rebuild".

* fixes
2020-03-31 13:41:24 +03:00
Yuri Kuznetsov
db5b37b221 Merge branch 'hotfix/5.8.5' 2020-03-31 13:08:02 +03:00
Yuri Kuznetsov
34a191446f webhook event autocomplete 2020-03-31 13:07:35 +03:00
Yuri Kuznetsov
2a3e9c143f select related empty filter 2020-03-31 12:37:24 +03:00
Yuri Kuznetsov
85b38f8119 layout set teams panel 2020-03-31 12:37:11 +03:00
Yuri Kuznetsov
9f70e7dce2 tooltip fix 2020-03-31 12:14:39 +03:00
Yuri Kuznetsov
d73222875a update symphony/http-foundation 2020-03-31 11:36:59 +03:00
Yuri Kuznetsov
a90098666d fix diff 2020-03-31 11:27:03 +03:00
Yuri Kuznetsov
6f06c75fdd version 2020-03-31 10:56:33 +03:00
Yuri Kuznetsov
6977f1adaa gruntfile changes 2020-03-31 10:35:49 +03:00
Yuri Kuznetsov
67beeca698 grunt composer ignore-platform-reqs 2020-03-31 09:21:59 +03:00
Yuri Kuznetsov
acc6684e2d fix readme 2020-03-31 09:18:45 +03:00
Yuri Kuznetsov
7eb4818739 layout set tooltip 2020-03-30 23:16:24 +03:00
Yuri Kuznetsov
a1caf22474 layout set no teams panel 2020-03-30 23:01:15 +03:00
Yuri Kuznetsov
588887b9d8 layout set tooltip 2020-03-30 23:00:24 +03:00
Yuri Kuznetsov
2c480cc45b naming fix 2020-03-30 16:28:03 +03:00
Yuri Kuznetsov
068eff9c94 email sending changes 2020-03-30 16:15:22 +03:00
Yuri Kuznetsov
ff627dd7dc tooltips 2020-03-30 13:27:57 +03:00
Yuri Kuznetsov
bcf6a24b9f number field: next number max value 2020-03-30 12:39:25 +03:00
Yuri Kuznetsov
714907d498 email account tooltip 2020-03-28 13:50:12 +02:00
Yuri Kuznetsov
14660ed0ae Merge branch 'hotfix/5.8.5' 2020-03-27 18:18:05 +02:00
Yuri Kuznetsov
3632e53f12 fix tasks date end being changed when date start is removed 2020-03-27 18:17:41 +02:00
Yuri Kuznetsov
f0f6455969 scheduler 2020-03-27 17:18:28 +02:00
Yuri Kuznetsov
24546658b8 Merge branch 'hotfix/5.8.5' 2020-03-26 10:54:26 +02:00
Yuri Kuznetsov
2fc728cce9 do not show view personal data for portal users 2020-03-26 10:54:07 +02:00
Yuri Kuznetsov
0ec1de2e8e formula find related order not mandatory 2020-03-25 18:04:26 +02:00
Yuri Kuznetsov
8ec6a350fd update bull 2020-03-25 12:10:10 +02:00
Yuri Kuznetsov
2b824537eb search order fixes 2020-03-25 09:56:22 +02:00
Yuri Kuznetsov
c9caebca28 fix 2020-03-24 12:59:20 +02:00
Yuri Kuznetsov
5fdae984db panels disabled fix 2020-03-24 11:54:30 +02:00
Yuri Kuznetsov
f6500d6c08 bottom layout and panels hide show improvement 2020-03-24 11:18:52 +02:00
Yuri Kuznetsov
b3b04a5346 Merge branch 'hotfix/5.8.5' of https://github.com/espocrm/espocrm into hotfix/5.8.5 2020-03-23 16:08:48 +02:00
Yuri Kuznetsov
7aee43c2ff cleanup 2020-03-23 16:08:11 +02:00
Yuri Kuznetsov
376ddbb5ff Merge branch 'hotfix/5.8.5' 2020-03-23 16:07:50 +02:00
Yuri Kuznetsov
dc00c53731 css fix 2020-03-23 16:04:58 +02:00
Yuri Kuznetsov
eadfa783f7 Merge branch 'hotfix/5.8.5' 2020-03-21 15:19:22 +02:00
Yuri Kuznetsov
5e0e644b5e Merge branch 'master' of github.com:espocrm/espocrm 2020-03-21 15:19:13 +02:00
Yuri Kuznetsov
e4a75a75d2 Merge branch 'hotfix/5.8.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.5 2020-03-21 15:18:01 +02:00
Yuri Kuznetsov
1d16a3cbfd Merge branch 'hotfix/5.8.5' of ssh://172.20.0.1/var/git/espo/backend 2020-03-21 15:17:05 +02:00
Yuri Kuznetsov
8b27bb6fb4 Merge branch 'hotfix/5.8.5' of github.com:espocrm/espocrm 2020-03-21 15:16:33 +02:00
Yuri Kuznetsov
3cf1db1e6f Merge branch 'hotfix/5.8.5' 2020-03-20 14:43:45 +02:00
Yuri Kuznetsov
c2d0b2fb86 fix task date timezone 2020-03-20 14:43:21 +02:00
Yuri Kuznetsov
99dde4d4e1 timeline calendar no border 2020-03-20 13:32:21 +02:00
Yuri Kuznetsov
0845baa72e Merge branch 'hotfix/5.8.5' 2020-03-20 11:49:34 +02:00
Yuri Kuznetsov
1567fd4feb fix locked panel 2020-03-20 11:49:25 +02:00
Yuri Kuznetsov
edf1bac058 panels fix 2020-03-20 11:45:52 +02:00
Yuri Kuznetsov
f02a193bda Merge branch 'hotfix/5.8.5' 2020-03-20 11:43:31 +02:00
Yuri Kuznetsov
a9dff223ed hide panel locked 2020-03-20 11:43:21 +02:00
Yuri Kuznetsov
2bfb414495 Merge branch 'hotfix/5.8.5' 2020-03-19 21:13:46 +02:00
Yuri Kuznetsov
d55b82a6ea fix css 2020-03-19 21:13:31 +02:00
Yuri Kuznetsov
c58ffc6cac Merge branch 'hotfix/5.8.5' 2020-03-19 19:40:43 +02:00
Yuri Kuznetsov
6dfd2d49bb meeting all day fix date time zone 2020-03-19 19:40:15 +02:00
Yuri Kuznetsov
dc0a866277 Merge branch 'hotfix/5.8.5' 2020-03-19 09:33:47 +02:00
Yuri Kuznetsov
32f07d81d4 install: admin preferences with default values 2020-03-18 21:13:27 +02:00
Yuri Kuznetsov
44460362c5 change default date time formats 2020-03-18 20:45:31 +02:00
Yuri Kuznetsov
1c5537bb55 css avatar fix 2020-03-18 20:30:18 +02:00
Yuri Kuznetsov
2636ac0b31 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-03-18 12:50:18 +02:00
Yuri Kuznetsov
e5cfe759b1 Merge branch 'hotfix/5.8.5' 2020-03-18 12:49:59 +02:00
Yuri Kuznetsov
a6f7df72b5 css fix 2020-03-18 12:49:42 +02:00
Taras Machyshyn
5b4788825d Fixes for actionQuickCreate in list view 2020-03-18 12:42:21 +02:00
Yuri Kuznetsov
c0355cb2b2 date-time css fix 2020-03-18 11:34:17 +02:00
Yuri Kuznetsov
2eac1b3721 fix date end 2020-03-18 10:35:16 +02:00
Yuri Kuznetsov
78aa4ce6c5 less changes 2020-03-18 09:53:25 +02:00
Yuri Kuznetsov
dd7bd118af css fix 2020-03-17 18:28:58 +02:00
Yuri Kuznetsov
225c6efe3e detail show more panels 2020-03-17 16:46:54 +02:00
Yuri Kuznetsov
fa3a2b729b Merge branch 'issue/config-locking' 2020-03-17 15:53:54 +02:00
Yuri Kuznetsov
2a3e3dde0a Merge branch 'master' of github.com:espocrm/espocrm 2020-03-17 15:25:36 +02:00
Yuri Kuznetsov
878ce83448 Merge branch 'stable' 2020-03-17 15:24:54 +02:00
Yuri Kuznetsov
db5d1850a9 email import catching errors 2020-03-17 13:07:08 +02:00
Yuri Kuznetsov
9b6b4e4199 fix email from change 2020-03-17 12:37:30 +02:00
Yuri Kuznetsov
1b7a06b909 email archive from validation 2020-03-17 12:26:18 +02:00
Yuri Kuznetsov
0925c6706e email ui fixes 2020-03-17 12:15:38 +02:00
Yuri Kuznetsov
f5829310b6 email to document 2020-03-17 12:11:57 +02:00
Yuri Kuznetsov
43ab056313 cleanup 2020-03-17 11:11:13 +02:00
Yuri Kuznetsov
57ef6f1b7e create record from email acl check 2020-03-17 11:08:44 +02:00
Yuri Kuznetsov
0525a0b3cd add GTQ currency 2020-03-17 09:59:47 +02:00
Yuri Kuznetsov
7a4cbec022 Merge branch 'hotfix/5.8.4' 2020-03-17 09:55:19 +02:00
Yuri Kuznetsov
705f791e12 css fixes 2020-03-17 09:54:59 +02:00
Yuri Kuznetsov
69f836575c wysiwyg iframe empty fix 2020-03-16 15:01:52 +02:00
Yuri Kuznetsov
f82b8ad49f Merge branch 'hotfix/5.8.4' 2020-03-16 11:39:03 +02:00
Yuri Kuznetsov
2e388b32f3 updage npm packages 2020-03-16 11:38:42 +02:00
Yuri Kuznetsov
de1e4abf0f Merge branch 'master' of https://github.com/espocrm/espocrm 2020-03-16 11:25:18 +02:00
Yuri Kuznetsov
c37c3d32aa account layout change 2020-03-16 11:25:07 +02:00
Yuri Kuznetsov
01e252085c portal user create: no send password if no portal 2020-03-16 10:47:31 +02:00
Yuri Kuznetsov
45b8764670 Update README.md 2020-03-16 10:42:31 +02:00
Yuri Kuznetsov
a21c6dda36 Update README.md 2020-03-16 10:13:53 +02:00
Yuri Kuznetsov
82be8d7fbd Update README.md 2020-03-16 10:10:15 +02:00
Yuri Kuznetsov
97ae13a27f Update README.md 2020-03-16 10:01:40 +02:00
Yuri Kuznetsov
edd2d6a630 search ui changes 2020-03-13 17:05:05 +02:00
Yuri Kuznetsov
8475e0a99c search buttons placement 2020-03-13 16:21:00 +02:00
Yuri Kuznetsov
02e3d1823f frontend some access checking for actions 2020-03-13 15:43:36 +02:00
Yuri Kuznetsov
6b6f522f1c frontend acl checkField function 2020-03-13 15:43:16 +02:00
Yuri Kuznetsov
80d695239c search button default style 2020-03-13 13:08:43 +02:00
Yuri Kuznetsov
d3684df6e3 confirm closing page 2020-03-13 12:43:40 +02:00
Yuri Kuznetsov
d2bf82d232 Merge branch 'hotfix/5.8.4' 2020-03-13 12:05:57 +02:00
Yuri Kuznetsov
e6f4b337ce action history mass remove 2020-03-13 12:00:04 +02:00
Yuri Kuznetsov
f4e58d9bee remove email confirm msg 2020-03-13 11:55:00 +02:00
Yuri Kuznetsov
af9e77a5bb confirm msg markdown support 2020-03-13 11:52:22 +02:00
Yuri Kuznetsov
1d9af232b9 Merge branch 'hotfix/5.8.4' 2020-03-13 11:37:25 +02:00
Yuri Kuznetsov
60b6c85864 fix move email to trash ui issue 2020-03-13 11:37:19 +02:00
Yuri Kuznetsov
709c301dda email: muted text if in trash 2020-03-13 11:34:07 +02:00
Yuri Kuznetsov
37dc6a1217 email attached notification 2020-03-13 11:17:46 +02:00
Yuri Kuznetsov
ba82989e97 Merge branch 'hotfix/5.8.4' 2020-03-12 17:05:36 +02:00
Yuri Kuznetsov
731273c296 select manager: default order by to select 2020-03-12 17:02:24 +02:00
Taras Machyshyn
2a11d9e7ed Added data/tmp to .gitignore 2020-03-12 15:46:27 +02:00
Taras Machyshyn
67dea8fd5e Fix config save 2020-03-12 15:41:09 +02:00
Yuri Kuznetsov
8aff7316e5 email attachment icon on side panel 2020-03-12 11:16:32 +02:00
Yuri Kuznetsov
6331300806 Merge branch 'hotfix/5.8.4' 2020-03-12 11:00:54 +02:00
Yuri Kuznetsov
64d04e1d78 css fix 2020-03-12 11:00:46 +02:00
Yuri Kuznetsov
6258893aa8 check duplicates apply access 2020-03-12 10:57:08 +02:00
Yuri Kuznetsov
3e27868760 version 2020-03-12 10:49:37 +02:00
Yuri Kuznetsov
cbf8301a89 fix typo 2020-03-12 10:40:09 +02:00
Yuri Kuznetsov
1601a12e20 config save fallback 2020-03-11 16:57:25 +02:00
Yuri Kuznetsov
4420511e02 config save with renaming 2020-03-11 16:21:49 +02:00
Yuri Kuznetsov
bbfe3c7366 ajaxTimeout param 2020-03-11 15:12:33 +02:00
Yuri Kuznetsov
64d31c8324 fix send invitation button 2020-03-11 14:58:34 +02:00
Yuri Kuznetsov
e93860101c comples created modified fields 2020-03-11 14:38:15 +02:00
Yuri Kuznetsov
04444aa03a email replied arrows 2020-03-11 12:44:45 +02:00
Yuri Kuznetsov
6eba798cd8 user account directAccessDisabled 2020-03-11 11:53:46 +02:00
Yuri Kuznetsov
c5555c2909 fix layout service 2020-03-10 16:16:58 +02:00
Yuri Kuznetsov
56164b9446 detail cell label 2020-03-10 15:41:27 +02:00
Yuri Kuznetsov
9f40bb4b1e email archive from autocomplete 2020-03-10 15:21:50 +02:00
Yuri Kuznetsov
d67323d35f email layout changes 2020-03-10 15:16:58 +02:00
Yuri Kuznetsov
3f26095281 calendar additional attribute list 2020-03-10 12:52:25 +02:00
Yuri Kuznetsov
d63871957d cs fix 2020-03-10 12:32:40 +02:00
Yuri Kuznetsov
553c692b9a timeline fix 2020-03-09 17:06:19 +02:00
Yuri Kuznetsov
4a71ab5f5d websocket update 2020-03-09 16:31:05 +02:00
Yuri Kuznetsov
3fbdff674e Merge branch 'hotfix/5.8.3' of ssh://172.20.0.1/var/git/espo/backend 2020-03-09 16:19:21 +02:00
Taras Machyshyn
e6aad6eabc Merge branch 'hotfix/5.8.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.3 2020-03-09 16:19:07 +02:00
Taras Machyshyn
3b1d307fad Cli installation notice fixes 2020-03-09 16:18:49 +02:00
Yuri Kuznetsov
66fc4c2294 calendar some changes 2020-03-09 15:27:13 +02:00
Yuri Kuznetsov
a21e4b836d fullTextSearchOrderType in metadata 2020-03-09 12:59:26 +02:00
Yuri Kuznetsov
3159181c5c acceptance status modal change 2020-03-06 13:26:35 +02:00
Yuri Kuznetsov
770cac9b87 orm: IFNULL, NULLIF functions 2020-03-06 13:02:39 +02:00
Yuri Kuznetsov
ec4afe0e11 foreign concat name null if empty 2020-03-06 12:48:45 +02:00
Yuri Kuznetsov
163d51770d multi-enum translateValueToEditLabel 2020-03-06 12:04:29 +02:00
Yuri Kuznetsov
364a5c50f0 fix test 2020-03-06 11:05:59 +02:00
Yuri Kuznetsov
581f09eb9b Merge branch 'hotfix/5.8.3' 2020-03-05 16:30:19 +02:00
Yuri Kuznetsov
d75bc354cf page title update fix 2020-03-05 16:28:16 +02:00
Yuri Kuznetsov
5e3f00f11d tootltip link in new tab 2020-03-05 12:40:07 +02:00
Yuri Kuznetsov
ffe83d24ce compose email: skip appending initial body if changed 2020-03-05 11:53:16 +02:00
Gerardo Exequiel Pozzi
ae874c44f5 add client/custom to perm check 2020-03-05 11:16:50 +02:00
Taras Machyshyn
e80269b61c Merge pull request #1625 from djgera/djgera-patch-2
add "client/custom" directory to permission check
2020-03-05 10:44:23 +02:00
Yuri Kuznetsov
2c4d5fb0f8 Merge branch 'hotfix/5.8.3' 2020-03-04 14:59:48 +02:00
Yuri Kuznetsov
1fe222852a order by non unique fields 2020-03-04 14:55:33 +02:00
Yuri Kuznetsov
60ae87a3cb fix orderById 2020-03-04 14:16:21 +02:00
Yuri Kuznetsov
7f092ad31d Merge branch 'hotfix/5.8.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.3 2020-03-04 13:15:53 +02:00
Taras Machyshyn
fd8090e13d Config improvements 2020-03-04 13:15:03 +02:00
Yuri Kuznetsov
4aba2aab4c timeline css changes 2020-03-04 13:14:20 +02:00
Yuri Kuznetsov
8c03b0a6cd remove overview label from layouts 2020-03-04 11:17:39 +02:00
Yuri Kuznetsov
e87fda3f3a convert lead tpl fixes 2020-03-04 10:59:00 +02:00
Yuri Kuznetsov
4cabd6c2f1 Merge branch 'hotfix/5.8.3' 2020-03-03 17:08:45 +02:00
Yuri Kuznetsov
f38361c7c7 fix duplicate link 2020-03-03 17:02:13 +02:00
Yuri Kuznetsov
ca3d10e7a2 timeline max label width 2020-03-03 16:30:42 +02:00
Yuri Kuznetsov
e4cb9071d0 tabs extra delimiter 2020-03-03 16:11:58 +02:00
Yuri Kuznetsov
2b2777b3da sales pipeline: team filter 2020-03-02 13:19:42 +02:00
Yuri Kuznetsov
83fd96a876 orderById param 2020-03-02 12:04:06 +02:00
Yuri Kuznetsov
c1b1eaf847 funnel chart 2020-02-28 16:45:55 +02:00
Yuri Kuznetsov
f63cc22f44 2fa required logout 2020-02-27 16:29:20 +02:00
Yuri Kuznetsov
3da1632e4b 2fa: force users 2020-02-27 16:22:18 +02:00
Yuri Kuznetsov
0bf4624839 2FA user field 2020-02-27 14:29:08 +02:00
Yuri Kuznetsov
0d38dfb385 Merge branch 'hotfix/5.8.3' 2020-02-27 13:03:29 +02:00
Yuri Kuznetsov
70fff1167b underscore-min.map 2020-02-27 12:05:16 +02:00
Yuri Kuznetsov
196c300be5 update purify.js 2020-02-27 12:05:06 +02:00
Yuri Kuznetsov
a894f9a70b fix warnings 2020-02-27 11:47:01 +02:00
Yuri Kuznetsov
9e3c258c90 fix foreign field 2020-02-27 11:41:53 +02:00
Yuri Kuznetsov
0893082326 foreign field skip disabled 2020-02-27 10:49:48 +02:00
Yuri Kuznetsov
18d12c1faf campaign: hide statystics if no access to corresponding scopes 2020-02-26 16:31:05 +02:00
Yuri Kuznetsov
46d9dd7bea layout changes 2020-02-26 16:00:18 +02:00
Yuri Kuznetsov
4fa562a283 panels show-more 2020-02-26 15:44:15 +02:00
Yuri Kuznetsov
1f02c8320b Merge branch 'hotfix/5.8.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.3 2020-02-26 14:52:55 +02:00
Taras Machyshyn
774aff1d17 CronManager: log file and line 2020-02-26 14:52:19 +02:00
Yuri Kuznetsov
74c142a1ac Merge branch 'hotfix/5.8.3' 2020-02-26 12:33:37 +02:00
Yuri Kuznetsov
06d604796f fix panel state param 2020-02-26 12:31:17 +02:00
Yuri Kuznetsov
f1db095fdc fix link manager 2020-02-26 11:13:07 +02:00
Yuri Kuznetsov
bac5bf46b5 fix link manager 2020-02-26 11:10:08 +02:00
Yuri Kuznetsov
a0f085e415 fix link manager ui 2020-02-26 11:08:47 +02:00
Yuri Kuznetsov
c69f13dda6 fix parent 2020-02-26 11:06:47 +02:00
Yuri Kuznetsov
f6362b3aa5 parent w/o enity type list 2020-02-26 10:53:25 +02:00
Yuri Kuznetsov
4433325abb resolve conflict 2020-02-25 16:43:17 +02:00
Yuri Kuznetsov
f2e5197568 layout sets 2020-02-25 16:41:52 +02:00
Yuri Kuznetsov
bffcd3adb7 multi-enum: display as list on edit view 2020-02-25 10:53:13 +02:00
Yuri Kuznetsov
20e22c43f0 fix formula detail view height 2020-02-24 12:41:10 +02:00
Yuri Kuznetsov
99f0194b80 fix email address css 2020-02-24 10:57:49 +02:00
Yuri Kuznetsov
a5422fd789 clean up 2020-02-24 10:34:46 +02:00
Yuri Kuznetsov
fc5203c3ba fix array int 2020-02-24 10:29:55 +02:00
Gerardo Exequiel Pozzi
5deefc5f35 add client/custom to perm check 2020-02-22 19:17:03 -03:00
Yuri Kuznetsov
40531f4d4e avatar changes 2020-02-21 13:19:50 +02:00
Yuri Kuznetsov
78358057e6 lead opt in confirm message center 2020-02-21 11:32:38 +02:00
Yuri Kuznetsov
a6b7b5ca61 fix currency order 2020-02-20 17:39:37 +02:00
Yuri Kuznetsov
0b71b4fc7d naming fix 2020-02-20 15:54:23 +02:00
Yuri Kuznetsov
4b6c177af6 select manager: non existing filter return empty result 2020-02-20 15:26:56 +02:00
Yuri Kuznetsov
48c665b641 css color fix 2020-02-20 13:19:46 +02:00
Yuri Kuznetsov
506f7cf410 css color fix 2020-02-20 13:19:26 +02:00
Yuri Kuznetsov
a5cec0370e currency: only default currency 2020-02-20 12:04:11 +02:00
Yuri Kuznetsov
2e92adff81 stream post preview 2020-02-20 11:30:12 +02:00
Yuri Kuznetsov
1e3a3b909b Merge branch 'hotfix/5.8.3' 2020-02-20 10:55:17 +02:00
Yuri Kuznetsov
1dbc41619f try catch on default value populate 2020-02-20 10:47:48 +02:00
Yuri Kuznetsov
e4237abdbf Merge branch 'hotfix/5.8.3' 2020-02-20 10:28:24 +02:00
Yuri Kuznetsov
b2e1ff0d5a Merge branch 'master' of github.com:espocrm/espocrm into hotfix/5.8.3 2020-02-20 10:26:30 +02:00
Yuri Kuznetsov
790c34cd8a fix label 2020-02-20 10:26:07 +02:00
Yuri Kuznetsov
1e175429c8 Update CONTRIBUTING.md 2020-02-20 08:49:04 +02:00
Yuri Kuznetsov
575cdf8f5b favicon in metadata 2020-02-19 16:55:19 +02:00
Yuri Kuznetsov
02948e09c5 cleanup 2020-02-19 14:48:02 +02:00
Yuri Kuznetsov
20b6dbe5ac Merge branch 'hotfix/5.8.3' 2020-02-19 14:36:14 +02:00
Yuri Kuznetsov
328b6fcaa0 user edit layout fix 2020-02-19 12:03:08 +02:00
Yuri Kuznetsov
42355f3bec chevron-right fix 2020-02-19 11:47:04 +02:00
Yuri Kuznetsov
37a84f8cea opportunity amount min value 0 2020-02-19 11:17:41 +02:00
Yuri Kuznetsov
f1ad8a734a css fix 2020-02-19 10:50:27 +02:00
Yuri Kuznetsov
870cdb19e3 fix date time 2020-02-19 10:24:33 +02:00
Yuri Kuznetsov
56e5ce0bba templatePlaceholderDisabled 2020-02-18 17:07:21 +02:00
Yuri Kuznetsov
576015fffe extension installation ui fix 2020-02-18 12:54:45 +02:00
Yuri Kuznetsov
5f3d34b027 cleanup 2020-02-18 11:44:49 +02:00
Yuri Kuznetsov
01bb105808 scripts 2020-02-18 11:38:50 +02:00
Yuri Kuznetsov
d9125a5204 streamEmailWithContentEntityTypeList 2020-02-18 11:38:44 +02:00
Yuri Kuznetsov
93a26dca36 v 2020-02-18 11:19:46 +02:00
Yuri Kuznetsov
5c539b99d8 port fields fixes 2020-02-18 11:03:53 +02:00
Yuri Kuznetsov
9e622bbfbd formula: string match 2020-02-14 11:11:31 +02:00
Yuri Kuznetsov
283a25162e json array validation 2020-02-13 17:06:35 +02:00
Yuri Kuznetsov
0c44eec377 formula array\\at 2020-02-13 15:39:32 +02:00
Yuri Kuznetsov
0cbdf88b21 formula findByEmailAddress 2020-02-13 12:24:28 +02:00
Yuri Kuznetsov
2f06f0b7d2 record view data-scope attribute 2020-02-12 12:42:45 +02:00
Yuri Kuznetsov
cee9d21b7c directUpdateDisabled param 2020-02-12 11:48:32 +02:00
Yuri Kuznetsov
c03afcce33 mass email fix 2020-02-11 13:14:10 +02:00
Yuri Kuznetsov
5e9ee92011 eventAssignedUserIsAttendeeDisabled 2020-02-11 12:56:16 +02:00
Yuri Kuznetsov
ad212d694f set password command 2020-02-11 12:17:37 +02:00
Yuri Kuznetsov
9530b2802b cleanup framework 2020-02-11 11:41:37 +02:00
Yuri Kuznetsov
b4e79d57ea todat button config param 2020-02-10 12:24:10 +02:00
Yuri Kuznetsov
7efd36ebe8 totp label 2020-02-10 12:08:37 +02:00
Yuri Kuznetsov
9f90802fb2 pdf: header 2020-02-10 11:16:56 +02:00
Yuri Kuznetsov
18f4cd4587 fix notice 2020-02-10 11:16:15 +02:00
Eymen Elkum
fc01905134 relationship panel recordsPerPage param (#1600) 2020-02-07 16:22:43 +02:00
Yuri Kuznetsov
91523553fb v 2020-02-07 11:33:49 +02:00
Yuri Kuznetsov
ca449b9952 fix upgrade script 2020-02-07 11:33:19 +02:00
Yuri Kuznetsov
3e0eade2f3 fix calendar call creation 2020-02-07 11:30:10 +02:00
Yuri Kuznetsov
68cca25635 Merge branch 'hotfix/5.8.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.2 2020-02-07 11:04:01 +02:00
Yuri Kuznetsov
050fb4330e new import button change 2020-02-06 16:59:24 +02:00
Taras Machyshyn
14d43bda1c Merge branch 'hotfix/5.8.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.8.2 2020-02-06 16:37:52 +02:00
Taras Machyshyn
34a8158284 Fix notices in PHP 7.4 2020-02-06 16:37:32 +02:00
Yuri Kuznetsov
8871842863 meeting self acceptance status fix 2020-02-06 15:47:04 +02:00
Taras Machyshyn
9392a26f81 ORM converter fixes 2020-02-06 15:00:11 +02:00
Yuri Kuznetsov
8eac39bd50 fix actitiviews view list html 2020-02-06 12:47:32 +02:00
Yuri Kuznetsov
2c8407581c hazyblue background lighter 2020-02-06 11:19:10 +02:00
Yuri Kuznetsov
b89349d0ee navbar logo padding 2020-02-05 17:09:00 +02:00
Yuri Kuznetsov
f554a6f649 fix duplicate link 2020-02-04 16:57:29 +02:00
Yuri Kuznetsov
e4a8383758 upgrade script 2020-02-04 15:13:54 +02:00
Yuri Kuznetsov
db94026d52 v 2020-02-04 15:08:55 +02:00
Yuri Kuznetsov
18e4aeb05d upgrade script fix 2020-02-04 15:08:33 +02:00
Yuri Kuznetsov
835ca877c7 import fix 2020-02-04 15:05:29 +02:00
Yuri Kuznetsov
89c1cf1f25 email send test group smtp handler 2020-02-04 13:13:38 +02:00
Yuri Kuznetsov
7cf512f75b portal url fix 2020-02-03 14:21:05 +02:00
Yuri Kuznetsov
d625647b29 fix main view global menu 2020-02-03 11:42:16 +02:00
Yuri Kuznetsov
21fbef7fb4 upgrade fix 2020-01-31 13:31:01 +02:00
588 changed files with 21154 additions and 6080 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/data/preferences/*
/data/.backup/*
/data/config.php
/data/tmp/*
/build
/node_modules
npm-debug.log
@@ -13,6 +14,7 @@ npm-debug.log
/tests/unit/testData/cache/*
!/tests/unit/testData/cache/.data
/tests/integration/config.php
.phpunit.result.cache
composer.phar
vendor/
/custom/Espo/Custom/*

View File

@@ -20,5 +20,5 @@ DirectoryIndex index.php index.html
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
RewriteRule reset/?$ reset.html [QSA,L]
RewriteRule /?web\.config - [F]
</IfModule>

View File

@@ -4,6 +4,12 @@ Before we can merge your pull request you need to accept our CLA [here](https://
[Code Style Guidelines](https://github.com/espocrm/espocrm/wiki/Code-Style-Guidelines).
Branches:
* *hotfix/** upcoming maintenance release; fixes should be pushed to this branch;
* *master* develop branch; new features should be pushed to this branch;
* *stable* last stable release.
## Issues
We don't provide developer help or any kind of support on github. Please use our [forum](https://forum.espocrm.com) for this.

View File

@@ -21,10 +21,11 @@
/**
* * `grunt` - full build
* * `grunt dev` - build only items needed for development
* * `grunt offline` - skips *composer install*
* * `grunt dev` - build only items needed for development (takes less time)
* * `grunt offline` - build but skip *composer install*
* * `grant release` - full build plus upgrade packages`
* * `grant tests` - build and run tests
* * `grant test` - build for test running
* * `grant run-tests` - build and run unit and integratino tests
*/
module.exports = function (grunt) {
@@ -300,19 +301,23 @@ module.exports = function (grunt) {
});
grunt.registerTask("composer", function() {
cp.execSync("composer install", {stdio: 'ignore'});
cp.execSync("composer install --ignore-platform-reqs --no-dev", {stdio: 'ignore'});
});
grunt.registerTask("composer-dev", function() {
cp.execSync("composer install --ignore-platform-reqs", {stdio: 'ignore'});
});
grunt.registerTask("upgrade", function() {
cp.execSync("node diff --all --vendor", {stdio: 'inherit'});
});
grunt.registerTask("unit-tests", function() {
cp.execSync("phpunit --bootstrap=vendor/autoload.php tests/unit", {stdio: 'inherit'});
grunt.registerTask("unit-tests-run", function() {
cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php tests/unit", {stdio: 'inherit'});
});
grunt.registerTask("integration-tests", function() {
cp.execSync("phpunit --bootstrap=vendor/autoload.php tests/integration", {stdio: 'inherit'});
grunt.registerTask("integration-tests-run", function() {
cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php tests/integration", {stdio: 'inherit'});
});
grunt.registerTask("zip", function() {
@@ -382,14 +387,29 @@ module.exports = function (grunt) {
'clean:release',
]);
grunt.registerTask('tests', [
'default',
'unit-tests',
'integration-tests',
grunt.registerTask('run-tests', [
'test',
'unit-tests-run',
'integration-tests-run',
]);
grunt.registerTask('run-unit-tests', [
'composer-dev',
'unit-tests-run',
]);
grunt.registerTask('run-integration-tests', [
'test',
'integration-tests-run',
]);
grunt.registerTask('dev', [
'composer',
'composer-dev',
'less',
]);
grunt.registerTask('test', [
'composer-dev',
'offline',
]);
};

View File

@@ -1,21 +1,21 @@
## EspoCRM
<a href='https://www.espocrm.com'>EspoCRM is an Open Source CRM</a> (Customer Relationship Management) software that allows you to see, enter and evaluate all your company relationships regardless of the type. People, companies or opportunities - all in an easy and intuitive interface.
[EspoCRM is an Open Source CRM](https://www.espocrm.com) (Customer Relationship Management) software that allows you to see, enter and evaluate all your company relationships regardless of the type. People, companies or opportunities - all in an easy and intuitive interface.
It's a web application with a frontend designed as a single page application based on backbone.js and a REST API backend written in PHP.
It's a web application with a frontend designed as a single page application and REST API backend written in PHP.
Download the latest release from our [website](https://www.espocrm.com).
[Download](https://www.espocrm.com/download/) the latest release from our website.
### Requirements
* PHP 7.2 and later (with pdo, json, gd, openssl, zip, imap, mbstring, curl extensions);
* MySQL 5.7 (and later), or MariaDB 10.1 (and later).
For more information about server configuration see [this article](https://www.espocrm.com/documentation/administration/server-configuration/).
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
### Documentation
Documentation for administrators, users and developers is available [here](https://www.espocrm.com/documentation/).
Documentation for administrators, users and developers is available [here](https://docs.espocrm.com).
### How to report a bug
@@ -23,34 +23,15 @@ Create an issue [here](https://github.com/espocrm/espocrm/issues) or post on our
### How to install a stable version
[Download](https://www.espocrm.com/download/) the latest version. See the [instructions](https://www.espocrm.com/documentation/administration/installation/) about installation.
[Download](https://www.espocrm.com/download/) the latest version. See the [instructions](https://docs.espocrm.com/administration/installation/) about installation.
### Getting started (for developers)
### Development
1. Clone repository to your local computer.
2. Change to the project's root directory.
3. Install [composer](https://getcomposer.org/doc/00-intro.md).
4. Run `composer install` if composer is installed globally or `php composer.phar install` if locally.
* [Getting started](https://docs.espocrm.com/development/how-to-start)
* [Running tests](https://docs.espocrm.com/development/tests)
* [Making translation](https://docs.espocrm.com/development/translation)
Never update composer dependencies if you are going to contribute code back.
Now you can build. Build will create compiled css files.
To compose a proper config.php and populate database you can run install by opening `http(s)://{YOUR_CRM_URL}/install` location in a browser. Then open `data/config.php` file and add `isDeveloperMode => true`.
### How to build (for developers)
You need to have nodejs and Grunt CLI installed.
1. Change to the project's root directory.
2. Install project dependencies with `npm install`.
3. Run Grunt with `grunt`.
The build will be created in the `build` directory.
Upgrade packages can be built with `grunt upgrade`.
### How to contribute (for developers)
### How to contribute
Before we can merge your pull request you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
@@ -60,37 +41,6 @@ Branches:
* *master* develop branch; new features should be pushed to this branch;
* *stable* last stable release.
### Running tests (for developers)
You need to have *phpunit* installed.
Unit tests:
```
phpunit --bootstrap=vendor/autoload.php tests/unit
```
Integration tests:
```
phpunit --bootstrap=vendor/autoload.php tests/integration
```
### How to make a translation
Build po file with command:
`node po.js en_EN`
(specify needed language instead of en_EN)
After that translate the generated po file.
Build json files from the translated po file:
1. Put your po file espocrm-en_EN.po into `build` directory
2. Run `node lang.js en_EN`
Json files will be created in build directory grouped by folders.
### License
EspoCRM is published under the GNU GPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).

9
SECURITY.md Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
## Reporting a vulnerability
If you believe you have discovered a vulnerability in EspoCRM please contacts us via [this](https://www.espocrm.com/contacts/) or [this](https://www.espocrm.com/support/) forms.
## Supported versions
For severe vulnerabilities we provide fixes for 2 minor versions (the second number in the version string) back from the current stable version. Separate patches or manual fix guidelines will be provided for more old versions.

View File

@@ -9,4 +9,6 @@ RewriteEngine On
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
RewriteRule ^ index.php [QSA,L]
RewriteRule /?web\.config - [F]

View File

@@ -9,4 +9,6 @@ RewriteEngine On
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
RewriteRule ^ index.php [QSA,L]
RewriteRule /?web\.config - [F]

View File

@@ -63,11 +63,6 @@ class ActionHistoryRecord extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
public function beforeMassDelete()
{
throw new Forbidden();
}
public function beforeMassConvertCurrency()
{
throw new Forbidden();

View File

@@ -57,4 +57,21 @@ class Attachment extends \Espo\Core\Controllers\Record
return $this->getRecordService()->getCopiedAttachment($data)->getValueMap();
}
public function getActionFile($params, $data, $request, $response)
{
$id = $params['id'] ?? null;
if (!$id) throw new BadRequest();
$fileData = $this->getRecordService()->getFileData($id);
$response->headers->set('Content-Type', $fileData->type);
$response->headers->set('Content-Disposition', 'Content-Disposition: attachment; filename="'.$fileData->name.'"');
if ($fileData->size) {
$response->headers->set('Content-Length', $fileData->size);
}
return $fileData->contents;
}
}

View File

@@ -31,6 +31,7 @@ namespace Espo\Controllers;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
class EmailAccount extends \Espo\Core\Controllers\Record
{

View File

@@ -93,12 +93,7 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
{
list($integration, $userId) = explode('__', $params['id']);
if ($this->getUser()->id != $userId && !$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
return $entity->toArray();
return $this->getRecordService()->read($params['id'])->getValueMap();
}
public function actionUpdate($params, $data, $request)

View File

@@ -30,62 +30,49 @@
namespace Espo\Controllers;
use Espo\Core\Utils as Utils;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
class Layout extends \Espo\Core\Controllers\Base
{
public function actionRead($params, $data)
public function getActionRead($params, $data)
{
return $this->getServiceFactory()->create('Layout')->getForFrontend($params['scope'], $params['name']);
$scope = $params['scope'] ?? null;
$name = $params['name'] ?? null;
return $this->getServiceFactory()->create('Layout')->getForFrontend($scope, $name);
}
public function actionUpdate($params, $data, $request)
public function putActionUpdate($params, $data, $request)
{
if (is_object($data)) {
$data = get_object_vars($data);
}
if (is_object($data)) $data = get_object_vars($data);
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
$scope = $params['scope'] ?? null;
$name = $params['name'] ?? null;
$setId = $params['setId'] ?? null;
$layoutManager = $this->getContainer()->get('layout');
$layoutManager->set($data, $params['scope'], $params['name']);
$result = $layoutManager->save();
if ($result === false) {
throw new Error("Error while saving layout.");
}
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
return $layoutManager->get($params['scope'], $params['name']);
}
public function actionPatch($params, $data, $request)
{
return $this->actionUpdate($params, $data, $request);
return $this->getServiceFactory()->create('Layout')->update($scope, $name, $setId, $data);
}
public function postActionResetToDefault($params, $data, $request)
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (empty($data->scope) || empty($data->name)) {
throw new BadRequest();
}
if (empty($data->scope) || empty($data->name)) throw new BadRequest();
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
return $this->getServiceFactory()->create('Layout')->resetToDefault($data->scope, $data->name, $data->setId ?? null);
}
return $this->getContainer()->get('layout')->resetToDefault($data->scope, $data->name);
public function getActionGetOriginal($params, $data, $request)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
return $this->getServiceFactory()->create('Layout')->getOriginal(
$request->get('scope'), $request->get('name'), $request->get('setId')
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Controllers;
use Espo\Core\Exceptions\Forbidden;
class LayoutSet extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
}
}

View File

@@ -69,29 +69,7 @@ class User extends \Espo\Core\Controllers\Record
throw new BadRequest();
}
if ($this->getConfig()->get('passwordRecoveryDisabled')) {
throw new Forbidden("Password recovery disabled");
}
$request = $this->getEntityManager()->getRepository('PasswordChangeRequest')->where([
'requestId' => $data->requestId
])->findOne();
if (!$request) {
throw new Forbidden();
}
$userId = $request->get('userId');
if (!$userId) {
throw new Error();
}
if ($this->getService('User')->changePassword($userId, $data->password)) {
$this->getEntityManager()->removeEntity($request);
return [
'url' => $request->get('url')
];
}
return $this->getService('User')->changePasswordByRequest($data->requestId, $data->password);
}
public function postActionPasswordChangeRequest($params, $data, $request)

View File

@@ -260,6 +260,9 @@ class Application
$route = $slim->router()->getCurrentRoute();
$conditions = $route->getConditions();
$response = $slim->response();
$response->headers->set('Content-Type', 'application/json');
if (isset($conditions['useController']) && $conditions['useController'] == false) {
return;
}
@@ -304,13 +307,16 @@ class Application
});
$this->getSlim()->hook('slim.after.router', function () use (&$slim) {
$slim->contentType('application/json');
$response = $slim->response();
$res = $slim->response();
$res->header('Expires', '0');
$res->header('Last-Modified', gmdate("D, d M Y H:i:s") . " GMT");
$res->header('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
$res->header('Pragma', 'no-cache');
if (!$response->headers->has('Content-Type')) {
$response->headers->set('Content-Type', 'application/json');
}
$response->headers->set('Expires', '0');
$response->headers->set('Last-Modified', gmdate("D, d M Y H:i:s") . " GMT");
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
$response->headers->set('Pragma', 'no-cache');
});
}

View File

@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Cleanup;
abstract class Base extends \Espo\Core\Injectable
{
protected function init()
{
$this->addDependency('config');
$this->addDependency('metadata');
$this->addDependency('entityManager');
$this->addDependency('fileManager');
}
protected function getConfig()
{
return $this->getInjection('config');
}
protected function getMetadata()
{
return $this->getInjection('metadata');
}
protected function getEntityManager()
{
return $this->getInjection('entityManager');
}
protected function getFileManager()
{
return $this->getInjection('fileManager');
}
abstract public function process();
}

View File

@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Cleanup;
class Reminders extends Base
{
protected $cleanupRemindersPeriod = '15 days';
public function process()
{
$period = '-' . $this->getConfig()->get('cleanupRemindersPeriod', $this->cleanupRemindersPeriod);
$datetime = new \DateTime();
$datetime->modify($period);
$pdo = $this->getEntityManager()->getPDO();
$query = "DELETE FROM `reminder` WHERE DATE(remind_at) < " . $pdo->quote($datetime->format('Y-m-d'));
$sth = $pdo->prepare($query);
$sth->execute();
}
}

View File

@@ -0,0 +1,59 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Cleanup;
class WebhookQueue extends Base
{
protected $cleanupWebhookQueuePeriod = '10 days';
public function process()
{
$pdo = $this->getEntityManager()->getPDO();
$period = '-' . $this->getConfig()->get('cleanupWebhookQueuePeriod', $this->cleanupWebhookQueuePeriod);
$datetime = new \DateTime();
$datetime->modify($period);
$from = $datetime->format('Y-m-d H:i:s');
$query = "
DELETE FROM `webhook_queue_item`
WHERE
DATE(created_at) < ".$pdo->quote($from)." AND
(status <> 'Pending' OR deleted = 1)
";
$pdo->query($query);
$query = "
DELETE FROM `webhook_event_queue_item`
WHERE DATE(created_at) < ".$pdo->quote($from)." AND (is_processed = 1 OR deleted = 1)
";
$pdo->query($query);
}
}

View File

@@ -0,0 +1,195 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Console\Commands;
use Espo\Core\Exceptions\Error;
class Extension extends Base
{
protected $extensionManager = null;
public function run($options, $flagList, $argumentList)
{
if (in_array('u', $flagList)) {
// uninstall
$name = $options['name'] ?? null;
$id = $options['id'] ?? null;
if (!$name && !$id) {
$this->out("Can't uninstall. Specify --name=\"Extension Name\".\n");
return;
}
$params = [];
if ($id) {
$params['id'] = $id;
} else {
$params['name'] = $name;
}
$params['delete'] = !in_array('k', $flagList);
$this->runUninstall($params);
return;
} else {
// install
$file = $options['file'] ?? null;
if (!$file) {
$this->out("Can't install. Specify --file=\"path/to/package.zip\".\n");
return;
}
$this->runInstall($file);
return;
}
}
protected function runInstall(string $file)
{
$manager = $this->createExtensionManager();
if (!file_exists($file)) {
$this->out("File does not exist.\n");
return;
}
$fileData = file_get_contents($file);
$fileData = 'data:application/zip;base64,' . base64_encode($fileData);
try {
$id = $manager->upload($fileData);
} catch (\Throwable $e) {
$this->out($e->getMessage() . "\n");
return;
}
$manifest = $manager->getManifestById($id);
$name = $manifest['name'] ?? null;
$version = $manifest['version'] ?? null;
if (!$name) {
$this->out("Can't install. Bad manifest.\n");
return;
}
$this->out("Installing... Do not close the terminal. This may take a while...");
try {
$manager->install(['id' => $id]);
} catch (\Throwable $e) {
$this->out("\n");
$this->out($e->getMessage() . "\n");
return;
}
$this->out("\n");
$this->out("Extension '{$name}' version {$version} is installed.\nExtension ID: '{$id}'.\n");
}
protected function runUninstall(array $params)
{
$id = $params['id'] ?? null;
if ($id) {
$record = $this->getEntityManager()->getRepository('Extension')->where([
'id' => $id,
'isInstalled' => true,
])->findOne();
if (!$record) {
$this->out("Extension with ID '{$id}' is not installed.\n");
return;
}
$name = $record->get('name');
} else {
$name = $params['name'] ?? null;
if (!$name) {
$this->out("Can't uninstall. No --name or --id specified.\n");
return;
}
$record = $this->getEntityManager()->getRepository('Extension')->where([
'name' => $name,
'isInstalled' => true,
])->findOne();
if (!$record) {
$this->out("Extension '{$name}' is not installed.\n");
return;
}
$id = $record->id;
}
$manager = $this->createExtensionManager();
$this->out("Uninstalling... Do not close the terminal. This may take a while...");
try {
$manager->uninstall(['id' => $id]);
} catch (\Throwable $e) {
$this->out("\n");
$this->out($e->getMessage() . "\n");
return;
}
$this->out("\n");
if ($params['delete'] ?? false) {
try {
$manager->delete(['id' => $id]);
} catch (\Throwable $e) {
$this->out($e->getMessage() . "\n");
$this->out("Extension '{$name}' is uninstalled but could not be deleted.\n");
return;
}
$this->out("Extension '{$name}' is uninstalled and deleted.\n");
} else {
$this->out("Extension '{$name}' is uninstalled.\n");
}
}
protected function createExtensionManager()
{
return new \Espo\Core\ExtensionManager($this->getContainer());
}
protected function getEntityManager()
{
return $this->getContainer()->get('entityManager');
}
protected function out(string $string)
{
fwrite(\STDOUT, $string);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Console\Commands;
class SetPassword extends Base
{
public function run($options, $flagList, $argumentList)
{
$userName = $argumentList[0] ?? null;
if (!$userName) {
$this->out("User name must be specified.\n");
die;
}
$em = $this->getContainer()->get('entityManager');
$user = $em->getRepository('User')->where(['userName' => $userName])->findOne();
if (!$user) {
$this->out("User '{$userName}' not found.\n");
die;
}
if (!in_array($user->get('type'), ['admin', 'super-admin', 'portal', 'regular'])) {
$this->out("Can't set password for user of type '".$user->get('type')."'.\n");
die;
}
$this->out("Enter a new password:\n");
$password = $this->ask();
$password = trim($password);
if (!$password) {
$this->out("Password can not be empty.\n");
die;
}
$hash = $this->getContainer()->get('passwordHash');
$user->set('password', $hash->hash($password));
$em->saveEntity($user);
$this->out("Password for user '{$userName}' is changed.\n");
}
protected function ask()
{
$input = fgets(\STDIN);
return rtrim($input, "\n");
}
protected function out($string)
{
fwrite(\STDOUT, $string);
}
}

View File

@@ -140,8 +140,7 @@ class Upgrade extends Base
fwrite(\STDOUT, "\n");
$app = new \Espo\Core\Application();
$currentVerison = $app->getContainer()->get('config')->get('version');
$currentVerison = $this->getCurrentVersion();
fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVerison}.\n");
@@ -211,8 +210,7 @@ class Upgrade extends Base
return;
}
$app = new \Espo\Core\Application();
$currentVerison = $app->getContainer()->get('config')->get('version');
$currentVerison = $this->getCurrentVersion();
fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVerison}.\n");
@@ -261,6 +259,8 @@ class Upgrade extends Base
try {
foreach ($stepList as $stepName) {
fwrite(\STDOUT, ".");
$upgradeManager = $this->getUpgradeManager(true);
$upgradeManager->runInstallStep($stepName, ['id' => $upgradeId]);
}
@@ -277,8 +277,9 @@ class Upgrade extends Base
$phpExecutablePath = $this->getPhpExecutablePath();
foreach ($stepList as $stepName) {
$command = $phpExecutablePath . " command.php upgrade-step --step=". ucfirst($stepName) ." --id=". $upgradeId;
fwrite(\STDOUT, ".");
$command = $phpExecutablePath . " command.php upgrade-step --step=". ucfirst($stepName) ." --id=". $upgradeId;
$shellResult = shell_exec($command);
if ($shellResult !== 'true') {
$GLOBALS['log']->error('Upgrade Error: ' . $shellResult);
@@ -403,4 +404,15 @@ class Upgrade extends Base
return true;
}
protected function getCurrentVersion()
{
$configData = include "data/config.php";
if (!$configData) {
return null;
}
return $configData['version'] ?? null;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Console\Commands;
class Version extends Base
{
public function run($options, $flagList, $argumentList)
{
$version = $this->getContainer()->get('config')->get('version');
if (is_null($version)) {
return;
}
echo $version . "\n";
}
}

View File

@@ -29,11 +29,12 @@
namespace Espo\Core\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\BadRequest;
use \Espo\Core\Utils\Util;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Utils\Util;
use Espo\Core\Exceptions\ForbiddenSilent;
class Record extends Base
{
@@ -88,7 +89,7 @@ class Record extends Base
}
if (!$this->getAcl()->check($this->name, 'create')) {
throw new Forbidden();
throw new Forbidden("No create access for {$this->name}.");
}
$service = $this->getRecordService();
@@ -109,7 +110,7 @@ class Record extends Base
}
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
throw new Forbidden("No edit access for {$this->name}.");
}
$id = $params['id'];
@@ -124,7 +125,7 @@ class Record extends Base
public function actionList($params, $data, $request)
{
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
throw new Forbidden("No read access for {$this->name}.");
}
$params = [];
@@ -156,7 +157,7 @@ class Record extends Base
public function getActionListKanban($params, $data, $request)
{
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
throw new Forbidden("No read access for {$this->name}.");
}
$params = [];
@@ -286,14 +287,14 @@ class Record extends Base
}
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
throw new Forbidden("No edit access for {$this->name}.");
}
if (empty($data->attributes)) {
throw new BadRequest();
}
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
throw new Forbidden();
throw new Forbidden("No massUpdatePermission.");
}
$actionParams = $this->getMassActionParamsFromData($data);
@@ -313,7 +314,7 @@ class Record extends Base
if (array_key_exists('where', $actionParams)) {
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
throw new Forbidden();
throw new Forbidden("No massUpdatePermission.");
}
}
@@ -412,7 +413,7 @@ class Record extends Base
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'stream')) {
throw new Forbidden();
throw new Forbidden("No stream access for {$this->name}.");
}
$id = $params['id'];
return $this->getRecordService()->follow($id);
@@ -424,7 +425,7 @@ class Record extends Base
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
throw new Forbidden("No read access for {$this->name}.");
}
$id = $params['id'];
return $this->getRecordService()->unfollow($id);
@@ -444,7 +445,7 @@ class Record extends Base
$attributes = $data->attributes;
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
throw new Forbidden("No edit access for {$this->name}.");
}
return $this->getRecordService()->merge($targetId, $sourceIds, $attributes);
@@ -480,7 +481,7 @@ class Record extends Base
public function postActionMassUnfollow($params, $data, $request)
{
if (!$this->getAcl()->check($this->name, 'stream')) {
throw new Forbidden();
throw new Forbidden("No stream access for {$this->name}.");
}
$actionParams = $this->getMassActionParamsFromData($data);

View File

@@ -316,7 +316,7 @@ class CronManager
$job->set('attempts', 0);
$skipLog = true;
} else {
$GLOBALS['log']->error('CronManager: Failed job running, job ['.$job->id.']. Error Details: '.$e->getMessage());
$GLOBALS['log']->error('CronManager: Failed job running, job ['. $job->id .']. Error Details: '. $e->getMessage() .' at '. $e->getFile() . ':' . $e->getLine());
}
}

View File

@@ -219,7 +219,7 @@ class DataManager
$cryptKey = $config->get('cryptKey');
if (!$cryptKey) {
$cryptKey = \Espo\Core\Utils\Util::generateKey();
$cryptKey = \Espo\Core\Utils\Util::generateSecretKey();
$config->set('cryptKey', $cryptKey);
}

View File

@@ -62,9 +62,11 @@ class Csv extends \Espo\Core\Injectable
$delimiter = $this->getInjection('preferences')->get('exportDelimiter');
if (empty($delimiter)) {
$delimiter = $this->getInjection('config')->get('exportDelimiter', ';');
$delimiter = $this->getInjection('config')->get('exportDelimiter', ',');
}
$delimiter = str_replace('\t', "\t", $delimiter);
$fp = fopen('php://temp', 'w');
fputcsv($fp, $attributeList, $delimiter);

View File

@@ -29,9 +29,11 @@
namespace Espo\Core\ExternalAccount;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\InjectableFactory;
class ClientManager
{
@@ -39,13 +41,17 @@ class ClientManager
protected $metadata;
protected $clientMap = array();
protected $clientMap = [];
public function __construct($entityManager, $metadata, $config)
protected $injectableFactory = null;
public function __construct(
$entityManager, $metadata, $config, ?InjectableFactory $injectableFactory = null)
{
$this->entityManager = $entityManager;
$this->metadata = $metadata;
$this->config = $config;
$this->injectableFactory = $injectableFactory;
}
protected function getMetadata()
@@ -69,24 +75,68 @@ class ClientManager
$externalAccountEntity = $this->clientMap[$hash]['externalAccountEntity'];
$externalAccountEntity->set('accessToken', $data['accessToken']);
$externalAccountEntity->set('tokenType', $data['tokenType']);
$externalAccountEntity->set('expiresAt', $data['expiresAt'] ?? null);
if ($data['refreshToken'] ?? null) {
$externalAccountEntity->set('refreshToken', $data['refreshToken']);
}
$copy = $this->getEntityManager()->getEntity('ExternalAccount', $externalAccountEntity->id);
if ($copy) {
if (!$copy->get('enabled')) {
throw new Error("External Account Client Manager: Account got disabled.");
}
$copy->set('accessToken', $data['accessToken']);
$copy->set('tokenType', $data['tokenType']);
$this->getEntityManager()->saveEntity($copy, ['isTokenRenewal' => true]);
$copy->set('expiresAt', $data['expiresAt'] ?? null);
if ($data['refreshToken'] ?? null) {
$copy->set('refreshToken', $data['refreshToken'] ?? null);
}
$this->getEntityManager()->saveEntity($copy, ['isTokenRenewal' => true, 'skipHooks' => true]);
}
}
}
public function create($integration, $userId)
public function create(string $integration, string $userId)
{
$authMethod = $this->getMetadata()->get("integrations.{$integration}.authMethod");
$methodName = 'create' . ucfirst($authMethod);
return $this->$methodName($integration, $userId);
if (method_exists($this, $methodName)) {
return $this->$methodName($integration, $userId);
}
if (!$this->injectableFactory) {
throw new Error();
}
$integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration);
$externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId);
if (!$externalAccountEntity) {
throw new Error("External Account {$integration} not found for {$userId}");
}
if (!$integrationEntity->get('enabled')) return null;
if (!$externalAccountEntity->get('enabled')) return null;
$className = $this->getMetadata()->get("integrations.{$integration}.clientClassName");
$client = $this->injectableFactory->createByClassName($className);
$client->setup(
$userId,
$integrationEntity,
$externalAccountEntity,
$this
);
$this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId);
return $client;
}
protected function createOAuth2($integration, $userId)
protected function createOAuth2(string $integration, string $userId)
{
$integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration);
$externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId);
@@ -113,7 +163,7 @@ class ClientManager
$oauth2Client = new \Espo\Core\ExternalAccount\OAuth2\Client();
$client = new $className($oauth2Client, array(
$params = [
'endpoint' => $this->getMetadata()->get("integrations.{$integration}.params.endpoint"),
'tokenEndpoint' => $this->getMetadata()->get("integrations.{$integration}.params.tokenEndpoint"),
'clientId' => $integrationEntity->get('clientId'),
@@ -122,7 +172,16 @@ class ClientManager
'accessToken' => $externalAccountEntity->get('accessToken'),
'refreshToken' => $externalAccountEntity->get('refreshToken'),
'tokenType' => $externalAccountEntity->get('tokenType'),
), $this);
'expiresAt' => $externalAccountEntity->get('expiresAt'),
];
foreach (get_object_vars($integrationEntity->getValueMap()) as $k => $v) {
if (array_key_exists($k, $params)) continue;
if ($integrationEntity->hasAttribute($k)) continue;
$params[$k] = $v;
}
$client = new $className($oauth2Client, $params, $this);
$this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId);
@@ -139,5 +198,82 @@ class ClientManager
'externalAccountEntity' => $externalAccountEntity,
);
}
}
protected function getClientRecord($client) : \Espo\ORM\Entity
{
$data = $this->clientMap[spl_object_hash($client)];
if (!$data) {
throw new Error("External Account Client Manager: Client not found in hash.");
}
return $data['externalAccountEntity'];
}
public function isClientLocked($client) : bool
{
$externalAccountEntity = $this->getClientRecord($client);
$id = $externalAccountEntity->id;
$e = $this->getEntityManager()->getRepository('ExternalAccount')
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
if (!$e) {
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
}
return $e->get('isLocked');
}
public function lockClient($client)
{
$externalAccountEntity = $this->getClientRecord($client);
$id = $externalAccountEntity->id;
$e = $this->getEntityManager()->getRepository('ExternalAccount')
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
if (!$e) {
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
}
$e->set('isLocked', true);
$this->getEntityManager()->saveEntity($e, ['skipHooks' => true, 'silent' => true]);
}
public function unlockClient($client)
{
$externalAccountEntity = $this->getClientRecord($client);
$id = $externalAccountEntity->id;
$e = $this->getEntityManager()->getRepository('ExternalAccount')
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
if (!$e) {
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
}
$e->set('isLocked', false);
$this->getEntityManager()->saveEntity($e, ['skipHooks' => true, 'silent' => true]);
}
public function reFetchClient($client)
{
$externalAccountEntity = $this->getClientRecord($client);
$id = $externalAccountEntity->id;
$e = $this->getEntityManager()->getEntity('ExternalAccount', $id);
if (!$e) {
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
}
$data = $e->getValueMap();
$externalAccountEntity->set($data);
$client->setParams(get_object_vars($data));
}
}

View File

@@ -29,9 +29,9 @@
namespace Espo\Core\ExternalAccount\Clients;
use \Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Error;
use \Espo\Core\ExternalAccount\OAuth2\Client;
use Espo\Core\ExternalAccount\OAuth2\Client;
abstract class OAuth2Abstract implements IClient
{
@@ -39,7 +39,7 @@ abstract class OAuth2Abstract implements IClient
protected $manager = null;
protected $paramList = array(
protected $paramList = [
'endpoint',
'tokenEndpoint',
'clientId',
@@ -48,7 +48,8 @@ abstract class OAuth2Abstract implements IClient
'accessToken',
'refreshToken',
'redirectUri',
);
'expiresAt',
];
protected $clientId = null;
@@ -60,7 +61,15 @@ abstract class OAuth2Abstract implements IClient
protected $redirectUri = null;
public function __construct($client, array $params = array(), $manager = null)
protected $expiresAt = null;
const ACCESS_TOKEN_EXPIRATION_MARGIN = '20 seconds';
const LOCK_TIMEOUT = 5;
const LOCK_CHECK_STEP = 0.5;
public function __construct($client, array $params = [], $manager = null)
{
$this->client = $client;
@@ -90,7 +99,7 @@ abstract class OAuth2Abstract implements IClient
public function setParams(array $params)
{
foreach ($this->paramList as $name) {
if (!empty($params[$name])) {
if (array_key_exists($name, $params)) {
$this->setParam($name, $params[$name]);
}
}
@@ -103,6 +112,28 @@ abstract class OAuth2Abstract implements IClient
}
}
protected function getAccessTokenDataFromResponseResult($result)
{
$data = [];
$data['accessToken'] = $result['access_token'];
$data['tokenType'] = $result['token_type'];
$data['expiresAt'] = null;
if (isset($result['refresh_token']) && $result['refresh_token'] !== $this->refreshToken) {
$data['refreshToken'] = $result['refresh_token'];
}
if (isset($result['expires_in']) && is_numeric($result['expires_in'])) {
$data['expiresAt'] = (new \DateTime())
->modify('+' . $result['expires_in'] . ' seconds')
->format('Y-m-d H:i:s');
}
return $data;
}
public function getAccessTokenFromAuthorizationCode($code)
{
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_AUTHORIZATION_CODE, [
@@ -111,15 +142,16 @@ abstract class OAuth2Abstract implements IClient
]);
if ($r['code'] == 200) {
$data = [];
if (!empty($r['result'])) {
$data['accessToken'] = $r['result']['access_token'];
$data['tokenType'] = $r['result']['token_type'];
$data = $this->getAccessTokenDataFromResponseResult($r['result']);
$data['refreshToken'] = $r['result']['refresh_token'];
return $data;
} else {
$GLOBALS['log']->debug("OAuth getAccessTokenFromAuthorizationCode; Response: " . json_encode($r));
return null;
}
return $data;
} else {
$GLOBALS['log']->debug("OAuth getAccessTokenFromAuthorizationCode; Response: " . json_encode($r));
}
@@ -144,9 +176,69 @@ abstract class OAuth2Abstract implements IClient
}
}
public function handleAccessTokenActuality()
{
if ($this->getParam('expiresAt')) {
try {
$dt = new \DateTime($this->getParam('expiresAt'));
$dt->modify('-' . $this::ACCESS_TOKEN_EXPIRATION_MARGIN);
} catch (\Exception $e) {
return;
}
if ($dt->format('U') <= (new \DateTime())->format('U')) {
$GLOBALS['log']->debug("Oauth: Refreshing expired token for client {$this->clientId}.");
$until = microtime(true) + $this::LOCK_TIMEOUT;
if ($this->isLocked()) {
while (true) {
usleep($this::LOCK_CHECK_STEP * 1000000);
if (!$this->isLocked()) {
$GLOBALS['log']->debug("Oauth: Waited until unlocked for client {$this->clientId}.");
$this->reFetch();
return;
}
if (microtime(true) > $until) {
$GLOBALS['log']->debug("Oauth: Waited until unlocked but timed out for client {$this->clientId}.");
$this->unlock();
break;
}
}
}
$this->refreshToken();
}
}
}
protected function isLocked()
{
return $this->manager->isClientLocked($this);
}
protected function lock()
{
$this->manager->lockClient($this);
}
protected function unlock()
{
$this->manager->unlockClient($this);
}
protected function reFetch()
{
$this->manager->reFetchClient($this);
}
public function request($url, $params = null, $httpMethod = Client::HTTP_METHOD_GET, $contentType = null, $allowRenew = true)
{
$httpHeaders = array();
$this->handleAccessTokenActuality();
$httpHeaders = [];
if (!empty($contentType)) {
$httpHeaders['Content-Type'] = $contentType;
switch ($contentType) {
@@ -192,24 +284,43 @@ abstract class OAuth2Abstract implements IClient
protected function refreshToken()
{
if (!empty($this->refreshToken)) {
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_REFRESH_TOKEN, array(
'refresh_token' => $this->refreshToken,
));
if ($r['code'] == 200) {
if (is_array($r['result'])) {
if (!empty($r['result']['access_token'])) {
$data = array();
$data['accessToken'] = $r['result']['access_token'];
$data['tokenType'] = $r['result']['token_type'];
if (empty($this->refreshToken)) {
throw new Error(
"Oauth: Could not refresh token for client {$this->clientId}, because refreshToken is empty."
);
}
$this->setParams($data);
$this->afterTokenRefreshed($data);
return true;
}
$this->lock();
try {
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_REFRESH_TOKEN, [
'refresh_token' => $this->refreshToken,
]);
} catch (\Exception $e) {
$this->unlock();
throw new Error("Oauth: Error while refreshing token: " . $e->getMessage());
}
if ($r['code'] == 200) {
if (is_array($r['result'])) {
if (!empty($r['result']['access_token'])) {
$data = $this->getAccessTokenDataFromResponseResult($r['result']);
$this->setParams($data);
$this->afterTokenRefreshed($data);
$this->unlock();
return true;
}
}
}
$this->unlock();
$GLOBALS['log']->error("Oauth: Refreshing token failed for client {$this->clientId}: " . json_encode($r));
return false;
}
protected function handleErrorResponse($r)
@@ -217,21 +328,20 @@ abstract class OAuth2Abstract implements IClient
if ($r['code'] == 401 && !empty($r['result'])) {
$result = $r['result'];
if (strpos($r['header'], 'error=invalid_token') !== false) {
return array(
return [
'action' => 'refreshToken'
);
];
} else {
return array(
return [
'action' => 'renew'
);
];
}
} else if ($r['code'] == 400 && !empty($r['result'])) {
if ($r['result']['error'] == 'invalid_token') {
return array(
return [
'action' => 'refreshToken'
);
];
}
}
}
}

View File

@@ -62,6 +62,8 @@ class Client
protected $accessToken = null;
protected $expiresAt = null;
protected $authType = self::AUTH_TYPE_URI;
protected $tokenType = self::TOKEN_TYPE_URI;
@@ -72,9 +74,9 @@ class Client
protected $certificateFile = null;
protected $curlOptions = array();
protected $curlOptions = [];
public function __construct(array $params = array())
public function __construct(array $params = [])
{
if (!extension_loaded('curl')) {
throw new \Exception('CURL extension not found.');
@@ -121,12 +123,17 @@ class Client
$this->tokenType = $tokenType;
}
public function setExpiresAt($value)
{
$this->expiresAt = $value;
}
public function setAccessTokenSecret($accessTokenSecret)
{
$this->accessTokenSecret = $accessTokenSecret;
}
public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = array())
public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = [])
{
if ($this->accessToken) {
switch ($this->tokenType) {
@@ -148,7 +155,7 @@ class Client
return $this->execute($url, $params, $httpMethod, $httpHeaders);
}
private function execute($url, $params = null, $httpMethod, array $httpHeaders = array())
private function execute($url, $params = null, $httpMethod, array $httpHeaders = [])
{
$curlOptions = array(
CURLOPT_RETURNTRANSFER => true,
@@ -200,13 +207,11 @@ class Client
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
if (!empty($this->certificateFile)) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CAINFO, $this->certificateFile);
} else {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
}
if (!empty($this->curlOptions)) {
@@ -243,7 +248,7 @@ class Client
{
$params['grant_type'] = $grantType;
$httpHeaders = array();
$httpHeaders = [];
switch ($this->tokenType) {
case self::AUTH_TYPE_URI:
case self::AUTH_TYPE_FORM:
@@ -261,4 +266,3 @@ class Client
return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders);
}
}

View File

@@ -46,9 +46,11 @@ class ArrayType extends BaseType
public function checkArray(\Espo\ORM\Entity $entity, string $field, $validationValue, $data) : bool
{
if (!$entity->has($field) || $entity->get($field) === null) return true;
if (isset($data->$field) && $data->$field !== null && !is_array($data->$field)) {
return false;
}
return is_array($entity->get($field));
return true;
}
protected function isNotEmpty(\Espo\ORM\Entity $entity, $field)

View File

@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\FieldValidators;
class JsonArrayType extends BaseType
{
public function checkArray(\Espo\ORM\Entity $entity, string $field, $validationValue, $data) : bool
{
if (!$entity->has($field) || $entity->get($field) === null) return true;
return is_array($entity->get($field));
}
protected function isNotEmpty(\Espo\ORM\Entity $entity, $field)
{
if (!$entity->has($field) || $entity->get($field) === null) return false;
$list = $entity->get($field);
if (!is_array($list)) return false;
if (count($list)) return true;
return false;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\ArrayGroup;
use Espo\Core\Exceptions\Error;
class AtType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 2) throw new Error("Formula: array\\at: Not enough arguments.");
$array = $args[0];
$index = $args[1];
if (!is_array($array)) throw new Error("Formula: array\\at: First argument must be array.");
if (!is_int($index)) throw new Error("Formula: array\\at: Second argument must be integer.");
if (!array_key_exists($index, $array)) {
$GLOBALS['log']->notice("Formula: array\\at: Index doesn't exist.");
return null;
}
return $array[$index];
}
}

View File

@@ -122,4 +122,9 @@ abstract class Base implements Injectable
return $eArgs;
}
}
protected function fetchRawArguments(\StdClass $item)
{
return $item->value ?? [];
}
}

View File

@@ -62,9 +62,9 @@ class DayOfWeekType extends \Espo\Core\Formula\Functions\Base
if (empty($value)) return -1;
if (strlen($value) > 11) {
$resultString = $this->getInjection('dateTime')->convertSystemDateTime($value, $timezone, 'e');
$resultString = $this->getInjection('dateTime')->convertSystemDateTime($value, $timezone, 'd');
} else {
$resultString = $this->getInjection('dateTime')->convertSystemDate($value, 'e');
$resultString = $this->getInjection('dateTime')->convertSystemDate($value, 'd');
}
$result = intval($resultString);

View File

@@ -0,0 +1,123 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\ExtGroup\EmailGroup;
use Espo\Core\Exceptions\Error;
class ApplyTemplateType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('serviceFactory');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 2) throw new Error("Formula ext\email\applyTemplate: Too few arguments.");
$id = $args[0];
$templateId = $args[1];
$parentType = $args[2] ?? null;
$parentId = $args[3] ?? null;
if (!$id || !is_string($id))
throw new Error("Formula ext\\email\applyTemplate: 1st argument should be string and not be empty.");
if (!$templateId || !is_string($templateId))
throw new Error("Formula ext\\email\applyTemplate: 2nd argument should be string and not be empty.");
if ($parentType && !is_string($parentType))
throw new Error("Formula ext\\email\applyTemplate: 3st argument should be string.");
if ($parentId && !is_string($parentId))
throw new Error("Formula ext\\email\applyTemplate: 4th argument should be string.");
$em = $this->getInjection('entityManager');
$email = $em->getEntity('Email', $id);
$emailTemplate = $em->getEntity('EmailTemplate', $templateId);
if (!$email) {
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: Email {$id} does not exist.");
return false;
}
if (!$emailTemplate) {
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: EmailTemplate {$templateId} does not exist.");
return false;
}
$status = $email->get('status');
if ($status && in_array($status, ['Sent'])) {
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: Can't apply template to email with 'Sent' status.");
return false;
}
$emailTemplateService = $this->getInjection('serviceFactory')->create('EmailTemplate');
$params = [];
if (!$parentType || !$parentId) {
$parentType = $email->get('parentType');
$parentId = $email->get('parentId');
}
if ($parentType && $parentId) {
$params['parentType'] = $parentType;
$params['parentId'] = $parentId;
}
$emailAddressList = $email->get('toEmailAddresses');
if (count($emailAddressList)) {
$params['emailAddress'] = $emailAddressList[0]->get('name');
}
$data = $emailTemplateService->parseTemplate($emailTemplate, $params, true, true);
$attachmentsIds = $email->getLinkMultipleIdList('attachments');
$attachmentsIds = array_merge($attachmentsIds, $data['attachmentsIds']);
$email->set([
'name' => $data['subject'],
'body' => $data['body'],
'isHtml' => $data['isHtml'],
'attachmentsIds' => $attachmentsIds,
]);
$em->saveEntity($email, [
'modifiedById' => 'system',
]);
return true;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\ExtGroup\EmailGroup;
use Espo\Core\Exceptions\Error;
class SendType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('serviceFactory');
$this->addDependency('config');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 1) throw new Error("Formula ext\email\send: Too few arguments.");
$id = $args[0];
if (!$id) throw new Error("Formula ext\\email\send: First argument should not be empty.");
if (!is_string($id)) throw new Error("Formula ext\\email\send: First argument should be a string.");
$em = $this->getInjection('entityManager');
$email = $em->getEntity('Email', $id);
if (!$email) {
$GLOBALS['log']->warning("Formula ext\\email\send: Email {$id} does not exist.");
return false;
}
$status = $email->get('status');
if ($status && in_array($status, ['Sent'])) {
$GLOBALS['log']->warning("Formula ext\\email\send: Can't send email that has 'Sent' status.");
return false;
}
$service = $this->getInjection('serviceFactory')->create('Email');
$service->loadAdditionalFields($email);
$toSave = false;
if ($status !== 'Sending') {
$email->set('status', 'Sending');
$toSave = true;
}
if (!$email->get('from')) {
$from = $this->getInjection('config')->get('outboundEmailFromAddress');
if ($from) {
$email->set('from', $from);
$toSave = true;
}
}
if ($toSave) {
$em->saveEntity($email, [
'modifiedById' => 'system',
'silent' => true,
]);
}
try {
$service->sendEntity($email);
} catch (\Exception $e) {
$GLOBALS['log']->error("Formula ext\\email\send: Error while sending. Message: " . $e->getMessage());
return false;
}
return true;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\ExtGroup\PdfGroup;
use Espo\Core\Exceptions\Error;
class GenerateType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('serviceFactory');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 3) throw new Error("Formula ext\\pdf\\generate: Too few arguments.");
$entityType = $args[0];
$id = $args[1];
$templateId = $args[2];
$fileName = $args[3];
if (!$entityType || !is_string($entityType))
throw new Error("Formula ext\\pdf\\generate: 1st argument should be string and not be empty.");
if (!$id || !is_string($id))
throw new Error("Formula ext\\pdf\\generate: 2nd argument should be string and not be empty.");
if (!$templateId || !is_string($templateId))
throw new Error("Formula ext\\pdf\\generate: 3rd argument should be string and not be empty.");
if ($fileName && !is_string($fileName))
throw new Error("Formula ext\\pdf\\generate: 4rd argument should be string.");
$em = $this->getInjection('entityManager');
try {
$entity = $em->getEntity($entityType, $id);
} catch (\Exception $e) {
$GLOBALS['log']->error("Formula ext\\pdf\\generate: Message: " . $e->getMessage());
return null;
}
if (!$entity) {
$GLOBALS['log']->warning("Formula ext\\pdf\\generate: Record {$entityType} {$id} does not exist.");
return null;
}
$template = $em->getEntity('Template', $templateId);
if (!$template) {
$GLOBALS['log']->warning("Formula ext\\pdf\\generate: Template {$templateId} does not exist.");
return null;
}
if ($fileName) {
if (substr($fileName, -4) !== '.pdf') {
$fileName .= '.pdf';
}
} else {
$fileName = \Espo\Core\Utils\Util::sanitizeFileName($entity->get('name')) . '.pdf';
}
try {
$contents = $this->getInjection('serviceFactory')->create('Pdf')->buildFromTemplate($entity, $template);
} catch (\Exception $e) {
$GLOBALS['log']->error("Formula ext\\pdf\\generate: Error while generating. Message: " . $e->getMessage());
return null;
}
$attachment = $em->createEntity('Attachment', [
'name' => $fileName,
'type' => 'application/pdf',
'contents' => $contents,
'relatedId' => $id,
'relatedType' => $entityType,
'role' => 'Attachment',
]);
return $attachment->id;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\NumberGroup;
use Espo\Core\Exceptions\Error;
class RandomIntType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
$min = $args[0] ?? 0;
$max = $args[1] ?? PHP_INT_MAX;
if (!is_int($min) || !is_int($max) ) throw new Error("Non-integer arguments passed to function number\\randomInt.");
return random_int($min, $max);
}
}

View File

@@ -82,7 +82,7 @@ class CountType extends \Espo\Core\Formula\Functions\Base
while ($i < count($item->value) - 1) {
$key = $this->evaluate($item->value[$i]);
$value = $this->evaluate($item->value[$i + 1]);
$whereClause[$key] = $value;
$whereClause[] = [$key => $value];
$i = $i + 2;
}

View File

@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Error;
class CreateType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 1) throw new Error("Formula record\create: Too few arguments.");
$entityType = $args[0];
if (!is_string($entityType)) throw new Error("Formula record\create: First argument should be a string.");
$data = [];
$i = 1;
while ($i < count($args) - 1) {
$attribute = $args[$i];
if (!is_string($entityType)) throw new Error("Formula record\create: Attribute should be a string.");
$value = $args[$i + 1];
$data[$attribute] = $value;
$i = $i + 2;
}
$entity = $this->getInjection('entityManager')->createEntity($entityType, $data);
if ($entity) {
return $entity->id;
}
return null;
}
}

View File

@@ -61,7 +61,7 @@ class ExistsType extends \Espo\Core\Formula\Functions\Base
while ($i < count($item->value) - 1) {
$key = $this->evaluate($item->value[$i]);
$value = $this->evaluate($item->value[$i + 1]);
$whereClause[$key] = $value;
$whereClause[] = [$key => $value];
$i = $i + 2;
}

View File

@@ -0,0 +1,158 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Error;
class FindRelatedManyType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('selectManagerFactory');
$this->addDependency('metadata');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 4) {
throw new Error("Formula record\\findRelatedMany: Too few arguments.");
}
$entityManager = $this->getInjection('entityManager');
$entityType = $args[0];
$id = $args[1];
$link = $args[2];
$limit = $args[3];
$orderBy = null;
$order = null;
if (count($args) > 4) {
$orderBy = $args[4];
}
if (count($args) > 5) {
$order = $args[5];
}
if (!$entityType) throw new Error("Formula record\\findRelatedMany: Empty entityType.");
if (!is_string($entityType)) throw new Error("Formula record\\findRelatedMany: entityType should be string.");
if (!$id) {
$GLOBALS['log']->warning("Formula record\\findRelatedMany: Empty id.");
return [];
}
if (!is_string($id)) throw new Error("Formula record\\findRelatedMany: id should be string.");
if (!$link) throw new Error("Formula record\\findRelatedMany: Empty link.");
if (!is_string($link)) throw new Error("Formula record\\findRelatedMany: link should be string.");
if (!is_int($limit)) throw new Error("Formula record\\findRelatedMany: limit should be int.");
$entity = $entityManager->getEntity($entityType, $id);
if (!$entity) {
$GLOBALS['log']->notice("Formula record\\findRelatedMany: Entity {$entity} {$id} not found.");
return [];
}
$metadata = $this->getInjection('metadata');
if (!$orderBy) {
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
if (is_null($order)) {
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'asc';
}
} else {
$order = $order ?? 'asc';
}
$relationType = $entity->getRelationParam($link, 'type');
if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) {
throw new Error("Formula record\\findRelatedMany: Not supported link type '{$relationType}'.");
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) throw new Error("Formula record\\findRelatedMany: Bad or not supported link '{$link}'.");
$foreignLink = $entity->getRelationParam($link, 'foreign');
if (!$foreignLink) throw new Error("Formula record\\findRelatedMany: Not supported link '{$link}'.");
$selectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
$selectParams = $selectManager->getEmptySelectParams();
if ($relationType === 'hasChildren') {
$selectParams['whereClause'][] = [$foreignLink . 'Id' => $entity->id];
$selectParams['whereClause'][] = [$foreignLink . 'Type' => $entity->getEntityType()];
} else {
$selectManager->addJoin($foreignLink, $selectParams);
$selectParams['whereClause'][] = [$foreignLink . '.id' => $entity->id];
}
if (count($args) <= 7) {
$filter = null;
if (count($args) == 7) {
$filter = $args[6];
}
if ($filter) {
if (!is_string($filter)) throw new Error("Formula record\\findRelatedMany: Bad filter.");
$selectManager->applyFilter($filter, $selectParams);
}
} else {
$i = 6;
while ($i < count($args) - 1) {
$key = $args[$i];
$value = $args[$i + 1];
$selectParams['whereClause'][] = [$key => $value];
$i = $i + 2;
}
}
$selectParams['limit'] = $limit;
if ($orderBy) {
$selectManager->applyOrder($orderBy, $order, $selectParams);
}
$collection = $entityManager->getRepository($foreignEntityType)->select(['id'])->find($selectParams);
$idList = [];
foreach ($collection as $e) {
$idList[] = $e->id;
}
return $idList;
}
}

View File

@@ -37,6 +37,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
{
$this->addDependency('entityManager');
$this->addDependency('selectManagerFactory');
$this->addDependency('metadata');
}
public function process(\StdClass $item)
@@ -49,7 +50,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
throw new Error();
}
if (count($item->value) < 5) {
if (count($item->value) < 3) {
throw new Error();
}
@@ -58,8 +59,16 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
$entityType = $this->evaluate($item->value[0]);
$id = $this->evaluate($item->value[1]);
$link = $this->evaluate($item->value[2]);
$orderBy = $this->evaluate($item->value[3]);
$order = $this->evaluate($item->value[4]) ?? 'asc';
$orderBy = null;
$order = null;
if (count($item->value) > 3) {
$orderBy = $this->evaluate($item->value[3]);
}
if (count($item->value) > 4) {
$order = $this->evaluate($item->value[4]) ?? null;
}
if (!$entityType) throw new Error("Formula record\\findRelatedOne: Empty entityType.");
if (!$id) return null;
@@ -69,8 +78,29 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
if (!$entity) return null;
$metadata = $this->getInjection('metadata');
$relationType = $entity->getRelationParam($link, 'type');
if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) {
$relatedEntity = $entityManager->getRepository($entityType)->findRelated($entity, $link, [
'select' => ['id'],
]);
if (!$relatedEntity) {
return null;
}
return $relatedEntity->id;
}
if (!$orderBy) {
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
if (is_null($order)) {
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'asc';
}
} else {
$order = $order ?? 'asc';
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) throw new Error("Formula record\\findRelatedOne: Bad or not supported link '{$link}'.");
@@ -80,7 +110,6 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
$selectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
$selectParams = $selectManager->getEmptySelectParams();
if ($relationType === 'hasChildren') {
$selectParams['whereClause'][] = [$foreignLink . 'Id' => $entity->id];
$selectParams['whereClause'][] = [$foreignLink . 'Type' => $entity->getEntityType()];
@@ -92,7 +121,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
if (count($item->value) <= 6) {
$filter = null;
if (count($item->value) == 6) {
$filter = $this->evaluate($item->value[3]);
$filter = $this->evaluate($item->value[5]);
}
if ($filter) {
if (!is_string($filter)) throw new Error("Formula record\\findRelatedOne: Bad filter.");

View File

@@ -61,9 +61,19 @@ class RelateType extends \Espo\Core\Formula\Functions\Base
$entity = $em->getEntity($entityType, $id);
if (!$entity) return null;
if ($em->getRepository($entityType)->isRelated($entity, $link, $foreignId))
if (is_array($foreignId)) {
foreach ($foreignId as $itemId) {
$em->getRepository($entityType)->relate($entity, $link, $itemId);
}
return true;
return $em->getRepository($entityType)->relate($entity, $link, $foreignId);
} else if (is_string($foreignId)) {
if ($em->getRepository($entityType)->isRelated($entity, $link, $foreignId)) {
return true;
}
return $em->getRepository($entityType)->relate($entity, $link, $foreignId);
} else {
throw new Error("Formula record\\relate: foreignId type is wrong.");
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Error;
class UpdateType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
}
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 2) throw new Error("Formula record\update: Too few arguments.");
$entityType = $args[0];
$id = $args[1];
if (!is_string($entityType)) throw new Error("Formula record\update: First argument should be a string.");
if (!is_string($id)) throw new Error("Formula record\update: Second argument should be a string.");
$data = [];
$i = 2;
while ($i < count($args) - 1) {
$attribute = $args[$i];
if (!is_string($entityType)) throw new Error("Formula record\update: Attribute should be a string.");
$value = $args[$i + 1];
$data[$attribute] = $value;
$i = $i + 2;
}
$em = $this->getInjection('entityManager');
$entity = $em->getEntity($entityType, $id);
if ($entity) {
$entity->set($data);
if ($em->saveEntity($entity)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\StringGroup;
use \Espo\Core\Exceptions\Error;
class MatchAllType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 2) throw new Error("Formula: string\\matchAll: Too few arguments.");
$string = $args[0];
$regexp = $args[1];
if (!is_string($string)) return null;
if (!is_string($regexp)) return null;
$offset = $args[2] ?? 0;
$matches = null;
$result = preg_match_all($regexp, $string, $matches, \PREG_PATTERN_ORDER, $offset);
if (!$result) return null;
$matchesResult = [];
if (!count($matches)) return null;
return $matches[0];
}
}

View File

@@ -0,0 +1,61 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\StringGroup;
use \Espo\Core\Exceptions\Error;
class MatchType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 2) throw new Error("Formula: string\\match: Too few arguments.");
$string = $args[0];
$regexp = $args[1];
if (!is_string($string)) return null;
if (!is_string($regexp)) return null;
$offset = $args[2] ?? 0;
$matches = null;
$result = preg_match($regexp, $string, $matches, 0, $offset);
if (!$result) return null;
$matchesResult = [];
if (!count($matches)) return null;
return $matches[0];
}
}

View File

@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\StringGroup;
use Espo\Core\Exceptions\Error;
class ReplaceType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $this->fetchArguments($item);
if (count($args) < 3) throw new Error("Formula: string\\replace: Too few arguments.");
$string = $args[0];
$search = $args[1];
$replace = $args[2];
if (!is_string($string)) {
$GLOBALS['log']->warning("Formula: string\\replace: 1st argument should be string.");
return '';
}
if (!is_string($search)) {
$GLOBALS['log']->warning("Formula: string\\replace: 2nd argument should be string.");
return $string;
}
if (!is_string($replace)) {
$GLOBALS['log']->warning("Formula: string\\replace: 3rd argument should be string.");
return $string;
}
return str_replace($search, $replace, $string);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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;
use Espo\Core\Exceptions\Error;
class WhileType extends Base
{
public function process(\StdClass $item)
{
$args = $this->fetchRawArguments($item);
if (count($args) < 2) {
throw new Error("Function \'while\' should receieve 2 arguments.");
}
while ($this->evaluate($args[0])) {
$this->evaluate($args[1]);
}
}
}

View File

@@ -109,6 +109,8 @@ class Parser
{
$isString = false;
$isSingleQuote = false;
$isComment = false;
$isLineComment = false;
$modifiedString = $string;
@@ -116,27 +118,31 @@ class Parser
for ($i = 0; $i < strlen($string); $i++) {
$isStringStart = false;
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isSingleQuote = true;
$isStringStart = true;
} else {
if ($isSingleQuote) {
$isString = false;
if (!$isLineComment && !$isComment) {
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isSingleQuote = true;
$isStringStart = true;
} else {
if ($isSingleQuote) {
$isString = false;
}
}
}
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isStringStart = true;
$isSingleQuote = false;
} else {
if (!$isSingleQuote) {
$isString = false;
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isStringStart = true;
$isSingleQuote = false;
} else {
if (!$isSingleQuote) {
$isString = false;
}
}
}
}
if ($isString) {
if ($string[$i] === '(' || $string[$i] === ')') {
$modifiedString[$i] = '_';
@@ -144,24 +150,51 @@ class Parser
$modifiedString[$i] = ' ';
}
} else {
if ($string[$i] === '(') {
$braceCounter++;
}
if ($string[$i] === ')') {
$braceCounter--;
}
if (!$isLineComment && !$isComment) {
if ($braceCounter === 0) {
if (!is_null($splitterIndexList)) {
if ($string[$i] === ';') {
$splitterIndexList[] = $i;
if (!$isComment) {
if ($i && $string[$i] === '/' && $string[$i - 1] === '/') {
$isLineComment = true;
}
}
if ($intoOneLine) {
if ($string[$i] === "\r" || $string[$i] === "\n" || $string[$i] === "\t") {
$string[$i] = ' ';
if (!$isLineComment) {
if ($i && $string[$i] === '*' && $string[$i - 1] === '/') {
$isComment = true;
}
}
if ($string[$i] === '(') {
$braceCounter++;
}
if ($string[$i] === ')') {
$braceCounter--;
}
if ($braceCounter === 0) {
if (!is_null($splitterIndexList)) {
if ($string[$i] === ';') {
$splitterIndexList[] = $i;
}
}
if ($intoOneLine) {
if ($string[$i] === "\r" || $string[$i] === "\n" || $string[$i] === "\t") {
$string[$i] = ' ';
}
}
}
}
if ($isLineComment) {
if ($string[$i] === "\n") {
$isLineComment = false;
}
}
if ($isComment) {
if ($string[$i - 1] === "*" && $string[$i] === "/") {
$isComment = false;
}
}
}
}
@@ -367,9 +400,13 @@ class Parser
}
if (is_numeric($expression)) {
$value = filter_var($expression, FILTER_VALIDATE_INT) !== false ?
(int) $expression :
(float) $expression;
return (object) [
'type' => 'value',
'value' => ($expression == (int) $expression) ? (int) $expression : (float) $expression
'value' => $value,
];
}

View File

@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Htmlizer;
use Espo\Core\Container;
class Factory
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function create(bool $skipAcl = false)
{
return new Htmlizer(
$this->container->get('fileManager'),
$this->container->get('dateTime'),
$this->container->get('number'),
!$skipAcl ? $this->container->get('acl') : null,
$this->container->get('entityManager'),
$this->container->get('metadata'),
$this->container->get('defaultLanguage'),
$this->container->get('config'),
$this->container->get('serviceFactory')
);
}
}

View File

@@ -39,7 +39,7 @@ use Espo\Core\Utils\Config;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Metadata;
use Espo\ORM\EntityManager;
use Espo\Core\serviceFactory;
use Espo\Core\ServiceFactory;
use LightnCandy\LightnCandy as LightnCandy;
@@ -228,7 +228,8 @@ class Htmlizer
if ($fieldType === 'currency' && $this->metadata) {
if ($entity->getAttributeParam($attribute, 'attributeRole') === 'currency') {
if ($currencyValue = $data[$attribute]) {
$currencyValue = $data[$attribute] ?? null;
if ($currencyValue) {
$data[$attribute . 'Symbol'] = $this->metadata->get(['app', 'currency', 'symbolMap', $currencyValue]);
}
}
@@ -450,6 +451,16 @@ class Htmlizer
return $context['inverse'] ? $context['inverse']() : '';
}
},
'ifMultipleOf' => function () {
$args = func_get_args();
$context = $args[count($args) - 1];
if ($args[0] % $args[1] === 0) {
return $context['fn']();
} else {
return $context['inverse'] ? $context['inverse']() : '';
}
},
'tableTag' => function () {
$args = func_get_args();
$context = $args[count($args) - 1];
@@ -566,11 +577,13 @@ class Htmlizer
$data[$k] = $value;
}
$data['__config'] = $this->config;
$data['__dateTime'] = $this->dateTime;
$data['__metadata'] = $this->metadata;
$data['__entityManager'] = $this->entityManager;
$data['__language'] = $this->language;
$data['__serviceFactory'] = $this->serviceFactory;
$data['__entityType'] = $entity->getEntityType();
$html = $renderer($data);

View File

@@ -0,0 +1,40 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Loaders;
class HtmlizerFactory extends Base
{
public function load()
{
return new \Espo\Core\Htmlizer\Factory(
$this->getContainer()
);
}
}

View File

@@ -35,7 +35,8 @@ class MailSender extends Base
{
return new \Espo\Core\Mail\Sender(
$this->getContainer()->get('config'),
$this->getContainer()->get('entityManager')
$this->getContainer()->get('entityManager'),
$this->getContainer()->get('serviceFactory')
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Loaders;
class PasswordHash extends Base
{
public function load()
{
return new \Espo\Core\Utils\PasswordHash(
$this->getContainer()->get('config')
);
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Mail;
use \Zend\Mime\Mime as Mime;
use Laminas\Mime\Mime as Mime;
use \Espo\ORM\Entity;
use \Espo\ORM\Email;
@@ -72,7 +72,15 @@ class Importer
return $this->notificator;
}
public function importMessage($parserType = 'ZendMail', $message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null)
public function importMessage(
string $parserType,
$message,
$assignedUserId = null,
$teamsIdList = [],
$userIdList = [],
$filterList = [],
$fetchOnlyHeader = false,
$folderData = null)
{
$parser = $message->getParser();
$parserClassName = '\\Espo\\Core\\Mail\\Parsers\\' . $parserType;

View File

@@ -32,18 +32,11 @@
* 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.
************************************************************************/
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Espo\Core\Mail\Mail\Header;
use \Zend\Mail\Header;
use Zend\Mime\Mime;
use Laminas\Mail\Header;
use Laminas\Mime\Mime;
class XQueueItemId implements Header\HeaderInterface
{

View File

@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -40,9 +40,9 @@ use ArrayIterator;
use Countable;
use Iterator;
use Traversable;
use Zend\Loader\PluginClassLocator;
use Laminas\Loader\PluginClassLocator;
class Headers extends \Zend\Mail\Headers
class Headers extends \Laminas\Mail\Headers
{
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Mail\Mail\Storage;
class Imap extends \Zend\Mail\Storage\Imap
class Imap extends \Laminas\Mail\Storage\Imap
{
public function getIdsFromUID($uid)
{

View File

@@ -30,13 +30,13 @@
namespace Espo\Core\Mail\Mail\Storage;
use Espo\Core\Mail\Mail\Headers;
use Zend\Mail\Header\HeaderInterface;
use Zend\Mime;
use Zend\Mail\Storage\Exception;
use Zend\Mail\Storage\AbstractStorage;
use Zend\Stdlib\ErrorHandler;
use Laminas\Mail\Header\HeaderInterface;
use Laminas\Mime;
use Laminas\Mail\Storage\Exception;
use Laminas\Mail\Storage\AbstractStorage;
use Laminas\Stdlib\ErrorHandler;
class Message extends \Zend\Mail\Storage\Message
class Message extends \Laminas\Mail\Storage\Message
{
public function __construct(array $params)
{
@@ -79,7 +79,7 @@ class Message extends \Zend\Mail\Storage\Message
$this->headers = new Headers();
$this->headers->addHeaders($params['headers']);
} else {
if ($params['headers'] instanceof \Zend\Mail\Headers) {
if ($params['headers'] instanceof \Laminas\Mail\Headers) {
$this->headers = $params['headers'];
} else {
if (empty($params['noToplines'])) {
@@ -160,4 +160,3 @@ class Message extends \Zend\Mail\Storage\Message
$headers = Headers::fromString($headers, $EOL);
}
}

View File

@@ -43,7 +43,7 @@ class MessageWrapper
private $zendMessage = null;
protected $zendMessageClass = '\Zend\Mail\Storage\Message';
protected $zendMessageClass = '\Laminas\Mail\Storage\Message';
protected $fullRawContent = null;

View File

@@ -106,7 +106,7 @@ class MailMimeParser
foreach (['from', 'to', 'cc', 'reply-To'] as $type) {
$header = $this->getMessage($message)->getHeader($type);
if ($header) {
if ($header && method_exists($header, 'getAddresses')) {
$list = $header->getAddresses();
foreach ($list as $item) {
$address = $item->getEmail();
@@ -125,7 +125,7 @@ class MailMimeParser
{
$addressList = [];
$header = $this->getMessage($message)->getHeader($type);
if ($header) {
if ($header && method_exists($header, 'getAddresses')) {
foreach ($header->getAddresses() as $item) {
return [
'address' => $item->getEmail(),
@@ -140,7 +140,7 @@ class MailMimeParser
{
$addressList = [];
$header = $this->getMessage($message)->getHeader($type);
if ($header) {
if ($header && method_exists($header, 'getAddresses')) {
$list = $header->getAddresses();
foreach ($list as $address) {
$addressList[] = $address->getEmail();
@@ -181,7 +181,9 @@ class MailMimeParser
if ($bodyHtml) {
$email->set('isHtml', true);
$email->set('body', $bodyHtml);
$email->set('bodyPlain', $bodyPlain);
if ($bodyPlain) {
$email->set('bodyPlain', $bodyPlain);
}
} else {
$email->set('isHtml', false);
$email->set('body', $bodyPlain);
@@ -198,18 +200,24 @@ class MailMimeParser
foreach ($attachmentObjList as $attachmentObj) {
$attachment = $this->getEntityManager()->getEntity('Attachment');
$content = $attachmentObj->getContent();
$disposition = $attachmentObj->getHeaderValue('Content-Disposition');
$attachment = $this->getEntityManager()->getEntity('Attachment');
$contentType = $attachmentObj->getHeaderValue('Content-Type');
$filename = $attachmentObj->getHeaderParameter('Content-Disposition', 'filename', null);
if ($filename === null) {
$filename = $attachmentObj->getHeaderParameter('Content-Type', 'name', 'unnamed');
}
$attachment->set('name', $filename);
$attachment->set('type', $attachmentObj->getHeaderValue('Content-Type'));
$attachment->set('type', $contentType);
$content = '';
$binaryContentStream = $attachmentObj->getBinaryContentStream();
if ($binaryContentStream) {
$content = $binaryContentStream->getContents();
}
$contentId = $attachmentObj->getHeaderValue('Content-ID');
@@ -257,4 +265,3 @@ class MailMimeParser
}
}
}

View File

@@ -283,7 +283,7 @@ class ZendMail
protected function getContentFromPart($part)
{
if ($part instanceof \Zend\Mime\Part) {
if ($part instanceof \Laminas\Mime\Part) {
$content = $part->getRawContent();
if (strtolower($part->charset) != 'utf-8') {
$content = mb_convert_encoding($content, 'UTF-8', $part->charset);

View File

@@ -31,14 +31,14 @@ namespace Espo\Core\Mail;
use Espo\Entities\Email;
use Zend\Mime\Message as MimeMessage;
use Zend\Mime\Part as MimePart;
use Zend\Mime\Mime as Mime;
use Laminas\Mime\Message as MimeMessage;
use Laminas\Mime\Part as MimePart;
use Laminas\Mime\Mime as Mime;
use Zend\Mail\Message;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;
use Zend\Mail\Transport\Envelope;
use Laminas\Mail\Message;
use Laminas\Mail\Transport\Smtp as SmtpTransport;
use Laminas\Mail\Transport\SmtpOptions;
use Laminas\Mail\Transport\Envelope;
use Espo\Core\Exceptions\Error;
@@ -48,16 +48,28 @@ class Sender
protected $entityManager;
protected $serviceFactory;
protected $transport;
protected $isGlobal = false;
protected $params = [];
public function __construct($config, $entityManager)
private $systemInboundEmail = null;
private $inboundEmailService = null;
private $systemInboundEmailIsCached = false;
private $envelope = null;
public function __construct($config, $entityManager, $serviceFactory = null)
{
$this->config = $config;
$this->entityManager = $entityManager;
$this->serviceFactory = $serviceFactory;
$this->useGlobal();
}
@@ -74,6 +86,7 @@ class Sender
public function resetParams() : self
{
$this->params = [];
$this->envelope = null;
return $this;
}
@@ -86,6 +99,19 @@ class Sender
public function useSmtp(array $params = []) : self
{
$this->isGlobal = false;
$this->applySmtp($params);
return $this;
}
public function useGlobal()
{
$this->params = [];
$this->isGlobal = true;
return $this;
}
protected function applySmtp(array $params = [])
{
$this->params = $params;
$this->transport = new SmtpTransport();
@@ -106,11 +132,12 @@ class Sender
$options['connectionConfig'][$key] = $value;
}
if ($params['auth']) {
if (!empty($params['smtpAuthMechanism'])) {
$smtpAuthMechanism = preg_replace("([\.]{2,})", '', $params['smtpAuthMechanism']);
if (in_array($smtpAuthMechanism, ['login', 'crammd5', 'plain'])) {
$options['connectionClass'] = $smtpAuthMechanism;
if ($params['auth'] ?? false) {
$authMechanism = $params['authMechanism'] ?? $params['smtpAuthMechanism'] ?? null;
if ($authMechanism) {
$authMechanism = preg_replace("([\.]{2,})", '', $authMechanism);
if (in_array($authMechanism, ['login', 'crammd5', 'plain'])) {
$options['connectionClass'] = $authMechanism;
} else {
$options['connectionClass'] = 'login';
}
@@ -121,11 +148,12 @@ class Sender
$options['connectionConfig']['password'] = $params['password'];
}
if (!empty($params['smtpAuthClassName'])) {
$options['connectionClass'] = $params['smtpAuthClassName'];
$authClassName = $params['authClassName'] ?? $params['smtpAuthClassName'] ?? null;
if ($authClassName) {
$options['connectionClass'] = $authClassName;
}
if ($params['security']) {
if ($params['security'] ?? null) {
$options['connectionConfig']['ssl'] = strtolower($params['security']);
}
@@ -139,47 +167,79 @@ class Sender
$smtpOptions = new SmtpOptions($options);
$this->transport->setOptions($smtpOptions);
return $this;
if ($this->envelope) {
$this->transport->setEnvelope($this->envelope);
}
}
public function useGlobal()
protected function applyGlobal()
{
$this->params = [];
if ($this->isGlobal) {
return $this;
}
$this->transport = new SmtpTransport();
$config = $this->config;
$localHostName = $config->get('smtpLocalHostName', gethostname());
if (!$config->get('smtpServer') && $config->get('outboundEmailFromAddress')) {
$inboundEmail = $this->getSystemInboundEmail();
if ($inboundEmail) {
$service = $this->getInboundEmailService();
if ($service) {
$params = $service->getSmtpParamsFromAccount($inboundEmail);
$this->applySmtp($params);
return;
}
}
}
$options = [
'name' => $localHostName,
'host' => $config->get('smtpServer'),
$this->applySmtp([
'server' => $config->get('smtpServer'),
'port' => $config->get('smtpPort'),
'connection_config' => [],
];
if ($config->get('smtpAuth')) {
$options['connection_class'] = $config->get('smtpAuthMechanism', 'login');
$options['connection_config']['username'] = $config->get('smtpUsername');
$options['connection_config']['password'] = $config->get('smtpPassword');
}
if ($config->get('smtpSecurity')) {
$options['connection_config']['ssl'] = strtolower($config->get('smtpSecurity'));
'auth' => $config->get('smtpAuth'),
'authMechanism' => $config->get('smtpAuthMechanism', 'login'),
'username' => $config->get('smtpUsername'),
'password' => $config->get('smtpPassword'),
'security' => $config->get('smtpSecurity'),
]);
}
public function hasSystemSmtp()
{
if ($this->config->get('smtpServer')) return true;
if ($this->getSystemInboundEmail()) return true;
return false;
}
protected function getSystemInboundEmail()
{
$address = $this->config->get('outboundEmailFromAddress');
if (!$this->systemInboundEmailIsCached && $address) {
$this->systemInboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->where([
'status' => 'Active',
'useSmtp' => true,
'emailAddress' => $address,
])->findOne();
$this->systemInboundEmailIsCached = true;
}
$smtpOptions = new SmtpOptions($options);
$this->transport->setOptions($smtpOptions);
return $this->systemInboundEmail;
}
$this->isGlobal = true;
protected function getInboundEmailService()
{
if (!$this->serviceFactory) return null;
return $this;
if (!$this->inboundEmailService) {
$this->inboundEmailService = $this->serviceFactory->create('InboundEmail');
}
return $this->inboundEmailService;
}
public function send(Email $email, ?array $params = [], $message = null, $attachmentList = [])
{
if ($this->isGlobal) {
$this->applyGlobal();
}
if (!$message) {
$message = new Message();
}
@@ -226,7 +286,7 @@ class Sender
}
$email->set('fromString', $fromString);
$sender = new \Zend\Mail\Header\Sender();
$sender = new \Laminas\Mail\Header\Sender();
$sender->setAddress($email->get('from'));
$message->getHeaders()->addHeader($sender);
@@ -383,7 +443,7 @@ class Sender
$message->getHeaders()->addHeaderLine('Content-Type', 'text/plain; charset=UTF-8');
} else {
if (!$message->getHeaders()->has('content-type')) {
$contentTypeHeader = new \Zend\Mail\Header\ContentType();
$contentTypeHeader = new \Laminas\Mail\Header\ContentType();
$message->getHeaders()->addHeader($contentTypeHeader);
}
$message->getHeaders()->get('content-type')->setType($messageType);
@@ -403,7 +463,7 @@ class Sender
$messageId = substr($messageId, 1, strlen($messageId) - 2);
}
$messageIdHeader = new \Zend\Mail\Header\MessageId();
$messageIdHeader = new \Laminas\Mail\Header\MessageId();
$messageIdHeader->setId($messageId);
$message->getHeaders()->addHeader($messageIdHeader);
@@ -412,10 +472,12 @@ class Sender
$email->set('status', 'Sent');
$email->set('dateSent', date("Y-m-d H:i:s"));
} catch (\Exception $e) {
$this->resetParams();
$this->useGlobal();
throw new Error($e->getMessage(), 500);
}
$this->resetParams();
$this->useGlobal();
}
@@ -437,7 +499,8 @@ class Sender
public function setEnvelopeOptions(array $options) : self
{
$this->transport->setEnvelope(new Envelope($options));
$this->envelope = new Envelope($options);
return $this;
}
}

View File

@@ -0,0 +1,301 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Password;
use Espo\Core\Utils\Util;
use Espo\Entities\User;
use Espo\Entities\PasswordChangeRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\Error;
class Recovery implements \Espo\Core\Interfaces\Injectable
{
use \Espo\Core\Traits\Injectable;
const REQUEST_DELAY = 3000; //ms
const REQUEST_LIFETIME = '3 hours';
public function __construct()
{
$this->addDependencyList([
'entityManager',
'config',
'mailSender',
'htmlizerFactory',
'templateFileManager',
]);
}
public function getRequest(string $id) : PasswordChangeRequest
{
$config = $this->getInjection('config');
$em = $this->getInjection('entityManager');
if ($config->get('passwordRecoveryDisabled')) {
throw new Forbidden("Password recovery: Disabled.");
}
$request = $em->getRepository('PasswordChangeRequest')->where([
'requestId' => $id,
])->findOne();
if (!$request) {
throw new NotFound("Password recovery: Request not found by id.");
}
$userId = $request->get('userId');
if (!$userId) {
throw new Error();
}
return $request;
}
public function removeRequest(string $id)
{
$em = $this->getInjection('entityManager');
$request = $em->getRepository('PasswordChangeRequest')->where([
'requestId' => $id,
])->findOne();
if ($request) {
$em->removeEntity($request);
}
}
public function request(string $emailAddress, ?string $userName = null, ?string $url) : bool
{
$config = $this->getInjection('config');
$em = $this->getInjection('entityManager');
$noExposure = $config->get('passwordRecoveryNoExposure') ?? false;
if ($config->get('passwordRecoveryDisabled')) {
throw new Forbidden("Password recovery: Disabled.");
}
$user = $em->getRepository('User')->where([
'userName' => $userName,
'emailAddress' => $emailAddress,
])->findOne();
if (!$user) {
$this->fail("Password recovery: User {$emailAddress} not found.", 404);
return false;
}
if (!$user->isActive()) {
$this->fail("Password recovery: User {$user->id} is not active.");
return false;
}
if ($user->isApi() || $user->isSystem() || $user->isSuperAdmin()) {
$this->fail("Password recovery: User {$user->id} is not allowed.");
return false;
}
if ($config->get('passwordRecoveryForInternalUsersDisabled')) {
if ($user->isRegular() || $user->isAdmin()) {
$this->fail("Password recovery: User {$user->id} is not allowed, disabled for internal users.");
return false;
}
}
if ($config->get('passwordRecoveryForAdminDisabled')) {
if ($user->isAdmin()) {
$this->fail("Password recovery: User {$user->id} is not allowed, disabled for admin users.");
return false;
}
}
if (!$user->isAdmin() && $config->get('authenticationMethod', 'Espo') !== 'Espo') {
$this->fail("Password recovery: User {$user->id} is not allowed, authentication method is not 'Espo'.");
return false;
}
$passwordChangeRequest = $em->getRepository('PasswordChangeRequest')->where([
'userId' => $user->id,
])->findOne();
if ($passwordChangeRequest) {
if (!$noExposure) {
throw new Forbidden(json_encode(['reason' => 'Already-Sent']));
}
$this->fail("Password recovery: Denied for {$user->id}, already sent.");
return false;
}
$requestId = Util::generateCryptId();
$passwordChangeRequest = $em->getEntity('PasswordChangeRequest');
$passwordChangeRequest->set([
'userId' => $user->id,
'requestId' => $requestId,
'url' => $url,
]);
$microtime = microtime(true);
$this->send($requestId, $emailAddress, $user);
$em->saveEntity($passwordChangeRequest);
if (!$passwordChangeRequest->id) throw new Error();
$lifetime = $config->get('passwordRecoveryRequestLifetime') ?? self::REQUEST_LIFETIME;
$dt = new \DateTime();
$dt->modify('+' . $lifetime);
$em->createEntity('Job', [
'serviceName' => 'User',
'methodName' => 'removeChangePasswordRequestJob',
'data' => ['id' => $passwordChangeRequest->id],
'executeTime' => $dt->format('Y-m-d H:i:s'),
'queue' => 'q1',
]);
$timeDiff = $this->getDelay() - floor((microtime(true) - $microtime) / 1000);
if ($noExposure && $timeDiff > 0) {
$this->delay($timeDiff);
}
return true;
}
private function getDelay()
{
return $this->getInjection('config')->get('passwordRecoveryRequestDelay') ?? self::REQUEST_DELAY;
}
protected function delay(?int $delay = null)
{
$delay = $delay ?? $this->getDelay();
usleep($delay * 1000);
}
protected function send(string $requestId, string $emailAddress, User $user)
{
$config = $this->getInjection('config');
$em = $this->getInjection('entityManager');
$mailSender = $this->getInjection('mailSender');
$htmlizerFactory = $this->getInjection('htmlizerFactory');
$templateFileManager = $this->getInjection('templateFileManager');
if (!$emailAddress) return;
$email = $em->getEntity('Email');
if (!$mailSender->hasSystemSmtp() && !$config->get('internalSmtpServer')) {
throw new Error("Password recovery: SMTP credentials are not defined.");
}
$subjectTpl = $templateFileManager->getTemplate('passwordChangeLink', 'subject', 'User');
$bodyTpl = $templateFileManager->getTemplate('passwordChangeLink', 'body', 'User');
$siteUrl = $config->getSiteUrl();
if ($user->isPortal()) {
$portal = $em->getRepository('Portal')->distinct()->join('users')->where([
'isActive' => true,
'users.id' => $user->id,
])->findOne();
if ($portal) {
if ($portal->get('customUrl')) {
$siteUrl = $portal->get('customUrl');
}
}
}
$data = [];
$link = $siteUrl . '?entryPoint=changePassword&id=' . $requestId;
$data['link'] = $link;
$htmlizer = $htmlizerFactory->create(true);
$subject = $htmlizer->render($user, $subjectTpl, null, $data, true);
$body = $htmlizer->render($user, $bodyTpl, null, $data, true);
$email->set([
'subject' => $subject,
'body' => $body,
'to' => $emailAddress,
'isSystem' => true,
]);
if ($mailSender->hasSystemSmtp()) {
$mailSender->useGlobal();
} else {
$mailSender->useSmtp([
'server' => $config->get('internalSmtpServer'),
'port' => $config->get('internalSmtpPort'),
'auth' => $config->get('internalSmtpAuth'),
'username' => $config->get('internalSmtpUsername'),
'password' => $config->get('internalSmtpPassword'),
'security' => $config->get('internalSmtpSecurity'),
'fromAddress' => $config->get('internalOutboundEmailFromAddress', $config->get('outboundEmailFromAddress')),
]);
}
$mailSender->send($email);
}
private function fail(?string $msg = null, int $errorCode = 403)
{
$config = $this->getInjection('config');
$noExposure = $config->get('passwordRecoveryNoExposure') ?? false;
if ($msg) {
$GLOBALS['log']->warning($msg);
}
if (!$noExposure) {
if ($errorCode === 403) {
throw new Forbidden();
} else if ($errorCode === 404) {
throw new NotFound();
} else {
throw new Error();
}
}
$this->delay();
}
}

View File

@@ -27,6 +27,11 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
/************************************************************************
* This file contains code parts copied from TCPDF software library
* that is published under GNU Lesser General Public License.
************************************************************************/
namespace Espo\Core\Pdf;
define('K_TCPDF_EXTERNAL_CONFIG', true);
@@ -68,8 +73,12 @@ class Tcpdf extends \TCPDF
{
protected $footerHtml = '';
protected $headerHtml = '';
protected $footerPosition = 15;
protected $headerPosition = 10;
protected $useGroupNumbers = false;
public function serializeTCPDFtagParameters($data)
@@ -87,6 +96,11 @@ class Tcpdf extends \TCPDF
$this->useGroupNumbers = $value;
}
public function setHeaderHtml($html)
{
$this->headerHtml = $html;
}
public function setFooterHtml($html)
{
$this->footerHtml = $html;
@@ -97,6 +111,34 @@ class Tcpdf extends \TCPDF
$this->footerPosition = $position;
}
public function setHeaderPosition($position)
{
$this->headerPosition = $position;
}
public function Header()
{
$this->SetY($this->headerPosition);
$html = $this->headerHtml;
if ($this->useGroupNumbers) {
$html = str_replace('{pageNumber}', '{:png:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
} else {
$html = str_replace('{pageNumber}', '{:pnp:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
}
if ($this->isUnicodeFont()) {
$html = str_replace('{totalPageNumber}', '{{:ptp:}}', $html);
} else {
$html = str_replace('{totalPageNumber}', '{:ptp:}', $html);
}
$this->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, '', 0, false, 'T');
}
public function Footer()
{
$breakMargin = $this->getBreakMargin();

View File

@@ -38,6 +38,8 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
protected $reminderSkippingStatusList = ['Held', 'Not Held'];
protected $preserveDuration = true;
protected function init()
{
parent::init();
@@ -86,7 +88,13 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
}
if (!$entity->isNew()) {
if ($entity->isAttributeChanged('dateStart') && $entity->isAttributeChanged('dateStart') && !$entity->isAttributeChanged('dateEnd')) {
if (
$this->preserveDuration
&&
$entity->isAttributeChanged('dateStart') && $entity->get('dateStart')
&&
$entity->isAttributeChanged('dateStart') && !$entity->isAttributeChanged('dateEnd')
) {
$dateEndPrevious = $entity->getFetched('dateEnd');
$dateStartPrevious = $entity->getFetched('dateStart');
if ($dateStartPrevious && $dateEndPrevious) {
@@ -251,15 +259,17 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
protected function convertDateTimeToDefaultTimezone($string)
{
$dateTime = \DateTime::createFromFormat($this->getDateTime()->getInternalDateTimeFormat(), $string);
$timeZone = $this->getConfig()->get('timeZone');
if (empty($timeZone)) {
$timeZone = 'UTC';
}
$tz = $timezone = new \DateTimeZone($timeZone);
$timeZone = $this->getConfig()->get('timeZone') ?? 'UTC';
if ($dateTime) {
return $dateTime->setTimezone($tz)->format($this->getDateTime()->getInternalDateTimeFormat());
$tz = new \DateTimeZone($timeZone);
try {
$dt = \DateTime::createFromFormat($this->getDateTime()->getInternalDateTimeFormat(), $string, $tz);
} catch (\Exception $e) {}
if ($dt) {
$utcTz = new \DateTimeZone('UTC');
return $dt->setTimezone($utcTz)->format($this->getDateTime()->getInternalDateTimeFormat());
}
return null;
}

View File

@@ -163,7 +163,7 @@ class Base
$desc = $desc === strtolower('desc');
}
if (!empty($sortBy)) {
if ($sortBy) {
$result['orderBy'] = $sortBy;
$type = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'type']);
if (in_array($type, ['link', 'file', 'image', 'linkOne'])) {
@@ -171,13 +171,11 @@ class Base
} else if ($type === 'linkParent') {
$result['orderBy'] .= 'Type';
} else if ($type === 'address') {
if (!$desc) {
$orderPart = 'ASC';
} else {
$orderPart = 'DESC';
}
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . 'Street', $orderPart]];
return;
$result['orderBy'] = [
[$sortBy . 'Country', $desc],
[$sortBy . 'City', $desc],
[$sortBy . 'Street', $desc],
];
} else if ($type === 'enum') {
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
if ($list && is_array($list) && count($list)) {
@@ -190,8 +188,7 @@ class Base
foreach ($list as $i => $listItem) {
$list[$i] = str_replace(',', '_COMMA_', $listItem);
}
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
return;
$result['orderBy'] = [['LIST:' . $sortBy . ':' . implode(',', $list)]];
}
} else {
if (strpos($sortBy, '.') === false && strpos($sortBy, ':') === false) {
@@ -200,7 +197,27 @@ class Base
}
}
}
$orderByAttribute = null;
if (!is_array($result['orderBy'])) {
$orderByAttribute = $result['orderBy'];
$result['orderBy'] = [[$result['orderBy'], $desc]];
}
if (
$sortBy != 'id'
&&
(!$orderByAttribute || !$this->getSeed()->getAttributeParam($orderByAttribute, 'unique'))
&&
$this->getSeed()->hasAttribute('id')
) {
$result['orderBy'][] = ['id', $desc];
}
return;
}
if (!$desc) {
$result['order'] = 'ASC';
} else {
@@ -1863,6 +1880,7 @@ class Base
$method = 'filter' . ucfirst($filter);
if (method_exists($this, $method)) {
$this->$method($result);
return;
} else {
$className = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'filters', $filter, 'className']);
if ($className) {
@@ -1877,7 +1895,10 @@ class Base
}
$impl->applyFilter($this->entityType, $filter, $result, $this);
}
return;
}
$result['whereClause'][] = ['id' => null];
}
public function applyFilter(string $filter, array &$result)
@@ -2272,12 +2293,23 @@ class Base
$relevanceExpression = $fullTextSearchData['where'];
if (!isset($result['orderBy']) || $this->fullTextOrderType === self::FT_ORDER_RELEVANCE) {
$fullTextOrderType = $this->fullTextOrderType;
$orderTypeMap = [
'combined' => self::FT_ORDER_COMBINTED,
'relavance' => self::FT_ORDER_RELEVANCE,
'original' => self::FT_ORDER_ORIGINAL,
];
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
if ($mOrderType) $fullTextOrderType = $orderTypeMap[$mOrderType];
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
$result['orderBy'] = [[$relevanceExpression, 'desc']];
$result['order'] = null;
} else {
if ($this->fullTextOrderType === self::FT_ORDER_COMBINTED) {
$relevanceExpression =
if ($fullTextOrderType === self::FT_ORDER_COMBINTED) {
$relevanceExpression =
'ROUND:(DIV:(' . $fullTextSearchData['where'] . ','.$this->fullTextOrderRelevanceDivider.'))';
if (is_string($result['orderBy'])) {
@@ -2285,6 +2317,11 @@ class Base
[$relevanceExpression, 'desc'],
[$result['orderBy'], $result['order'] ?? 'asc'],
];
} else if (is_array($result['orderBy'])) {
$result['orderBy'] = array_merge(
[[$relevanceExpression, 'desc']],
$result['orderBy']
);
}
}
}
@@ -2657,9 +2694,9 @@ class Base
}
}
if (!empty($params['orderBy'])) {
$sortByField = $params['orderBy'];
$sortByField = $params['orderBy'] ?? $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
if ($sortByField) {
$sortByAttributeList = $this->getFieldManagerUtil()->getAttributeList($this->getEntityType(), $sortByField);
foreach ($sortByAttributeList as $attribute) {
if (!in_array($attribute, $attributeList) && $seed->hasAttribute($attribute)) {
@@ -2680,4 +2717,27 @@ class Base
return $attributeList;
}
protected function hasInOrderBy(string $attribute, array &$result)
{
$orderBy = $result['orderBy'] ?? null;
if (!$orderBy) return false;
if (is_string($orderBy)) {
return $attribute === $orderBy;
}
if (is_array($orderBy)) {
foreach ($orderBy as $item) {
if (is_array($item) && count($item)) {
if ($item[0] === $attribute) {
return true;
}
}
}
}
return false;
}
}

View File

@@ -0,0 +1,194 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\TemplateHelpers;
class GoogleMaps
{
public static function image()
{
$args = func_get_args();
$context = $args[count($args) - 1];
$hash = $context['hash'];
$data = $context['data']['root'];
$em = $data['__entityManager'];
$metadata = $data['__metadata'];
$config = $data['__config'];
$entityType = $data['__entityType'];
$field = $hash['field'] ?? null;
$size = $hash['size'] ?? '400x400';
$zoom = $hash['zoom'] ?? null;
$language = $hash['language'] ?? $config->get('language');
if (strpos($size, 'x') === false) {
$size = $size .'x' . $size;
}
if ($field && $metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']) !== 'address') {
$GLOBALS['log']->warning("Template helper _googleMapsImage: Specified field is not of address type.");
return null;
}
if (
!$field &&
!array_key_exists('street', $hash) &&
!array_key_exists('city', $hash) &&
!array_key_exists('country', $hash) &&
!array_key_exists('state', $hash) &&
!array_key_exists('postalCode', $hash)
) {
$field = ($entityType === 'Account') ? 'billingAddress' : 'address';
}
if ($field) {
$street = $data[$field . 'Street'] ?? null;
$city = $data[$field . 'City'] ?? null;
$country = $data[$field . 'Country'] ?? null;
$state = $data[$field . 'State'] ?? null;
$postalCode = $data[$field . 'postalCode'] ?? null;
} else {
$street = $hash['street'] ?? null;
$city = $hash['city'] ?? null;
$country = $hash['country'] ?? null;
$state = $hash['state'] ?? null;
$postalCode = $hash['postalCode'] ?? null;
}
$address = '';
if ($street) {
$address .= $street;
}
if ($city) {
if ($address != '') {
$address .= ', ';
}
$address .= $city;
}
if ($state) {
if ($address != '') {
$address .= ', ';
}
$address .= $state;
}
if ($postalCode) {
if ($state || $city) {
$address .= ' ';
} else {
if ($address) {
$address .= ', ';
}
}
$address .= $postalCode;
}
if ($country) {
if ($address != '') {
$address .= ', ';
}
$address .= $country;
}
$address = urlencode($address);
$apiKey = $config->get('googleMapsApiKey');
if (!$apiKey) {
$GLOBALS['log']->error("Template helper _googleMapsImage: No Google Maps API key.");
return null;
}
if (!$address) {
$GLOBALS['log']->debug("Template helper _googleMapsImage: No address to display.");
return null;
}
$format = 'jpg;';
$url = "https://maps.googleapis.com/maps/api/staticmap?" .
'center=' . $address .
'format=' . $format .
'&size=' . $size .
'&key=' . $apiKey;
if ($zoom) {
$url .= '&zoom=' . $zoom;
}
if ($language) {
$url .= '&language=' . $language;
}
$GLOBALS['log']->debug("Template helper _googleMapsImage: URL: {$url}.");
$image = \Espo\Core\TemplateHelpers\GoogleMaps::getImage($url);
if (!$image) {
return null;
}
$filePath = tempnam(sys_get_temp_dir(), 'google_maps_image');
file_put_contents($filePath, $image);
list($width, $height) = explode('x', $size);
$tag = "<img src=\"{$filePath}\" width=\"{$width}\" height=\"{$height}\">";
return new LightnCandy\SafeString($tag);
}
public static function getImage(string $url)
{
$headers = [];
$headers[] = 'Accept: image/jpeg, image/pjpeg';
$headers[] = 'Connection: Keep-Alive';
$agent = 'Mozilla/5.0';
$c = curl_init();
curl_setopt($c, \CURLOPT_URL, $url);
curl_setopt($c, \CURLOPT_HTTPHEADER, $headers);
curl_setopt($c, \CURLOPT_HEADER, 0);
curl_setopt($c, \CURLOPT_USERAGENT, $agent);
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($c, \CURLOPT_BINARYTRANSFER, 1);
$raw = curl_exec($c);
curl_close($c);
return $raw;
}
}

View File

@@ -8,6 +8,26 @@
"activitiesCreate": true,
"historyCreate": true
},
"bottomPanels": {
"detail": [
{
"name": "scheduler",
"label": "Scheduler",
"view": "crm:views/meeting/record/panels/scheduler",
"disabled": true,
"order": 3
}
],
"edit": [
{
"name": "scheduler",
"label": "Scheduler",
"view": "crm:views/meeting/record/panels/scheduler",
"disabled": true,
"order": 1
}
]
},
"filterList": [
{
"name":"planned"

View File

@@ -87,7 +87,8 @@
},
"links": {
"parent": {
"type": "belongsToParent"
"type": "belongsToParent",
"foreign": "{entityTypeLowerFirst}Children"
},
"createdBy": {
"type": "belongsTo",

View File

@@ -1,5 +1,5 @@
{
"labels": {
"Create {entityType}": "Vytvořit {entityTypeTranslated}"
"Create {entityType}": "Crear {entityTypeTranslated}"
}
}

View File

@@ -398,11 +398,19 @@ class Auth
$requestTimeFrom = (new \DateTime('@' . intval($_SERVER['REQUEST_TIME_FLOAT'])))->modify('-' . $failedAttemptsPeriod);
$failAttemptCount = $this->getEntityManager()->getRepository('AuthLogRecord')->where([
$failAttemptCount = 0;
$where = [
'requestTime>' => $requestTimeFrom->format('U'),
'ipAddress' => $_SERVER['REMOTE_ADDR'],
'isDenied' => true
])->count();
'isDenied' => true,
];
$wasFailed = !!$this->getEntityManager()->getRepository('AuthLogRecord')->select(['id'])->where($where)->findOne();
if ($wasFailed) {
$failAttemptCount = $this->getEntityManager()->getRepository('AuthLogRecord')->where($where)->count();
}
if ($failAttemptCount > $maxFailedAttempts) {
$GLOBALS['log']->warning("AUTH: Max failed login attempts exceeded for IP '".$_SERVER['REMOTE_ADDR']."'.");
@@ -434,7 +442,7 @@ class Auth
if ($authToken->get('secret')) {
$sentSecret = $_COOKIE['auth-token-secret'] ?? null;
if ($sentSecret === $authToken->get('secret')) {
setcookie('auth-token-secret', null, -1, '/');
$this->setSecretInCookie(null);
}
}
return true;
@@ -479,15 +487,21 @@ class Auth
$this->getEntityManager()->saveEntity($authLogRecord);
}
protected function setSecretInCookie(string $secret)
protected function setSecretInCookie(?string $secret)
{
if (!$secret) {
$time = -1;
} else {
$time = strtotime('+1000 days');
}
if (version_compare(\PHP_VERSION, '7.3.0') < 0) {
setcookie('auth-token-secret', $secret, strtotime('+1000 days'), '/', '', false, true);
setcookie('auth-token-secret', $secret, $time, '/', '', false, true);
return;
}
setcookie('auth-token-secret', $secret, [
'expires' => strtotime('+1000 days'),
'expires' => $time,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',

View File

@@ -53,6 +53,12 @@ class Espo extends Base
]
]);
if ($user && $authToken) {
if ($user->id !== $authToken->get('userId')) {
return null;
}
}
return $user;
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils\Authentication\LDAP;
class Client extends \Zend\Ldap\Ldap
class Client extends \Laminas\Ldap\Ldap
{
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core\Utils\Authentication\LDAP;
use \Espo\Core\Utils\Config;
class Utils
@@ -95,7 +96,7 @@ class Utils
);
/**
* accountCanonicalForm Map between Espo and Zend value
* accountCanonicalForm Map between Espo and Laminas value
*
* @var array
*/
@@ -181,7 +182,7 @@ class Utils
}
/**
* Get Zend options for using Zend\Ldap
* Get Laminas options for using Laminas\Ldap
*
* @return array
*/

View File

@@ -153,6 +153,9 @@ class ClientManager
'scriptsHtml' => $scriptsHtml,
'additionalStyleSheetsHtml' => $additionalStyleSheetsHtml,
'linksHtml' => $linksHtml,
'favicon196Path' => $this->getMetadata()->get(['app', 'client', 'favicon196']) ?? 'client/img/favicon196x196.png',
'faviconPath' => $this->getMetadata()->get(['app', 'client', 'favicon']) ?? 'client/img/favicon.ico',
'ajaxTimeout' => $this->getConfig()->get('ajaxTimeout') ?? 60000,
];
$html = file_get_contents($htmlFilePath);

View File

@@ -29,6 +29,8 @@
namespace Espo\Core\Utils;
use Espo\Core\Exceptions\Error;
class Config
{
private $defaultConfigPath = 'application/Espo/Core/defaults/config.php';
@@ -181,7 +183,16 @@ class Config
$removeData = empty($this->removeData) ? null : $this->removeData;
$data = include($this->configPath);
$configPath = $this->getConfigPath();
if (!file_exists($configPath)) {
throw new Error('Config file ['. $configPath .'] is not found.');
}
$data = include($configPath);
if (!is_array($data)) {
$data = include($configPath);
}
if (is_array($values)) {
foreach ($values as $key => $value) {
@@ -195,7 +206,21 @@ class Config
}
}
$result = $this->getFileManager()->putPhpContents($this->configPath, $data, true);
if (!is_array($data)) {
$GLOBALS['log']->error('Invalid config data ['. var_export($data, true) .'] while saving to ['. $configPath .'].');
throw new Error('Invalid config data while saving.');
}
$data['microtime'] = $microtime = microtime(true);
$result = $this->getFileManager()->putPhpContents($configPath, $data, true, true);
if ($result) {
$reloadedData = include($configPath);
if (!is_array($reloadedData) || $microtime !== ($reloadedData['microtime'] ?? null)) {
$result = $this->getFileManager()->putPhpContents($configPath, $data, true, false);
}
}
if ($result) {
$this->changedData = array();

View File

@@ -97,6 +97,6 @@ class Crypt
public function generateKey()
{
return \Espo\Core\Utils\Util::generateKey();
return \Espo\Core\Utils\Util::generateSecretKey();
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
class JsonArrayType extends \Doctrine\DBAL\Types\TextType
class JsonArrayType extends TextType
{
const JSON_ARRAY = 'jsonArray';
@@ -37,10 +37,4 @@ class JsonArrayType extends \Doctrine\DBAL\Types\TextType
{
return self::JSON_ARRAY;
}
public static function getDbTypeName()
{
return 'TEXT';
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
class JsonObjectType extends \Doctrine\DBAL\Types\TextType
class JsonObjectType extends TextType
{
const JSON_OBJECT = 'jsonObject';
@@ -37,10 +37,4 @@ class JsonObjectType extends \Doctrine\DBAL\Types\TextType
{
return self::JSON_OBJECT;
}
public static function getDbTypeName()
{
return 'TEXT';
}
}

View File

@@ -0,0 +1,52 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\DBAL\FieldTypes;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class LongtextType extends TextType
{
const LONGTEXT = 'longtext';
public function getName()
{
return self::LONGTEXT;
}
public static function getDbTypeName()
{
return 'longtext';
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'LONGTEXT';
}
}

View File

@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\DBAL\FieldTypes;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class TextType extends \Doctrine\DBAL\Types\TextType
{
public static function getDbTypeName()
{
return 'mediumtext';
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'MEDIUMTEXT';
}
}

View File

@@ -39,6 +39,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
public function diffColumn(Column $column1, Column $column2)
{
$changedProperties = array();
if ( $column1->getType() != $column2->getType() ) {
//espo: fix problem with executing query for custom types
@@ -82,15 +83,6 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
}
}
if ($column1->getType() instanceof \Doctrine\DBAL\Types\TextType) {
$length1 = $column1->getLength() ?: 16777215/* mediumtext length*/;
$length2 = $column2->getLength() ?: 16777215;
if ($length2 > $length1) {
$changedProperties[] = 'length';
}
}
if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) {
$changedProperties[] = 'precision';
@@ -124,7 +116,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
$changedProperties = array_merge($changedProperties, $diffKeys);
/** Espo: do not change a field length while changing other parameters */
/** Espo: do not change a field length downwards */
if (!empty($changedProperties) && !in_array('length', $changedProperties) && $column1->getType() instanceof \Doctrine\DBAL\Types\StringType) {
$length1 = $column1->getLength() ?: 255;
$length2 = $column2->getLength() ?: 255;
@@ -134,6 +126,15 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
$column2->setLength($length1);
}
}
if (in_array('type', $changedProperties) && $column1->getType() instanceof \Doctrine\DBAL\Types\TextType && isset($column1DbTypeName) && isset($column2DbTypeName)) {
$constName1 = '\Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform::LENGTH_LIMIT_' . strtoupper($column1DbTypeName);
$constName2 = '\Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform::LENGTH_LIMIT_' . strtoupper($column2DbTypeName);
if (defined($constName1) && defined($constName2) && constant($constName1) >= constant($constName2)) {
$changedProperties = array_diff($changedProperties, ['type']);
}
}
/** Espo: end */
return $changedProperties;

View File

@@ -209,7 +209,7 @@ class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
case 'text':
case 'mediumtext':
case 'longtext':
$length = $this->_platform->getClobTypeLength($dbType);
$length = null;
break;
/* Espo: end */
}

View File

@@ -145,14 +145,21 @@ class Helper
switch ($tableEngine) {
case 'InnoDB':
$version = $this->getFullDatabaseVersion();
$databaseType = $this->getDatabaseType();
$version = $this->getDatabaseVersion();
if (version_compare($version, '10.0.0') >= 0) {
return 767; //InnoDB, MariaDB
}
switch ($databaseType) {
case 'MariaDB':
if (version_compare($version, '10.2.2') >= 0) {
return 3072; //InnoDB, MariaDB 10.2.2+
}
break;
if (version_compare($version, '5.7.0') >= 0) {
return 3072; //InnoDB, MySQL 5.7+
case 'MySQL':
if (version_compare($version, '5.7.0') >= 0) {
return 3072; //InnoDB, MySQL 5.7+
}
break;
}
return 767; //InnoDB

View File

@@ -224,7 +224,7 @@ class Base
{
$foreignField = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $name]);
if ($foreignField['type'] == 'personName') {
if (isset($foreignField['type']) && $foreignField['type'] == 'personName') {
$personNameFormat = $this->config->get('personNameFormat');
switch ($personNameFormat) {

View File

@@ -81,13 +81,7 @@ class Converter
'index' => 'index',
/*'conditions' => 'conditions',
'additionalColumns' => 'additionalColumns', */
'default' => array(
'condition' => '^javascript:',
'conditionEquals' => false,
'value' => array(
'default' => '{0}',
),
),
'default' => 'default',
'select' => 'select',
'orderBy' => 'orderBy',
'where' => 'where',
@@ -178,6 +172,8 @@ class Converter
continue;
}
if ($entityMetadata['skipRebuild'] ?? false) $ormMetadata[$entityName]['skipRebuild'] = true;
$ormMetadata = Util::merge($ormMetadata, $this->convertEntity($entityName, $entityMetadata));
}
@@ -480,26 +476,24 @@ class Converter
protected function getInitValues(array $fieldParams)
{
$values = array();
$values = [];
foreach($this->fieldAccordances as $espoType => $ormType) {
if (isset($fieldParams[$espoType])) {
if (!array_key_exists($espoType, $fieldParams)) {
continue;
}
if (is_array($ormType)) {
$conditionRes = false;
if (!is_array($fieldParams[$espoType])) {
$conditionRes = preg_match('/'.$ormType['condition'].'/i', $fieldParams[$espoType]);
switch ($espoType) {
case 'default':
if (is_array($fieldParams[$espoType]) || !preg_match('/^javascript:/i', $fieldParams[$espoType])) {
$values[$ormType] = $fieldParams[$espoType];
}
break;
if (!$conditionRes || ($conditionRes && $conditionRes === $ormType['conditionEquals']) ) {
$value = is_array($fieldParams[$espoType]) ? json_encode($fieldParams[$espoType]) : $fieldParams[$espoType];
$values = Util::merge( $values, Util::replaceInArray('{0}', $value, $ormType['value']) );
}
} else {
default:
$values[$ormType] = $fieldParams[$espoType];
}
break;
}
}

View File

@@ -60,7 +60,7 @@ class Currency extends Base
]
];
$foreignAlias = "{$alias}{$entityType}Foreign";
$foreignAlias = "{$alias}{$entityType}{alias}Foreign";
$params = $this->getFieldParams($fieldName);
if (!empty($params['notStorable'])) {
@@ -99,6 +99,7 @@ class Currency extends Base
'orderBy' => [
'sql' => $converedFieldName . " {direction}",
'leftJoins' => $leftJoins,
'additionalSelect' => ["{$alias}.rate"],
],
'attributeRole' => 'valueConverted',
'fieldType' => 'currency',
@@ -107,6 +108,7 @@ class Currency extends Base
$defs[$entityType]['fields'][$fieldName]['orderBy'] = [
'sql' => $part . " * {$alias}.rate {direction}",
'leftJoins' => $leftJoins,
'additionalSelect' => ["{$alias}.rate"],
];
}

View File

@@ -33,8 +33,8 @@ class Email extends Base
{
protected function load($fieldName, $entityType)
{
$foreignJoinAlias = "{$fieldName}{$entityType}Foreign";
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}ForeignMiddle";
$foreignJoinAlias = "{$fieldName}{$entityType}{alias}Foreign";
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}{alias}ForeignMiddle";
return [
$entityType => [
@@ -53,6 +53,7 @@ class Email extends Base
[
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
"{$foreignJoinMiddleAlias}.primary" => 1,
"{$foreignJoinMiddleAlias}.deleted" => 0,
]
],
[
@@ -60,6 +61,7 @@ class Email extends Base
$foreignJoinAlias,
[
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.emailAddressId",
"{$foreignJoinAlias}.deleted" => 0,
]
]
],
@@ -108,6 +110,7 @@ class Email extends Base
'orderBy' => [
'sql' => 'emailAddresses.lower {direction}',
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
'additionalSelect' => ['emailAddresses.lower'],
],
],
$fieldName .'Data' => [
@@ -119,6 +122,28 @@ class Email extends Base
'type' => 'bool',
'notStorable' => true,
'select' => 'emailAddresses.opt_out',
'selectForeign' => [
'sql' => "{$foreignJoinAlias}.opt_out",
'leftJoins' => [
[
'EntityEmailAddress',
$foreignJoinMiddleAlias,
[
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
"{$foreignJoinMiddleAlias}.primary" => 1,
"{$foreignJoinMiddleAlias}.deleted" => 0,
]
],
[
'EmailAddress',
$foreignJoinAlias,
[
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.emailAddressId",
"{$foreignJoinAlias}.deleted" => 0,
]
]
],
],
'where' => [
'= TRUE' => [
'sql' => 'emailAddresses.opt_out = true AND emailAddresses.opt_out IS NOT NULL',
@@ -129,7 +154,11 @@ class Email extends Base
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
]
],
'orderBy' => 'emailAddresses.opt_out {direction}'
'orderBy' => [
'sql' => 'emailAddresses.opt_out {direction}',
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
'additionalSelect' => ['emailAddresses.opt_out'],
],
]
],
'relations' => [

View File

@@ -33,8 +33,8 @@ class Phone extends Base
{
protected function load($fieldName, $entityType)
{
$foreignJoinAlias = "{$fieldName}{$entityType}Foreign";
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}ForeignMiddle";
$foreignJoinAlias = "{$fieldName}{$entityType}{alias}Foreign";
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}{alias}ForeignMiddle";
return [
$entityType => [
@@ -53,6 +53,7 @@ class Phone extends Base
[
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
"{$foreignJoinMiddleAlias}.primary" => 1,
"{$foreignJoinMiddleAlias}.deleted" => 0,
]
],
[
@@ -60,6 +61,7 @@ class Phone extends Base
$foreignJoinAlias,
[
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.phoneNumberId",
"{$foreignJoinAlias}.deleted" => 0,
]
]
],
@@ -109,6 +111,7 @@ class Phone extends Base
'orderBy' => [
'sql' => 'phoneNumbers.name {direction}',
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
'additionalSelect' => ['phoneNumbers.name'],
],
),
$fieldName .'Data' => array(
@@ -120,6 +123,28 @@ class Phone extends Base
'type' => 'bool',
'notStorable' => true,
'select' => 'phoneNumbers.opt_out',
'selectForeign' => [
'sql' => "{$foreignJoinAlias}.opt_out",
'leftJoins' => [
[
'EntityPhoneNumber',
$foreignJoinMiddleAlias,
[
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
"{$foreignJoinMiddleAlias}.primary" => 1,
"{$foreignJoinMiddleAlias}.deleted" => 0,
]
],
[
'PhoneNumber',
$foreignJoinAlias,
[
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.phoneNumberId",
"{$foreignJoinAlias}.deleted" => 0,
]
]
],
],
'where' => [
'= TRUE' => [
'sql' => 'phoneNumbers.opt_out = true AND phoneNumbers.opt_out IS NOT NULL',
@@ -130,7 +155,11 @@ class Phone extends Base
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
]
],
'orderBy' => 'phoneNumbers.opt_out {direction}'
'orderBy' => [
'sql' => 'phoneNumbers.opt_out {direction}',
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
'additionalSelect' => ['phoneNumbers.opt_out'],
],
],
$fieldName . 'Numeric' => [
'type' => 'varchar',

View File

@@ -135,12 +135,14 @@ class RelationManager
}
if (isset($className) && $className !== false) {
$foreignLinkName = (is_array($foreignLink) && array_key_exists('name', $foreignLink)) ? $foreignLink['name'] : null;
$helperClass = new $className($this->metadata, $ormMetadata, $entityDefs, $this->config);
return $helperClass->process($linkName, $entityName, $foreignLink['name'], $foreignEntityName);
return $helperClass->process($linkName, $entityName, $foreignLinkName, $foreignEntityName);
}
//END: relationDefs defined in separate file
return null;
}
}
}

View File

@@ -181,11 +181,12 @@ class Converter
$schema = $this->getSchema(true);
$indexList = SchemaUtils::getIndexList($ormMeta);
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getMaxIndexLength());
$tables = array();
foreach ($ormMeta as $entityName => $entityParams) {
if ($entityParams['skipRebuild'] ?? false) continue;
$tableName = Util::toUnderScore($entityName);
if ($schema->hasTable($tableName)) {
@@ -225,10 +226,6 @@ class Converter
continue;
}
if (isset($fieldListExceededIndexMaxLength[$entityName]) && in_array($fieldName, $fieldListExceededIndexMaxLength[$entityName])) {
$fieldParams['utf8mb3'] = true;
}
$columnName = Util::toUnderScore($fieldName);
if (!$tables[$entityName]->hasColumn($columnName)) {
$tables[$entityName]->addColumn($columnName, $fieldType, $this->getDbFieldParams($fieldParams));
@@ -315,7 +312,7 @@ class Converter
$table->addIndex(array($columnName), SchemaUtils::generateIndexName($columnName));
$uniqueIndex[] = $columnName;
}
}
}
//END: add midKeys to a schema
//add additionalColumns

View File

@@ -238,7 +238,7 @@ class Utils
'varchar' => 255,
);
$type = static::getFieldType($ormFieldDefs);
$type = static::getDbFieldType($ormFieldDefs);
$length = isset($defaultLength[$type]) ? $defaultLength[$type] : $length;
$length = isset($ormFieldDefs['len']) ? $ormFieldDefs['len'] : $length;
@@ -252,8 +252,13 @@ class Utils
return $length;
}
protected static function getFieldType(array $ormFieldDefs)
protected static function getDbFieldType(array $ormFieldDefs)
{
return isset($ormFieldDefs['dbType']) ? $ormFieldDefs['dbType'] : $ormFieldDefs['type'];
}
protected static function getFieldType(array $ormFieldDefs)
{
return isset($ormFieldDefs['type']) ? $ormFieldDefs['type'] : static::getDbFieldType($ormFieldDefs);
}
}

View File

@@ -46,8 +46,7 @@ return [
'type' => 'id',
],
'data' => [
'type' => 'text',
'len' => 16777216,
'type' => 'text'
]
]
],

View File

@@ -176,7 +176,7 @@ class DateTime
$dateTime = new \DateTime();
$dateTime->setTimezone($tz);
$format = $format ?? $this->getDateTimeFormat();
$format = $format ?? $this->getDateFormat();
$carbon = Carbon::instance($dateTime);
$carbon->locale($this->language);

View File

@@ -341,6 +341,7 @@ class EntityManager
$filePath = $templatePath . "/Metadata/{$type}/entityDefs.json";
$entityDefsDataContents = $this->getFileManager()->getContents($filePath);
$entityDefsDataContents = str_replace('{entityType}', $name, $entityDefsDataContents);
$entityDefsDataContents = str_replace('{entityTypeLowerFirst}', lcfirst($name), $entityDefsDataContents);
$entityDefsDataContents = str_replace('{tableName}', $this->getEntityManager()->getQuery()->toDb($name), $entityDefsDataContents);
foreach ($replaceData as $key => $value) {
$entityDefsDataContents = str_replace('{'.$key.'}', $value, $entityDefsDataContents);
@@ -937,7 +938,7 @@ class EntityManager
'fields' => [
$link => [
'type' => 'linkParent',
'entityList' => $params['parentEntityTypeList'] ?? [],
'entityList' => $params['parentEntityTypeList'] ?? null,
],
],
'links' => [
@@ -980,7 +981,7 @@ class EntityManager
if ($linkType === 'childrenToParent') {
$foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
if ($linkForeign && is_array($foreignLinkEntityTypeList)) {
$this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
}
}
@@ -1093,7 +1094,8 @@ class EntityManager
if ($linkType === 'belongsToParent') {
$parentEntityTypeList = $params['parentEntityTypeList'] ?? null;
if ($parentEntityTypeList && is_array($parentEntityTypeList)) {
if (is_array($parentEntityTypeList)) {
$data = [
'fields' => [
$link => [
@@ -1106,7 +1108,7 @@ class EntityManager
}
$foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
if ($linkForeign && is_array($foreignLinkEntityTypeList)) {
$this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
}
}
@@ -1185,7 +1187,9 @@ class EntityManager
'links.' . $link,
]);
$this->getMetadata()->save();
$this->updateParentForeignLinks($entity, $link, $linkForeign, []);
if ($linkForeign) {
$this->updateParentForeignLinks($entity, $link, $linkForeign, []);
}
return true;
}

View File

@@ -38,6 +38,8 @@ class Manager
private $permissionDeniedList = array();
protected $tmpDir = 'data/tmp';
public function __construct(\Espo\Core\Utils\Config $config = null)
{
$params = null;
@@ -195,7 +197,7 @@ class Manager
*
* @return bool
*/
public function putContents($path, $data, $flags = 0)
public function putContents($path, $data, $flags = 0, bool $useRenaming = false)
{
$fullPath = $this->concatPaths($path); //todo remove after changing the params
@@ -203,12 +205,49 @@ class Manager
throw new Error('Permission denied for '. $fullPath);
}
$res = (file_put_contents($fullPath, $data, $flags) !== FALSE);
if ($res && function_exists('opcache_invalidate')) {
$result = false;
if ($useRenaming) {
$result = $this->putContentsUseRenaming($fullPath, $data);
}
if (!$result) {
$result = (file_put_contents($fullPath, $data, $flags) !== FALSE);
}
if ($result && function_exists('opcache_invalidate')) {
@opcache_invalidate($fullPath);
}
return $res;
return $result;
}
protected function putContentsUseRenaming($path, $data)
{
$tmpDir = $this->tmpDir;
if (!$this->isDir($tmpDir)) $this->mkdir($tmpDir);
if (!$this->isDir($tmpDir)) return false;
$tmpPath = tempnam($tmpDir, 'tmp');
$tmpPath = $this->getRelativePath($tmpPath);
if (!$tmpPath) return false;
if (!$this->isFile($tmpPath)) return false;
if (!$this->isWritable($tmpPath)) return false;
$h = fopen($tmpPath, 'w');
fwrite($h, $data);
fclose($h);
$this->getPermissionUtils()->setDefaultPermissions($tmpPath);
if (!$this->isReadable($tmpPath)) return false;
$result = rename($tmpPath, $path);
if ($this->isFile($tmpPath)) $this->removeFile($tmpPath);
return $result;
}
/**
@@ -219,9 +258,9 @@ class Manager
*
* @return bool
*/
public function putPhpContents($path, $data, $withObjects = false)
public function putPhpContents($path, $data, $withObjects = false, bool $useRenaming = false)
{
return $this->putContents($path, $this->wrapForDataExport($data, $withObjects), LOCK_EX);
return $this->putContents($path, $this->wrapForDataExport($data, $withObjects), LOCK_EX, $useRenaming);
}
/**
@@ -356,7 +395,7 @@ class Manager
protected function concatPaths($paths)
{
if (is_string($paths)) {
return $paths;
return Utils\Util::fixPath($paths);
}
$fullPath = '';
@@ -940,4 +979,24 @@ class Manager
return $fullPath;
}
public function getRelativePath($path, $basePath = null, $dirSeparator = null)
{
if (!$basePath) {
$basePath = getcwd();
}
$path = Utils\Util::fixPath($path);
$basePath = Utils\Util::fixPath($basePath);
if (!$dirSeparator) {
$dirSeparator = Utils\Util::getSeparator();
}
if (substr($basePath, -1) != $dirSeparator) {
$basePath .= $dirSeparator;
}
return preg_replace('/^'. preg_quote($basePath, $dirSeparator) . '/', '', $path);
}
}

View File

@@ -52,6 +52,9 @@ class Permission
'application/Espo/Modules' => [
'recursive' => false,
],
'client/custom' => [
'recursive' => true,
],
'client/modules' => [
'recursive' => false,
],

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