Compare commits

...

501 Commits
5.1.1 ... 5.3.5

Author SHA1 Message Date
yuri
94c00e2901 orm: revert forcing updating json object 2018-07-27 11:59:22 +03:00
yuri
7de988104b link multiple attachment multiple list view fixes 2018-07-27 11:55:19 +03:00
yuri
03bc90968c fix email sending 2 2018-07-27 11:32:29 +03:00
yuri
a5401a22b9 fix email sending 2018-07-27 11:13:49 +03:00
yuri
4c1375d8b2 user buttons links 2018-07-27 10:46:18 +03:00
yuri
bab4a3b0d6 fix full text 2018-07-27 10:40:48 +03:00
yuri
33e135f227 fix email fetching 2018-07-27 10:03:26 +03:00
yuri
56b9d8d5c1 fix campaign 2018-07-26 17:56:35 +03:00
yuri
38ec88e302 fix formatting 2018-07-26 17:02:31 +03:00
yuri
10be208d8d fix linkedWith filter 2018-07-26 12:26:25 +03:00
yuri
38d227a948 fix model sync 2018-07-26 10:55:55 +03:00
yuri
1a8e4435e2 use put istead of patch 2018-07-25 15:04:33 +03:00
yuri
12febbdff5 version 2018-07-25 12:44:45 +03:00
yuri
ebea168350 field ui fixes 2018-07-25 12:41:48 +03:00
yuri
704519f80b fix setFetched link multiple and link one 2018-07-25 11:38:48 +03:00
yuri
5f1000ddd3 fix audited 2018-07-25 11:04:17 +03:00
yuri
2ee7e6cf4a orm: isUnordered param 2018-07-25 10:57:53 +03:00
yuri
2989975829 audited fix 2018-07-25 10:41:39 +03:00
yuri
a4c068427d cleanup 2018-07-24 18:22:54 +03:00
yuri
e1a76b9924 orm: isAttributeChanged improvement 2018-07-24 18:22:04 +03:00
yuri
1f2efcd716 link multiple available in list fix 2018-07-23 12:59:04 +03:00
yuri
576dfe068f fix array field setOptionList 2018-07-20 15:51:21 +03:00
yuri
56f8e68599 fix wysiwyg width 2018-07-19 14:56:28 +03:00
yuri
e8d75808ef fix email scrolling 2018-07-19 14:43:35 +03:00
yuri
bc8bf89d6e css fixes 2018-07-19 12:14:06 +03:00
yuri
91385cc3f0 fix naming 2018-07-19 11:49:43 +03:00
yuri
ad21511c72 email list is replied fix 2018-07-19 11:44:40 +03:00
yuri
938a24f102 fix assignment email notificaiotion 2018-07-19 10:59:32 +03:00
yuri
43b041ed6c fix replied icon 2018-07-18 15:37:37 +03:00
yuri
5e3f048795 replied email icon 2018-07-18 14:57:12 +03:00
yuri
3aec3ef6d2 formula higher 2018-07-18 11:37:55 +03:00
yuri
712a450734 formula mb string functions 2018-07-18 11:35:31 +03:00
yuri
5675b6721b sort by list comma fix 2018-07-18 11:25:58 +03:00
yuri
a4e23b8adb formula parser fix 2018-07-18 10:29:06 +03:00
yuri
9d49f418c8 orm fix order list 2018-07-18 10:11:36 +03:00
yuri
ea9f70dffa global search ability to select text 2018-07-17 17:26:06 +03:00
yuri
b624accbe8 version 2018-07-17 17:07:50 +03:00
yuri
12f974635e global search fixes 2018-07-17 17:07:32 +03:00
yuri
cfce68eb7e default panel complex fields 2018-07-17 15:38:44 +03:00
yuri
db8fbd1ed3 hide default panel if empty 2018-07-17 14:39:54 +03:00
yuri
06395f3ff5 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 15:48:49 +03:00
Taras Machyshyn
4065ec5477 Test fixes 2018-07-16 15:48:25 +03:00
yuri
31d45a0583 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 14:20:29 +03:00
yuri
696faa4468 fix wysiwyg toolbar 2018-07-16 14:20:19 +03:00
Taras Machyshyn
625876f123 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 14:06:54 +03:00
Taras Machyshyn
974cd05276 Integration test for fulltext index 2018-07-16 14:06:37 +03:00
yuri
fb5db991a3 fix vars 2018-07-16 13:37:00 +03:00
yuri
3dafb7e922 fix categories 2018-07-16 13:12:09 +03:00
yuri
ee640273a1 fix stream attachments 2018-07-16 10:51:55 +03:00
yuri
b92de17f72 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-13 16:55:22 +03:00
Taras Machyshyn
abc48ca76f Fulltext bug fixes 2018-07-13 16:52:02 +03:00
yuri
46443aae7e fix full text search 2018-07-13 16:37:32 +03:00
yuri
a88c3283d6 full text search fix 2018-07-13 16:33:19 +03:00
yuri
2b9653ee0b replace tabs 2018-07-13 14:53:37 +03:00
yuri
2cf79abdb1 fix list view remove 2018-07-13 14:49:56 +03:00
yuri
20a000a46c fix full text search 2018-07-13 12:33:21 +03:00
ayman-alkom
95bfd5bace fix typo (#968) 2018-07-13 11:51:34 +03:00
yuri
da736008f1 version 2018-07-13 11:19:49 +03:00
yuri
1de98d9616 text filter fixes 2018-07-13 11:19:30 +03:00
yuri
31a6143cc3 email template text filter 2018-07-13 11:07:34 +03:00
yuri
8502a84a3f chart mousetick fix 2018-07-12 12:52:05 +03:00
yuri
2234678728 email search tests 2018-07-12 11:31:44 +03:00
yuri
92fedd2c2e Merge branch 'hotfix/5.3.2' of ssh://172.20.0.1/var/git/espo/backend 2018-07-12 10:09:31 +03:00
Taras Machyshyn
0ec450d59d Schema bug fixes 2018-07-12 10:09:01 +03:00
yuri
f0a1634f90 version 2018-07-12 10:03:04 +03:00
Taras Machyshyn
5b5f5c8ab7 Schema bug fixes 2018-07-11 15:16:59 +03:00
yuri
19d37e8081 text filter disabling 2018-07-11 11:04:01 +03:00
yuri
d8ab10fd75 textFilterDisabled param 2018-07-11 10:37:17 +03:00
yuri
312de11a15 smtp local host name 2018-07-10 15:53:15 +03:00
yuri
47a22042b2 fix select records 2 2018-07-10 13:13:07 +03:00
yuri
31cea9e36e fix select records 2018-07-10 12:26:37 +03:00
yuri
2b983ad880 fix attachment multiple 2018-07-10 12:11:45 +03:00
yuri
21b0c2b2eb attachment multiple in list view 2018-07-10 11:58:11 +03:00
yuri
59b4aa61e4 not sortable by default 2018-07-10 11:36:20 +03:00
yuri
25d91a1e73 Merge branch 'hotfix/5.3.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.1 2018-07-09 13:36:24 +03:00
yuri
6750d32bd6 skip printing x status reason for pdo exception 2018-07-09 13:30:36 +03:00
yuri
70b8fc9ac9 foreign field: supporting number field 2018-07-09 12:52:55 +03:00
yuri
38a1be5ae3 version 2018-07-09 12:04:26 +03:00
yuri
1d9e9752c8 mass remove memory usage 2018-07-09 12:03:45 +03:00
yuri
eb37c1dc47 fix email text filter by email 2018-07-09 11:58:31 +03:00
yuri
1df6ec7f0c mass update improvement memory usage 2018-07-09 11:52:33 +03:00
yuri
1d6ee3e030 clean up 2018-07-09 11:37:39 +03:00
yuri
6eb80b747c login view name in clientDefs 2018-07-09 11:14:28 +03:00
yuri
539c0d22d7 convert lead skip disabled entity types 2018-07-09 10:54:29 +03:00
yuri
8e7d607ad4 fix dynamic logic not has 2018-07-09 10:49:44 +03:00
yuri
59e15eb71f fix select all results modal 2018-07-09 10:48:36 +03:00
Taras Machyshyn
c1ba6d5330 Metadata improvements 2018-07-06 17:02:19 +03:00
yuri
410aec734a lead select name fix 2018-07-05 17:21:58 +03:00
yuri
6078b17d38 export memory usage optimization 2018-07-05 16:47:20 +03:00
yuri
3b36d607ac code style 2018-07-05 16:20:36 +03:00
yuri
156cd85474 export memory usage improvement 2018-07-05 16:18:06 +03:00
yuri
0e29798e2b cleanup 2018-07-05 15:41:03 +03:00
yuri
19dbe81c79 hide followers in portal 2018-07-05 11:13:51 +03:00
yuri
62838961bb record max size limit improvement 2018-07-05 11:09:20 +03:00
yuri
ee84162470 createDisabled for modal select records 2018-07-03 12:22:12 +03:00
yuri
7a76dcce2c email to case copy body and attachments 2018-07-03 12:13:28 +03:00
yuri
0c21ed2e31 fix case 2018-07-03 11:50:53 +03:00
yuri
39daa763ab use numberic format param 2018-07-03 11:41:39 +03:00
yuri
f0c9690152 opp mandatory account 2018-07-03 11:32:10 +03:00
yuri
450091e71f contact mandatory account id 2018-07-03 11:31:28 +03:00
yuri
3a5c64b877 email skip replyTo when adding user 2018-07-02 15:16:33 +03:00
yuri
949d96db7a fix expanded list css 2018-07-02 14:58:16 +03:00
yuri
c974ce8864 entity manager check exists 2018-07-02 11:21:54 +03:00
yuri
caab8e9bbb custom calendar views 2018-07-02 11:05:02 +03:00
Taras Machyshyn
12469ee6f9 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-29 16:04:06 +03:00
Taras Machyshyn
dcd90b6f70 AdminNotifications improvements 2018-06-29 16:03:43 +03:00
Taras Machyshyn
f92b3c3d16 Bug fixes in Util 2018-06-29 16:02:45 +03:00
yuri
f3b41783c5 fix campaingn stats 2018-06-29 15:50:31 +03:00
yuri
3ec33c6054 fix currency converted hook 2018-06-29 15:36:34 +03:00
yuri
eb7c0da40c naming fix 2018-06-29 15:30:00 +03:00
yuri
2817c0027e fix campaign log record 2018-06-29 15:28:52 +03:00
yuri
c7457b95d1 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-29 14:29:55 +03:00
yuri
05244d598c email teams improvements 2018-06-29 13:08:49 +03:00
Taras Machyshyn
f55e0b2cb0 Minor bug fixes 2018-06-29 12:36:52 +03:00
Taras Machyshyn
ed6256da2c Possibility to check new versions of extensions 2018-06-29 12:10:25 +03:00
Taras Machyshyn
0398137ba7 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-28 16:36:03 +03:00
yuri
a6448a2769 kanban support link multiple fields 2018-06-28 13:07:31 +03:00
yuri
71cf0d01f8 fix template 2018-06-28 11:29:49 +03:00
yuri
d2a6d7ee99 fix event confirmation 2018-06-28 11:28:36 +03:00
yuri
a07bc15f00 fix typo 2018-06-28 11:26:22 +03:00
yuri
52ebd35785 supporting link multiple field on the list view 2018-06-28 11:20:13 +03:00
Taras Machyshyn
636d24a117 Check version url for extensions 2018-06-27 17:33:10 +03:00
yuri
8afbfaeb31 view jobs button 2018-06-27 11:19:49 +03:00
yuri
117084f835 fix sorting by index 2018-06-27 11:08:00 +03:00
yuri
eae92d8638 version 2018-06-27 10:52:41 +03:00
yuri
31f5df9db4 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-27 10:48:25 +03:00
Taras Machyshyn
00419a4cfc Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-26 18:02:05 +03:00
Taras Machyshyn
3f36e5b2e8 Bug fixes 2018-06-26 18:01:52 +03:00
Taras Machyshyn
cc2abc961e Fulltext index Fixes 2018-06-26 16:47:44 +03:00
yuri
bea0398776 grunt clean custom dir 2018-06-26 16:08:13 +03:00
yuri
fd4c55ba9d Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-26 15:05:35 +03:00
Taras Machyshyn
fef5edce28 Fulltext index changes 2018-06-26 15:05:21 +03:00
Taras Machyshyn
6423e4cb68 Changing text to medium types for fulltext index fields 2018-06-26 15:01:58 +03:00
yuri
2acaf1f7ff orm: 0 result if not existing attribnure is used in where 2018-06-26 12:35:24 +03:00
yuri
fe31b078f6 full-text search for email 2018-06-26 11:30:55 +03:00
yuri
e3d81f4a61 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-26 11:26:18 +03:00
yuri
fb1d1d8fc5 fix import 2018-06-26 11:15:16 +03:00
yuri
cabef5906c fix list view 2018-06-25 14:29:39 +03:00
Taras Machyshyn
2dfffe00d0 Database helper improvements 2018-06-25 13:03:07 +03:00
yuri
78390efe45 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-25 11:29:10 +03:00
yuri
8ae0d2da88 email: add teams from replied 2018-06-25 11:24:58 +03:00
Taras Machyshyn
ecb3273883 Possibility to check if table supports fulltext index 2018-06-25 11:08:51 +03:00
yuri
256b94f877 fix panels row actions links 2018-06-25 10:54:21 +03:00
yuri
422f02b5c9 text filter supporting int and autoincrement fields 2018-06-22 17:00:48 +03:00
yuri
7e4c31db1a relationship proper orm attribute type 2018-06-22 16:03:31 +03:00
yuri
c9918c07b8 htmlizer var helper 2018-06-22 15:59:51 +03:00
Taras Machyshyn
ed438b1a31 Code improvements 2018-06-22 15:46:02 +03:00
yuri
83843cfe46 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-22 14:47:30 +03:00
yuri
462de7b025 fix fulltext 2018-06-22 12:51:14 +03:00
yuri
01dbc183f8 fix email text search 2018-06-22 12:30:07 +03:00
yuri
8817c82996 fix select attributes 2018-06-22 12:02:03 +03:00
yuri
3493addec5 print pdf mass max 50 2018-06-22 11:20:42 +03:00
yuri
d41a588bb5 disable full text for email 2018-06-22 11:15:16 +03:00
Taras Machyshyn
7953705e30 MEDIUMTEXT is a default text type 2018-06-21 19:00:36 +03:00
Taras Machyshyn
763a1ad96f Possibility to ignore creating fulltext indexes 2018-06-21 18:59:34 +03:00
yuri
b58d958f51 mb string functions 2018-06-21 16:10:16 +03:00
yuri
0b8d43f734 fixes 2018-06-21 14:21:38 +03:00
yuri
87518f33a9 full-text search 3 2018-06-21 12:53:12 +03:00
yuri
d83530bbf6 fix massDelete 2018-06-20 16:53:15 +03:00
yuri
517e1bab7c serach by email address performance improvement 2018-06-20 16:01:37 +03:00
yuri
cd22552e4a entity manager full-text search parameter 2018-06-20 15:24:39 +03:00
yuri
abc394512c Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2018-06-20 15:07:06 +03:00
yuri
fa0cb01660 full text search 2 2018-06-20 14:51:50 +03:00
Taras Machyshyn
e6d509bd0b Added support fulltext index 2018-06-20 14:37:23 +03:00
yuri
775641aee1 fix globals search name field 2018-06-20 12:20:34 +03:00
yuri
3c51c6bc77 remove test 2018-06-19 18:46:59 +03:00
yuri
9ddd7b1d32 Merge branch 'hotfix/5.2.6' 2018-06-19 18:44:54 +03:00
yuri
6020a01a62 fix orm empty in 2018-06-19 18:44:47 +03:00
yuri
1ad9ee10f6 full text search 2018-06-19 17:29:12 +03:00
yuri
59863b8a91 test 2018-06-19 15:51:22 +03:00
yuri
1a083c247c fix css 2018-06-19 15:48:14 +03:00
yuri
cd7ca31212 orm query: supporting match 2018-06-19 15:35:30 +03:00
yuri
9be9ff3d68 mass print to pdf 2018-06-19 12:16:07 +03:00
yuri
e3b1ead830 fix acl manager empty action 2018-06-19 12:14:11 +03:00
yuri
b6041592ea fix job select attributes 2018-06-19 11:58:15 +03:00
yuri
0fa8b3da0b mail merge only with address 2018-06-18 16:13:36 +03:00
yuri
7dd0fe07ac fixes 2018-06-18 15:23:04 +03:00
yuri
2d1770f439 ignore select attributes 2018-06-18 15:11:50 +03:00
yuri
6be192514a force select all attributes 2018-06-18 15:06:46 +03:00
yuri
63fc42f8cf email mandatory select attributes 2018-06-18 14:47:32 +03:00
yuri
6f8a593f09 list select changes 2018-06-18 14:45:20 +03:00
yuri
b00e8f8900 merge 2018-06-18 14:26:00 +03:00
yuri
4e226ebcb7 select records button disable 2018-06-18 14:23:04 +03:00
yuri
30909c497b record dashlet populate assigned user 2018-06-18 14:14:48 +03:00
yuri
efd5ccfa96 list view select only attributes from layout 2018-06-18 13:23:35 +03:00
yuri
88d159d4c6 field manager load language after save 2018-06-15 15:28:45 +03:00
yuri
29788c353b link manager ui prevent existing link names 2018-06-15 15:18:43 +03:00
yuri
7897272f65 mail merge 2018-06-15 15:03:22 +03:00
yuri
5cabc76782 Merge branch 'hotfix/5.2.6' 2018-06-15 15:02:54 +03:00
yuri
b0ef416a4f target list listed status 2018-06-14 17:23:51 +03:00
yuri
0de0768bfb remove et category from layout manager 2018-06-14 17:16:08 +03:00
yuri
e4ac128a2e cleanup 2018-06-14 15:39:51 +03:00
yuri
2307f21d04 version 2018-06-14 15:15:15 +03:00
yuri
89d706c94f layout manager default panel 2018-06-14 13:00:18 +03:00
yuri
23350a0ffe field manager tpl fix 2018-06-14 12:04:02 +03:00
yuri
65d047f831 skip default opt out for users 2018-06-14 11:58:36 +03:00
yuri
dc40045de6 fix css 2018-06-14 11:57:01 +03:00
yuri
50d91ea6d8 email folder count 200 2018-06-14 11:50:33 +03:00
yuri
3a8865e382 calendar task creating fix 2018-06-14 11:47:47 +03:00
yuri
fddcef284f fix calendar task 2018-06-14 10:48:57 +03:00
yuri
c0854250e4 cleanup 2018-06-12 14:47:27 +03:00
yuri
71c9501354 cleanup 2018-06-12 14:38:25 +03:00
yuri
a85d0f91a2 layout manager panels dynamic logic 2018-06-12 14:34:57 +03:00
yuri
977514f5ef fix panels css 2018-06-12 13:22:17 +03:00
yuri
bf9ad953a1 lead converted panel use dynamic logic 2018-06-12 12:52:32 +03:00
yuri
5b1d96f649 ability to remove notifications for regular users 2018-06-12 11:24:53 +03:00
yuri
bf4ac0c9f3 opp detect closed stages by probability 2018-06-11 15:16:28 +03:00
yuri
3c8b2534eb opp last stage field 2018-06-11 15:06:19 +03:00
yuri
16c9f46583 countRelated subRelated functions 2018-06-11 12:01:52 +03:00
yuri
ef9609b710 target list listed status 2018-06-08 16:00:31 +03:00
yuri
a4c15992a9 fix calendar task 2018-06-08 14:20:35 +03:00
yuri
14d1173a0c calendar prevent drag between allday and hours 2018-06-08 14:01:06 +03:00
yuri
1e7acbdbd2 fix calendar 2018-06-08 12:47:11 +03:00
yuri
35e729b25c Merge branch 'hotfix/5.2.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.2.5 2018-06-08 12:12:18 +03:00
Taras Machyshyn
4ee5ea78e3 LDAP bug fixes 2018-06-08 12:11:32 +03:00
yuri
fe971f9f67 outboundEmailBccAddress on ui 2018-06-08 11:30:04 +03:00
yuri
a828523f26 external email client for email address field link 2018-06-08 11:25:32 +03:00
yuri
af9ca6788e re-render header on name change after sync 2018-06-07 16:28:48 +03:00
yuri
36d1c3af63 version 2018-06-07 13:58:52 +03:00
yuri
1853e98209 wysiwyg text filters support 2018-06-07 12:43:38 +03:00
yuri
f526d43798 improve dynamic logic conditions ui 2018-06-07 12:30:38 +03:00
yuri
ae8c76cecb fix select manager 2018-06-07 10:48:48 +03:00
yuri
2b32c94543 Merge branch 'hotfix/5.2.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.2.5 2018-06-07 10:45:38 +03:00
Taras Machyshyn
b6f5909df1 MySQL 8 bug fixes 2018-06-06 16:44:06 +03:00
yuri
ef35bbbb63 import: setting null for wrong date time 2018-06-05 13:11:34 +03:00
yuri
64f2cc6c7e fix import varchar length exceeded 2018-06-05 13:03:00 +03:00
yuri
a16635eb26 fix import ui 2018-06-05 13:02:31 +03:00
yuri
6e614f0a7d history has attachment 2018-06-04 16:40:07 +03:00
yuri
859f4eab0a target list panels change order 2018-06-04 14:33:11 +03:00
yuri
104e0b9079 target list changes 2 2018-06-04 14:25:00 +03:00
yuri
612abbf5c0 fix range fields 2018-06-04 12:46:54 +03:00
yuri
2dfbf71806 target list changes 2018-06-04 12:39:57 +03:00
yuri
c43f4d129d fix calendar 2018-06-04 10:47:14 +03:00
yuri
8d47a48f62 lang fix 2018-06-04 10:44:55 +03:00
yuri
9a333e6e38 list layout widthPx preserving 2018-06-01 12:07:45 +03:00
yuri
f6e0ef8cc6 layout manager data attribute list for custom layouts 2018-06-01 11:37:53 +03:00
yuri
36a45717f1 fix excel export varchar 2018-05-31 11:47:34 +03:00
yuri
86f63d72e1 fix list expanded 2018-05-31 11:41:38 +03:00
yuri
ade4078f0d fix kanban sort 2 2018-05-30 16:33:14 +03:00
yuri
fdeac68216 fix kanban sorting 2018-05-30 16:14:15 +03:00
yuri
4c220a5a65 version 2018-05-30 15:50:10 +03:00
yuri
f148bd82c5 fix kanban 2018-05-30 15:48:36 +03:00
yuri
0d983ef34c fix create new field name 2018-05-30 15:40:41 +03:00
yuri
75ad9f5cf2 email body fit height fix 2018-05-30 15:37:33 +03:00
yuri
893efe212e email template category layouts 2018-05-30 12:06:39 +03:00
yuri
4a4d2e473f version 2018-05-30 11:48:36 +03:00
yuri
9d663ad140 email template categories 2018-05-30 11:41:48 +03:00
yuri
f377dfc5b1 wysiwyg use iframe 2018-05-30 10:56:18 +03:00
yuri
a4054f5273 keep initial attachments on forward email 2018-05-30 10:47:20 +03:00
yuri
fcc3ac978f mass email track opened img alt 2018-05-30 10:40:59 +03:00
yuri
2bbeaa8198 es_MX lang fixes 2018-05-30 10:34:41 +03:00
yuri
70308d19ae hu and sk languages 2018-05-30 10:26:58 +03:00
yuri
143897cf18 fix set fetched link multiple 2018-05-30 10:18:44 +03:00
yuri
ddc90a05d9 fix email template insertion 2018-05-25 10:15:14 +03:00
yuri
bbbadb1c32 version 2018-05-24 14:35:10 +03:00
yuri
8995835d8e email attach in toolbox 2018-05-24 14:16:51 +03:00
yuri
df3c119f5f wysywyg stick toolbar 2018-05-24 13:57:48 +03:00
yuri
6a2ba73412 fix orm 2018-05-23 15:22:00 +03:00
yuri
cbee8cc541 version 2018-05-23 15:12:51 +03:00
yuri
22f186af29 fix label manager 2018-05-22 14:41:16 +03:00
yuri
77ab385db8 fix client manager exit 2018-05-22 10:25:48 +03:00
yuri
13495ee32e fix rdb 2018-05-21 11:16:39 +03:00
yuri
254ce7d5d2 email-to-case contact 2018-05-21 10:34:24 +03:00
yuri
4e1053d037 lang fix 2018-05-21 10:31:13 +03:00
yuri
2fcc6e168f orm setAsFetched fix 2018-05-21 10:30:22 +03:00
yuri
96ea0ae690 pl_PL lang fix 2018-05-18 14:40:41 +03:00
yuri
aa66001981 fix personal email account check 2018-05-18 12:00:37 +03:00
yuri
173ff943a4 next number fix 2018-05-18 11:48:51 +03:00
yuri
79ac73bb95 cleanup 2018-05-18 11:43:53 +03:00
yuri
2143832c25 cleanup 2018-05-18 11:42:51 +03:00
yuri
c656e36265 export timeout 2018-05-18 11:41:20 +03:00
yuri
8564adca6a fix portal assinment permission 2018-05-18 11:37:05 +03:00
yuri
19c9922115 fix navbar 2018-05-18 11:19:33 +03:00
yuri
b22a6f204a fix export 2018-05-15 13:33:51 +03:00
yuri
daa0cf9fc9 fix calendar 2018-05-15 12:52:28 +03:00
yuri
6365aa04df fix 2018-05-15 12:23:26 +03:00
yuri
ff20e077b1 fix 2018-05-15 12:19:49 +03:00
yuri
1106aaf18e fix 2018-05-15 12:15:13 +03:00
yuri
872ba225b4 fix 2018-05-15 12:13:15 +03:00
yuri
95c15efa92 fix typo 2018-05-15 11:57:34 +03:00
yuri
c179cec8a0 fix attachment multiple 2018-05-15 11:50:59 +03:00
yuri
97f96396c5 fix 2018-05-15 11:28:08 +03:00
yuri
e05cac1261 fix kanban 2018-05-15 11:21:50 +03:00
yuri
f9d8894f37 fix email field 2018-05-15 11:09:40 +03:00
yuri
5bd5a76722 fix person name 2018-05-15 11:06:09 +03:00
yuri
2fbb4f3725 fix email address 2018-05-15 11:02:29 +03:00
yuri
61e1b18eb9 opted out bool field 2018-05-14 16:50:12 +03:00
yuri
08c1710326 fix erase persoanal data 2018-05-14 15:59:14 +03:00
yuri
39d3baf1b5 fix stream 2018-05-14 15:57:35 +03:00
yuri
fd4753d190 email template info panel fix 2018-05-14 15:48:32 +03:00
yuri
29745367f7 tr_TR lang fix 2018-05-14 12:26:06 +03:00
yuri
25bb4f08ba fix record list dashlet 2018-05-14 11:57:41 +03:00
yuri
40a13c16eb data privacy 2018-05-14 11:49:25 +03:00
yuri
8ad2c425bf fix table get 2018-05-11 17:15:29 +03:00
yuri
2b6c9bc57e fix opp report 2018-05-10 16:35:47 +03:00
yuri
bebbee5abd email: replyTo link 2018-05-10 12:41:42 +03:00
yuri
99156499f1 display opted out on list view 2018-05-08 12:21:30 +03:00
yuri
9ad81581bb email opt out ui change 2018-05-08 11:48:33 +03:00
yuri
1b4cd1478b bc fix 2018-05-08 11:14:16 +03:00
yuri
5fdf2e4403 css fix 2018-05-07 16:33:10 +03:00
yuri
319b523117 css fix 2018-05-07 16:28:01 +03:00
yuri
62823646b0 css fix 2018-05-07 16:12:20 +03:00
yuri
3c16758605 xls float fix 2018-05-07 13:35:56 +03:00
yuri
4705d2e38d link multiple json fields 2018-05-07 13:13:07 +03:00
yuri
05b405f76e attachment multiple order 2018-05-07 12:38:46 +03:00
yuri
f54b2788de fix list tree 2018-05-07 12:05:30 +03:00
yuri
6ac0c7b301 fix task/meeting accountName contactName 2018-05-07 11:57:05 +03:00
yuri
c44cc9906e fix lang 2018-05-04 16:19:12 +03:00
yuri
ecd5671e1a fix panels 2018-05-04 16:00:18 +03:00
yuri
c83f729eea color picker fix 2018-05-04 14:11:09 +03:00
yuri
10afd2dfef update tcpdf 2018-05-04 14:03:38 +03:00
yuri
8229b8320f entity maanger reset to defaults 2018-05-04 13:58:40 +03:00
yuri
50493cf725 user icon 2018-05-04 13:24:04 +03:00
yuri
3937b80254 icons update 2018-05-04 13:18:16 +03:00
yuri
bcb7aaf13c entity manager: ability to select icons 2018-05-04 13:18:04 +03:00
yuri
f9d7ec7f47 fix test 2018-05-04 11:14:19 +03:00
yuri
b7d43edf22 attachment tests 2018-05-04 11:13:05 +03:00
yuri
0f0060f0f6 fix attachment duplicate 2018-05-04 11:00:59 +03:00
yuri
036bad3912 entity manager iconClass backend 2018-05-03 17:09:57 +03:00
yuri
c9d675798b icon color change 2018-05-03 15:08:23 +03:00
yuri
10703750f9 stream icon 2018-05-03 14:10:29 +03:00
yuri
4d942851d3 css fix 2018-05-03 14:01:50 +03:00
yuri
1434c31b2f theme names 2018-05-03 12:01:32 +03:00
yuri
74405b2842 theme order 2018-05-03 12:01:23 +03:00
yuri
1070d18085 icons 2018-05-03 11:45:28 +03:00
yuri
b729c13c7a fix css 2018-05-03 11:37:00 +03:00
yuri
1c99327bfd team tab 2018-05-03 11:18:07 +03:00
yuri
61279b0e59 sidebar width fix 2018-05-03 11:10:07 +03:00
yuri
31cb17a41d sidebar expanded by default 2018-05-03 11:00:45 +03:00
yuri
f9349d5545 icon colors 2018-05-03 10:59:18 +03:00
yuri
c3703494eb icons 2018-05-02 16:42:37 +03:00
yuri
d94ff7c4fa concurrency fix 2 2018-05-02 12:58:25 +03:00
yuri
01e0bf4d6d add GNF currency 2018-05-02 12:02:46 +03:00
yuri
ca5f1a47fd cleanup 2018-05-02 11:54:07 +03:00
yuri
54af8ee7c6 fix concurrency 2018-05-02 11:41:46 +03:00
yuri
8572b565f3 update bull 2018-05-02 11:41:02 +03:00
yuri
ea1cc1c65a fix calendar 2018-04-27 16:19:47 +03:00
yuri
4610c61bd4 fix dashlets 2018-04-27 16:16:37 +03:00
yuri
02f04ea9c4 concurrency race fix 2018-04-27 16:06:23 +03:00
yuri
1ce4db6be4 model abort last fetch 2018-04-27 15:47:21 +03:00
yuri
888291a99d fix 2018-04-27 14:07:10 +03:00
yuri
3f9773a4cf fix htmlizer 2018-04-27 11:59:26 +03:00
yuri
a223c721aa fix nav tab overflow 2018-04-27 11:54:05 +03:00
yuri
5bce57b1f8 fix theme 2018-04-27 11:43:03 +03:00
yuri
3666c34845 tab colors enabled 2018-04-27 11:12:42 +03:00
yuri
0dceccac46 attachment create file before create record 2018-04-27 11:11:23 +03:00
yuri
59a2644e0f email order replies 2018-04-27 10:52:49 +03:00
yuri
6c33a7304d email template skip empty subject 2018-04-27 10:47:31 +03:00
yuri
e89f01a525 person name autocomplete off 2018-04-27 10:42:32 +03:00
yuri
7181b45461 fix campaign stats panel 2018-04-27 10:41:00 +03:00
yuri
b7ce52f476 fix 2018-04-26 18:05:04 +03:00
yuri
d2a8cd961f theme fixes 2018-04-26 18:03:56 +03:00
yuri
c152dbf4bf fix row actions 2018-04-26 11:50:27 +03:00
yuri
594a111bc3 fix import panels 2018-04-26 11:46:09 +03:00
yuri
005be2fe5a impoty fix attribute translation 2018-04-26 11:26:51 +03:00
yuri
8f194e6d9e import currency fix 2018-04-26 10:53:10 +03:00
yuri
e3a5cb464c fix datetime optional 2018-04-25 16:52:12 +03:00
yuri
779f0df83e link parent fix 2018-04-25 16:46:23 +03:00
yuri
086d9bdbb6 kanban fix 2018-04-25 15:52:28 +03:00
yuri
9dcaa46bc7 css fix 2018-04-25 15:00:34 +03:00
yuri
3bca95a4da entity manager color fix 2018-04-25 14:24:26 +03:00
yuri
be09935287 icon fix 2018-04-25 14:10:08 +03:00
yuri
410c338da8 css fix 2018-04-25 13:55:55 +03:00
yuri
be0606ede6 fix css 2018-04-25 13:47:27 +03:00
yuri
f54a84420a color icon change 2018-04-25 13:46:59 +03:00
yuri
6a0d1e3b8b kanban no data 2018-04-25 11:57:39 +03:00
yuri
33127cd1bb version 2018-04-25 11:09:33 +03:00
yuri
5cd03312e1 assigned users notifications 2018-04-24 17:02:14 +03:00
yuri
391c0dcaf8 fix list expanded layout 2018-04-24 16:26:53 +03:00
yuri
0b7b9599d3 multiple assigned users support 2018-04-24 16:18:50 +03:00
yuri
b45ff69376 email account assigned user view 2018-04-24 14:00:17 +03:00
yuri
1836d0a127 css change 2018-04-24 12:13:07 +03:00
yuri
048e156e59 fix kanban 2018-04-23 14:38:18 +03:00
yuri
a85be60e30 fix kanban 2018-04-23 14:29:32 +03:00
yuri
90d5c9eca6 fix kanban 2018-04-23 14:01:17 +03:00
yuri
1c8f0c7d9c css fix 2018-04-23 13:40:29 +03:00
yuri
f8d1c9ce05 opp kanban fix 2018-04-23 13:32:12 +03:00
yuri
28052bac23 layout manager kanban view 2018-04-23 12:14:16 +03:00
yuri
c361a940eb kanban view in entity manager 2018-04-23 11:56:46 +03:00
yuri
fbc1e936db Merge branch 'master' of github.com:espocrm/espocrm 2018-04-23 10:46:20 +03:00
yuri
84661f88fd Merge branch 'hotfix/5.1.3' 2018-04-23 10:45:49 +03:00
yuri
10b4c88872 template variable triple braces 2018-04-23 10:37:04 +03:00
yuri
10ddc7d941 fix send email attachment filename 2018-04-23 10:23:40 +03:00
Yuri Kuznetsov
b1c63e2cb9 Update CONTRIBUTING.md 2018-04-22 20:36:54 +03:00
yuri
3f6544d03b kanban fix 2018-04-20 16:05:40 +03:00
yuri
a9f211dfd5 notify loading 2018-04-20 16:02:25 +03:00
yuri
f2427abbf4 kanban 2018-04-20 15:27:58 +03:00
yuri
a6187f9838 empty date on list view 2018-04-20 15:25:33 +03:00
yuri
384600ed95 Merge branch 'hotfix/5.1.3' 2018-04-20 15:23:02 +03:00
yuri
6d06f03ef9 fix frontend acl 2018-04-20 13:56:09 +03:00
yuri
0829c714fd Merge branch 'hotfix/5.1.3' 2018-04-20 10:57:07 +03:00
yuri
9e2251755f quick view edit icons 2018-04-20 10:55:03 +03:00
yuri
436f871be2 update marked js 2018-04-20 10:45:14 +03:00
yuri
8d09f8e9b2 Merge branch 'hotfix/5.1.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.1.3 2018-04-19 13:46:27 +03:00
yuri
e4d43a6790 fix field names conflict with template variables 2018-04-19 13:11:20 +03:00
Taras Machyshyn
bb3d9d3466 ExternalAccount bug fixes 2018-04-19 11:16:08 +03:00
yuri
bd208e259c reset sorting in select modal 2018-04-18 14:42:39 +03:00
yuri
1a1cfec3e5 refactoring 2018-04-16 12:11:13 +03:00
yuri
1e95e98549 calendar dashlet fix 2018-04-16 11:36:00 +03:00
yuri
82056b5650 version 2018-04-16 10:35:44 +03:00
yuri
c0d118ee98 email plain fix 2018-04-16 10:31:31 +03:00
yuri
da13177292 shared calendar dashlet 2018-04-13 16:01:54 +03:00
yuri
4cc38ca564 meeting avatar fix 2018-04-13 15:17:00 +03:00
yuri
fa945c981e nav menu changes 2018-04-13 15:15:41 +03:00
yuri
63d76c6b71 Merge branch 'hotfix/5.1.3' 2018-04-13 14:42:22 +03:00
yuri
9b749a8f67 fix read only 2018-04-13 13:32:12 +03:00
yuri
1ce1d5c79a scope colors changes 2018-04-13 12:14:11 +03:00
yuri
0c2dd73334 password disable autocomplete 2018-04-12 16:15:02 +03:00
yuri
7331f14e0a quick view remove color 2018-04-12 15:38:58 +03:00
yuri
d44f2fd7cd calendar avatars change 2018-04-12 15:20:55 +03:00
yuri
4da7eddc69 event users with avatars 2018-04-12 15:12:16 +03:00
yuri
3ebe30a9c0 entity colors and icons 2018-04-12 14:48:45 +03:00
yuri
e959361df9 createField changes 2018-04-12 14:34:11 +03:00
yuri
7773506361 shared calendar 2018-04-12 13:43:27 +03:00
yuri
9db652a501 fix repository merge select params 2018-04-11 16:03:53 +03:00
yuri
27ebfd7f23 fix link multiple required 2018-04-11 12:09:14 +03:00
yuri
96f80e0008 create field options 2018-04-11 12:03:46 +03:00
yuri
5f3c208161 Merge branch 'hotfix/5.1.3' 2018-04-11 10:56:35 +03:00
yuri
d402f33c74 fix field level acl for json fields 2018-04-10 15:29:46 +03:00
yuri
879da4dca2 fix calls row actions 2018-04-10 14:02:36 +03:00
yuri
3e862790e6 Merge branch 'hotfix/5.1.3' 2018-04-10 12:08:29 +03:00
yuri
425e36bff6 view helper getAvatarHtml 2018-04-10 12:07:58 +03:00
yuri
705ecbdf72 es_MX lang fix 2018-04-10 11:49:28 +03:00
yuri
c658d67ac8 lt_LT lang fix 2018-04-10 11:48:27 +03:00
yuri
c46055b469 hr_HR language 2018-04-10 11:46:03 +03:00
yuri
66e4b89d1a email body plain fix 2018-04-10 11:35:45 +03:00
yuri
38b9f1d68c fix email body plain not filled 2018-04-10 11:17:27 +03:00
yuri
19b8a4e771 fix upload timeout 2018-04-10 10:42:07 +03:00
yuri
937ffea4ab fix upload timeout 2018-04-10 10:41:44 +03:00
yuri
1ae49a3d47 timeline users avatars 2018-04-06 16:48:02 +03:00
yuri
c76daf50db currency rate ui improvement 2018-04-06 14:54:38 +03:00
yuri
9e5c797243 opp currency convert 2018-04-06 14:09:15 +03:00
yuri
662c4afcdf fix mass update 2018-04-06 13:00:08 +03:00
yuri
9b50b97e79 field labelText 2018-04-06 12:09:06 +03:00
yuri
d00cefc37f list mass action all 2018-04-06 11:17:32 +03:00
yuri
1f94c13e25 fix confirm leavout 2018-04-05 17:12:04 +03:00
yuri
75801ea717 calendar fixes 2018-04-05 15:04:48 +03:00
yuri
b845f5086e remove ics tests 2018-04-05 11:57:34 +03:00
yuri
8c8e6ec551 fix ics 2018-04-05 11:50:14 +03:00
yuri
a4aecee18d fix stream parent fields load 2018-04-05 11:49:14 +03:00
Sebastian
3c0d445824 Fix Multiline descriptions for Outlook. (#862)
See https://social.technet.microsoft.com/Forums/lync/en-US/c16cc4aa-0a07-4742-929d-1b01d698066e/ics-file-description-with-colon-and-newline-is-displayed-without-newlines?forum=outlook
2018-04-05 11:48:31 +03:00
yuri
c36c6dc42d wysywyg remove max length 2018-04-04 13:36:40 +03:00
yuri
f7f8f14725 case new note color 2018-04-03 17:44:06 +03:00
yuri
9402bf4baf chart colors fix 2018-04-03 14:33:03 +03:00
yuri
12fcdcdb2d call icon change 2018-04-03 14:24:38 +03:00
yuri
61735ed4aa cleanup 2018-04-03 14:11:49 +03:00
yuri
3c65d252b6 fix image orientation 2018-04-02 15:57:14 +03:00
yuri
6423859195 email move to trash fix 2018-04-02 13:56:31 +03:00
yuri
53df34d6f2 acl read only comment 2018-04-02 13:21:51 +03:00
yuri
ed762c9be3 stream notifications types 2018-04-02 12:44:55 +03:00
yuri
f05f14b12a email fixes 2018-04-02 12:44:44 +03:00
yuri
183b5cb29b clientReadOnly 2018-04-02 12:44:07 +03:00
yuri
07193b1fb2 email send shared name 2018-04-02 12:29:05 +03:00
yuri
b24f8f538b fix auth 2018-03-30 16:21:45 +03:00
yuri
a259174415 outboundEmailBccAddress 2018-03-30 14:43:12 +03:00
yuri
3d93c2a4b1 Merge branch 'hotfix/5.1.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.1.2 2018-03-30 14:18:44 +03:00
yuri
8c112791ef external email client support 2018-03-30 14:16:28 +03:00
Taras Machyshyn
0ff4553c94 LDAP fixes 2018-03-30 13:52:22 +03:00
yuri
1e6094b5ee fix wysiwyg 2018-03-29 17:28:14 +03:00
yuri
a646d97aec opp list layout changes 2018-03-29 16:03:18 +03:00
yuri
57f4980802 fix bc 2018-03-29 13:39:12 +03:00
yuri
2477f5b696 fix import float values 2018-03-29 12:33:36 +03:00
yuri
6463422e81 version 2018-03-29 11:48:49 +03:00
yuri
3daf041698 auth improvements 2018-03-29 11:45:57 +03:00
yuri
933456ecdf repository check 2018-03-28 15:55:12 +03:00
yuri
ab18f72f69 schedule meeting for portal user fix 2018-03-28 15:22:55 +03:00
yuri
28465cf2bf acl read only 2018-03-27 17:22:28 +03:00
yuri
4cef5c547e string length and contains functions 2018-03-27 16:16:07 +03:00
yuri
057abf8024 fix email getBodyPlain 2018-03-27 15:00:36 +03:00
yuri
20de5658e9 role fields fixes 2018-03-27 14:43:29 +03:00
yuri
4a2ed2e0f7 kb language options disabled 2018-03-27 14:25:44 +03:00
yuri
3dc574bc48 fix field manager options 2018-03-27 14:23:52 +03:00
yuri
d43a42b646 fix export 2018-03-27 13:18:04 +03:00
yuri
823c46a6df fix email body plain 2018-03-27 12:00:58 +03:00
yuri
919c0bc8ae fix field manager enum 2018-03-27 11:51:22 +03:00
yuri
54d62a19cd tooltip added 2018-03-22 15:28:09 +02:00
yuri
18304fb710 attachment throw error if fie not stored 2018-03-22 13:21:57 +02:00
yuri
cc1afbed5d file field tpl fix 2018-03-22 12:52:51 +02:00
yuri
a195acfee9 fix field name conflict with view helpers 2018-03-22 12:47:24 +02:00
yuri
e22ec4c20b cleanup 2018-03-21 16:00:00 +02:00
yuri
7c8f4f9db8 display ... for not set values in detail views 2018-03-21 12:29:37 +02:00
yuri
d28ff1e438 Merge branch 'hotfix/5.1.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.1.2 2018-03-20 16:38:11 +02:00
Taras Machyshyn
5af7499fdb Use utf8mb4 for fresh instances with MySQL 5.7+ 2018-03-20 16:02:40 +02:00
yuri
6588d783cc activities dashlet: next x days 2018-03-20 15:52:09 +02:00
yuri
f1b8279d50 insert template fix 2018-03-20 14:51:31 +02:00
yuri
252d31ffac email template for reply 2018-03-20 14:00:05 +02:00
yuri
8a98cca4fa email insert email confirm 2018-03-20 13:12:30 +02:00
Taras Machyshyn
0b8486c1a7 Merge branch 'hotfix/5.1.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.1.2 2018-03-20 12:29:54 +02:00
Taras Machyshyn
b0bd0664f9 Extension installation improvements 2018-03-20 12:29:36 +02:00
yuri
286fd7e0ce template placeholders translation improvement 2018-03-20 12:22:47 +02:00
yuri
42f5d28369 template today now 2018-03-20 12:00:46 +02:00
yuri
ea5d873cc3 fix field manager buttons 2018-03-20 11:09:25 +02:00
yuri
ee8608469e fix css 2018-03-19 14:08:42 +02:00
yuri
f541625318 cache timestamp in dev html 2018-03-19 13:52:58 +02:00
yuri
ee70fb8483 new version notification skip for dev 2018-03-19 13:10:38 +02:00
yuri
f79e093f52 fix base auth split 2018-03-19 12:11:01 +02:00
yuri
254d9280b9 fix login 2018-03-16 15:47:03 +02:00
yuri
ddd9c765b8 Merge branch 'master' of github.com:espocrm/espocrm into hotfix/5.1.2 2018-03-16 15:43:52 +02:00
Ettienne Pitts
080f204eed uninstallConfirmation added to en_US (#834) 2018-03-16 15:43:33 +02:00
tanyalei
f30325849a Activities Dashlet: fix Display Records option (#835) 2018-03-16 14:20:57 +02:00
yuri
8caa5b3e60 image preview original target blank 2018-03-15 16:26:25 +02:00
993 changed files with 31847 additions and 4637 deletions

View File

@@ -6,4 +6,4 @@ Before we can merge your pull request you need to accept our CLA [here](https://
## Issues
We don't provide developer help or any kind of support on github. Please use our [forum](http://forum.espocrm.com/) for this.
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

@@ -118,6 +118,9 @@ module.exports = function (grunt) {
clean: {
start: ['build/*'],
final: ['build/tmp'],
beforeFinal: {
src: ['build/tmp/custom/Espo/Custom/*', '!build/tmp/custom/Espo/Custom/.htaccess']
}
},
less: lessData,
uglify: {
@@ -290,9 +293,10 @@ module.exports = function (grunt) {
'copy:frontendLib',
'copy:backend',
'replace',
'clean:beforeFinal',
'copy:final',
'chmod',
'clean:final',
'clean:final'
]);
};

View File

@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Acl;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class EmailAddress extends \Espo\Core\Acl\Base
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity)
{
$id = $entity->id;
$isFobidden = false;
$repository = $this->getEntityManager()->getRepository('EmailAddress');
if (!$user->isAdmin()) {
$entityWithSameAddressList = $repository->getEntityListByAddressId($id, $excludeEntity);
foreach ($entityWithSameAddressList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
$isFobidden = true;
if (
$e->get('isPortalUser') && $excludeEntity->getEntityType() === 'Contact' &&
$e->get('contactId') === $excludeEntity->id
) {
$isFobidden = false;
}
if ($isFobidden) break;
}
}
}
return !$isFobidden;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Acl;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class PhoneNumber extends \Espo\Core\Acl\Base
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity)
{
$id = $entity->id;
$isFobidden = false;
$repository = $this->getEntityManager()->getRepository('PhoneNumber');
if (!$user->isAdmin()) {
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($id, $excludeEntity);
foreach ($entityWithSameNumberList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
$isFobidden = true;
if (
$e->get('isPortalUser') && $excludeEntity->getEntityType() === 'Contact' &&
$e->get('contactId') === $excludeEntity->id
) {
$isFobidden = false;
}
if ($isFobidden) break;
}
}
}
return !$isFobidden;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class EmailAddress extends \Espo\Core\AclPortal\Base
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity)
{
$id = $entity->id;
$isFobidden = false;
$repository = $this->getEntityManager()->getRepository('EmailAddress');
if (!$user->isAdmin()) {
$entityWithSameAddressList = $repository->getEntityListByAddressId($id, $excludeEntity);
foreach ($entityWithSameAddressList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
$isFobidden = true;
break;
}
}
}
return !$isFobidden;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class PhoneNumber extends \Espo\Core\AclPortal\Base
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity)
{
$id = $entity->id;
$isFobidden = false;
$repository = $this->getEntityManager()->getRepository('PhoneNumber');
if (!$user->isAdmin()) {
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($id, $excludeEntity);
foreach ($entityWithSameNumberList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
$isFobidden = true;
break;
}
}
}
return !$isFobidden;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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 AuthLogRecord extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
}
public function actionUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionMassUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionCreate($params, $data, $request)
{
throw new Forbidden();
}
public function actionCreateLink($params, $data, $request)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data, $request)
{
throw new Forbidden();
}
}

View File

@@ -0,0 +1,55 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\BadRequest;
class DataPrivacy extends \Espo\Core\Controllers\Base
{
protected function checkControllerAccess()
{
if ($this->getAcl()->get('dataPrivacyPermission') === 'no') {
throw new Forbidden();
}
}
public function postActionErase($params, $data)
{
if (empty($data->entityType) || empty($data->id) || empty($data->fieldList) || !is_array($data->fieldList)) {
throw new BadRequest();
}
return $this->getServiceFactory()->create('DataPrivacy')->erase($data->entityType, $data->id, $data->fieldList);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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;
class EmailTemplateCategory extends \Espo\Core\Templates\Controllers\CategoryTree
{
}

View File

@@ -83,6 +83,20 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (isset($data['textFilterFields']) && is_array($data['textFilterFields'])) {
$params['textFilterFields'] = $data['textFilterFields'];
}
if (!empty($data['color'])) {
$params['color'] = $data['color'];
}
if (!empty($data['iconClass'])) {
$params['iconClass'] = $data['iconClass'];
}
if (isset($data['fullTextSearch'])) {
$params['fullTestSearch'] = $data['fullTextSearch'];
}
$params['kanbanViewMode'] = !empty($data['kanbanViewMode']);
if (!empty($data['kanbanStatusIgnoreList'])) {
$params['kanbanStatusIgnoreList'] = $data['kanbanStatusIgnoreList'];
}
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
@@ -324,4 +338,16 @@ class EntityManager extends \Espo\Core\Controllers\Base
return true;
}
public function postActionResetToDefault($params, $data, $request)
{
if (empty($data->scope)) {
throw new BadRequest();
}
$this->getContainer()->get('entityManagerUtil')->resetToDefaults($data->scope);
$this->getContainer()->get('dataManager')->clearCache();
return true;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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;
use \Espo\Core\Exceptions\BadRequest;
use \Espo\Core\Exceptions\Error;
class Pdf extends \Espo\Core\Controllers\Base
{
public function postActionMassPrint($params, $data)
{
if (empty($data->idList) || !is_array($data->idList)) {
throw new BadRequest();
}
if (empty($data->entityType)) {
throw new BadRequest();
}
if (empty($data->templateId)) {
throw new BadRequest();
}
if (!$this->getAcl()->checkScope('Template')) {
throw new Forbidden();
}
if (!$this->getAcl()->checkScope($data->entityType)) {
throw new Forbidden();
}
return [
'id' => $this->getServiceFactory()->create('Pdf')->massGenerate($data->entityType, $data->idList, $data->templateId, true)
];
}
}

View File

@@ -217,7 +217,7 @@ class Base implements Injectable
}
}
if ($entity->hasAttribute('assignedUsersIds') && $entity->hasRelation('assignedUsers')) {
if ($entity->hasLinkMultipleField('assignedUsers')) {
if ($entity->hasLinkMultipleId('assignedUsers', $user->id)) {
return true;
}

View File

@@ -169,7 +169,7 @@ class Table
if (isset($this->data->$permission)) {
return $this->data->$permission;
}
return null;
return 'no';
}
public function getLevel($scope, $action)
@@ -210,6 +210,7 @@ class Table
$this->applyDisabled($aclTable, $fieldTable);
$this->applyMandatory($aclTable, $fieldTable);
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
$this->applyReadOnlyFields($fieldTable);
} else {
$aclTable = (object) [];
foreach ($this->getScopeList() as $scope) {
@@ -714,4 +715,28 @@ class Table
{
$this->fileManager->putPhpContents($this->cacheFilePath, $this->data, true);
}
protected function applyReadOnlyFields(&$fieldTable)
{
// TODO Enable in 5.4.0
return;
$scopeList = $this->getScopeWithAclList();
foreach ($scopeList as $scope) {
if (!property_exists($fieldTable, $scope)) continue;
$fieldList = array_keys($this->getMetadata()->get(['entityDefs', $scope, 'fields'], []));
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $scope, 'fields', $field, 'readOnly'])) {
if (property_exists($fieldTable->$scope, $field)) {
$fieldTable->$scope->$field->edit = 'no';
} else {
$fieldTable->$scope->$field = (object) [];
foreach ($this->fieldActionList as $action) {
$fieldTable->$scope->$field->$action = 'yes';
}
$fieldTable->$scope->$field->edit = 'no';
}
}
}
}
}
}

View File

@@ -182,9 +182,15 @@ class AclManager
$impl = $this->getImplementation($scope);
$methodName = 'checkEntity' . ucfirst($action);
if (method_exists($impl, $methodName)) {
return $impl->$methodName($user, $entity, $data);
if (!$action) {
$action = 'read';
}
if ($action) {
$methodName = 'checkEntity' . ucfirst($action);
if (method_exists($impl, $methodName)) {
return $impl->$methodName($user, $entity, $data);
}
}
return $impl->checkEntity($user, $entity, $data, $action);

View File

@@ -91,6 +91,7 @@ class Application
public function runClient()
{
$this->getContainer()->get('clientManager')->display();
exit;
}
public function runEntryPoint($entryPoint, $data = array(), $final = false)
@@ -127,7 +128,7 @@ class Application
$slim->run();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode(), true);
$container->get('output')->processError($e->getMessage(), $e->getCode(), true, $e);
}
}
@@ -176,7 +177,7 @@ class Application
try {
$auth = $this->createAuth();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode());
$container->get('output')->processError($e->getMessage(), $e->getCode(), false, $e);
}
$apiAuth = $this->createApiAuth($auth);
@@ -226,7 +227,7 @@ class Application
$result = $controllerManager->process($controllerName, $actionName, $params, $data, $slim->request());
$container->get('output')->render($result);
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode());
$container->get('output')->processError($e->getMessage(), $e->getCode(), false, $e);
}
});

View File

@@ -273,7 +273,7 @@ class Container
return new \Espo\Core\Utils\Metadata\OrmMetadata(
$this->get('metadata'),
$this->get('fileManager'),
$this->get('config')->get('useCache')
$this->get('config')
);
}

View File

@@ -133,11 +133,12 @@ class Record extends Base
$q = $request->get('q');
$textFilter = $request->get('textFilter');
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
if (empty($maxSize)) {
$maxSize = self::MAX_SIZE_LIMIT;
$maxSize = $maxSizeLimit;
}
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
throw new Forbidden("Max should should not exceed " . self::MAX_SIZE_LIMIT . ". Use pagination (offset, limit).");
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
}
$params = array(
@@ -160,6 +161,49 @@ class Record extends Base
);
}
public function getActionListKanban($params, $data, $request)
{
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
}
$where = $request->get('where');
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$asc = $request->get('asc', 'true') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$textFilter = $request->get('textFilter');
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
if (empty($maxSize)) {
$maxSize = $maxSizeLimit;
}
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
}
$params = array(
'where' => $where,
'offset' => $offset,
'maxSize' => $maxSize,
'asc' => $asc,
'sortBy' => $sortBy,
'q' => $q,
'textFilter' => $textFilter
);
$this->fetchListParamsFromRequest($params, $request, $data);
$result = $this->getRecordService()->getListKanban($params);
return (object) [
'total' => $result->total,
'list' => $result->collection->getValueMapList(),
'additionalData' => $result->additionalData
];
}
protected function fetchListParamsFromRequest(&$params, $request, $data)
{
if ($request->get('primaryFilter')) {
@@ -171,6 +215,10 @@ class Record extends Base
if ($request->get('filterList')) {
$params['filterList'] = $request->get('filterList');
}
if ($request->get('select')) {
$params['select'] = explode(',', $request->get('select'));
}
}
public function actionListLinked($params, $data, $request)
@@ -186,11 +234,12 @@ class Record extends Base
$q = $request->get('q');
$textFilter = $request->get('textFilter');
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
if (empty($maxSize)) {
$maxSize = self::MAX_SIZE_LIMIT;
$maxSize = $maxSizeLimit;
}
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
throw new Forbidden();
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
}
$params = array(

View File

@@ -53,6 +53,8 @@ class DataManager
*/
public function rebuild($entityList = null)
{
$this->populateConfigParameters();
$result = $this->clearCache();
$result &= $this->rebuildMetadata();
@@ -172,5 +174,25 @@ class DataManager
$this->getContainer()->get('config')->save();
return true;
}
}
protected function populateConfigParameters()
{
$config = $this->getContainer()->get('config');
$pdo = $this->getContainer()->get('entityManager')->getPDO();
$query = "SHOW VARIABLES LIKE 'ft_min_word_len'";
$sth = $pdo->prepare($query);
$sth->execute();
$fullTextSearchMinLength = null;
if ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
if (isset($row['Value'])) {
$fullTextSearchMinLength = intval($row['Value']);
}
}
$config->set('fullTextSearchMinLength', $fullTextSearchMinLength);
$config->save();
}
}

View File

@@ -62,18 +62,26 @@ class Xlsx extends \Espo\Core\Injectable
public function loadAdditionalFields(Entity $entity, $fieldList)
{
foreach ($entity->getRelationList() as $link) {
if ($entity->getRelationType($link) === 'belongsToParent') {
if (in_array($link, $fieldList)) {
$parent = $entity->get($link);
if ($parent instanceof Entity) {
$entity->set($link . 'Name', $parent->get('name'));
if (in_array($link, $fieldList)) {
if ($entity->getRelationType($link) === 'belongsToParent') {
if (!$entity->get($link . 'Name')) {
$entity->loadParentNameField($link);
}
}
} else if ($entity->getRelationType($link) === 'belongsTo' && $entity->getRelationParam($link, 'noJoin') && $entity->hasField($link . 'Name')) {
if (in_array($link, $fieldList)) {
$related = $entity->get($link);
if ($related instanceof Entity) {
$entity->set($link . 'Name', $related->get('name'));
} else if (
(
(
$entity->getRelationType($link) === 'belongsTo'
&&
$entity->getRelationParam($link, 'noJoin')
)
||
$entity->getRelationType($link) === 'hasOne'
)
&&
$entity->hasAttribute($link . 'Name')
) {
if (!$entity->get($link . 'Name') || !$entity->get($link . 'Id')) {
$entity->loadLinkField($link);
}
}
}
@@ -292,6 +300,8 @@ class Xlsx extends \Espo\Core\Injectable
}
} else if ($type == 'int') {
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
} else if ($type == 'float') {
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
} else if ($type == 'currency') {
if (array_key_exists($name.'Currency', $row) && array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
@@ -467,7 +477,7 @@ class Xlsx extends \Espo\Core\Injectable
} else {
if (array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name]);
$sheet->setCellValueExplicit("$col$rowNumber", $row[$name], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
}
}
@@ -534,6 +544,9 @@ class Xlsx extends \Espo\Core\Injectable
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
if (!array_key_exists($name, $typesCache)) {
break;
}
$type = $typesCache[$name];
switch ($type) {
@@ -546,6 +559,11 @@ class Xlsx extends \Espo\Core\Injectable
->getNumberFormat()
->setFormatCode('0');
} break;
case 'float': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
} break;
case 'date': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()

View File

@@ -0,0 +1,89 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\EntityGroup;
use \Espo\ORM\Entity;
use \Espo\Core\Exceptions\Error;
class CountRelatedType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('selectManagerFactory');
}
public function process(\StdClass $item)
{
if (!property_exists($item, 'value')) {
throw new Error();
}
if (!is_array($item->value)) {
throw new Error();
}
if (count($item->value) < 1) {
throw new Error();
}
$link = $this->evaluate($item->value[0]);
if (empty($link)) {
throw new Error("No link passed to countRelated function.");
}
$filter = null;
if (count($item->value) > 1) {
$filter = $this->evaluate($item->value[1]);
}
$entity = $this->getEntity();
$entityManager = $this->getInjection('entityManager');
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (empty($foreignEntityType)) {
throw new Error();
}
$foreignSelectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
$selectParams = $foreignSelectManager->getEmptySelectParams();
if ($filter) {
$foreignSelectManager->applyFilter($filter, $selectParams);
}
return $entityManager->getRepository($entity->getEntityType())->countRelated($entity, $link, $selectParams);
}
}

View File

@@ -0,0 +1,119 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\EntityGroup;
use \Espo\ORM\Entity;
use \Espo\Core\Exceptions\Error;
class SumRelatedType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
$this->addDependency('selectManagerFactory');
}
public function process(\StdClass $item)
{
if (!property_exists($item, 'value')) {
throw new Error();
}
if (!is_array($item->value)) {
throw new Error();
}
if (count($item->value) < 2) {
throw new Error();
}
$link = $this->evaluate($item->value[0]);
if (empty($link)) {
throw new Error("No link passed to sumRelated function.");
}
$field = $this->evaluate($item->value[1]);
if (empty($field)) {
throw new Error("No field passed to sumRelated function.");
}
$filter = null;
if (count($item->value) > 2) {
$filter = $this->evaluate($item->value[2]);
}
$entity = $this->getEntity();
$entityManager = $this->getInjection('entityManager');
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (empty($foreignEntityType)) {
throw new Error();
}
$foreignSelectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
$foreignLink = $entity->getRelationParam($link, 'foreign');
if (empty($foreignLink)) {
throw new Error("No foreign link for link {$link}.");
}
$selectParams = $foreignSelectManager->getEmptySelectParams();
if ($filter) {
$foreignSelectManager->applyFilter($filter, $selectParams);
}
$selectParams['select'] = [[$foreignLink . '.id', 'foreignId'], 'SUM:' . $field];
$foreignSelectManager->addJoin($foreignLink, $selectParams);
$selectParams['groupBy'] = [$foreignLink . '.id'];
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);
$sql = $entityManager->getQuery()->createSelectQuery($foreignEntityType, $selectParams);
$pdo = $entityManager->getPDO();
$sth = $pdo->prepare($sql);
$sth->execute();
$rowList = $sth->fetchAll(\PDO::FETCH_ASSOC);
if (empty($rowList)) {
return 0;
}
return $rowList[0]['SUM:' . $field];
}
}

View File

@@ -0,0 +1,53 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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 ContainsType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
if (!property_exists($item, 'value') || !is_array($item->value)) {
throw new Error('Value for \'String\\Contains\' item is not an array.');
}
if (count($item->value) < 2) {
throw new Error('Bad arguments passed to \'String\\Contains\'.');
}
$string = $this->evaluate($item->value[0]);
$needle = $this->evaluate($item->value[1]);
if (!is_string($string)) {
return false;
}
return mb_strpos($string, $needle) !== false;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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 LengthType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
if (!property_exists($item, 'value')) {
return '';
}
if (!is_array($item->value)) {
throw new Error();
}
if (count($item->value) < 1) {
throw new Error();
}
$value = $this->evaluate($item->value[0]);
if (!is_string($value)) {
$value = strval($value);
}
return mb_strlen($value);
}
}

View File

@@ -53,6 +53,6 @@ class LowerCaseType extends \Espo\Core\Formula\Functions\Base
$value = strval($value);
}
return strtolower($value);
return mb_strtolower($value);
}
}

View File

@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -52,9 +52,9 @@ class SubstringType extends \Espo\Core\Formula\Functions\Base
if (count($item->value) > 2) {
$length = $this->evaluate($item->value[2]);
return substr($string, $start, $length);
return mb_substr($string, $start, $length);
} else {
return substr($string, $start);
return mb_substr($string, $start);
}
}
}

View File

@@ -53,6 +53,6 @@ class UpperCaseType extends \Espo\Core\Formula\Functions\Base
$value = strval($value);
}
return strtoupper($value);
return mb_strtoupper($value);
}
}

View File

@@ -176,6 +176,8 @@ class Parser
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
$expressionOutOfBraceList = [];
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
if ($modifiedExpression[$i] === '(') {
$braceCounter++;
@@ -186,6 +188,11 @@ class Parser
if ($braceCounter === 0 && $i < strlen($modifiedExpression) - 1) {
$hasExcessBraces = false;
}
if ($braceCounter === 0) {
$expressionOutOfBraceList[] = true;
} else {
$expressionOutOfBraceList[] = false;
}
}
if ($braceCounter !== 0) {
throw new Error('Incorrect round brackets in expression ' . $expression . '.');
@@ -226,7 +233,13 @@ class Parser
foreach ($this->priorityList as $operationList) {
foreach ($operationList as $operator) {
$index = strpos($expression, $operator, 1);
$startFrom = 1;
while (true) {
$index = strpos($expression, $operator, $startFrom);
if ($index === false) break;
if ($expressionOutOfBraceList[$index]) break;
$startFrom = $index + 1;
}
if ($index !== false) {
$possibleRightOperator = null;
if (strlen($operator) === 1) {

View File

@@ -100,10 +100,10 @@ class Htmlizer
$forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read');
}
foreach ($fieldList as $field) {
if (in_array($field, $forbidenAttributeList)) continue;
$type = $entity->getAttributeType($field);
if ($type == Entity::DATETIME) {
@@ -121,7 +121,7 @@ class Htmlizer
foreach ($list as $item) {
$v = $item;
if ($item instanceof \StdClass) {
$v = json_decode(json_encode($v), true);
$v = json_decode(json_encode($v, \JSON_PRESERVE_ZERO_FRACTION), true);
}
if (is_array($v)) {
foreach ($v as $k => $w) {
@@ -139,7 +139,7 @@ class Htmlizer
if (!empty($data[$field])) {
$value = $data[$field];
if ($value instanceof \StdClass) {
$data[$field] = json_decode(json_encode($value), true);
$data[$field] = json_decode(json_encode($value, \JSON_PRESERVE_ZERO_FRACTION), true);
}
foreach ($data[$field] as $k => $w) {
$keyRaw = $k . '_RAW';
@@ -219,6 +219,14 @@ class Htmlizer
return number_format($number, $decimals, $decimalPoint, $thousandsSeparator);
}
return '';
},
'var' => function ($context, $options) {
if ($context && isset($context[0]) && isset($context[1])) {
if (isset($context[1][$context[0]])) {
return $context[1][$context[0]];
}
}
return;
}
],
'hbhelpers' => [
@@ -260,13 +268,20 @@ class Htmlizer
$data = $this->getDataFromEntity($entity, $skipLinks);
if (!array_key_exists('today', $data)) {
$data['today'] = $this->dateTime->getTodayString();
}
if (!array_key_exists('now', $data)) {
$data['now'] = $this->dateTime->getNowString();
}
foreach ($additionalData as $k => $value) {
$data[$k] = $value;
}
$html = $renderer($data);
$html = str_replace('?entryPoint=attachment&amp;', '?entryPoint=attachment&', $html);
if ($this->getEntityManager()) {

View File

@@ -205,6 +205,10 @@ class Importer
))->findOne();
if ($replied) {
$email->set('repliedId', $replied->id);
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
foreach ($repliedTeamIdList as $repliedTeamId) {
$email->addLinkMultipleId('teams', $repliedTeamId);
}
}
}
@@ -296,6 +300,23 @@ class Importer
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
if ($parentFound) {
$parentType = $email->get('parentType');
$parentId = $email->get('parentId');
$emailKeepParentTeamsEntityList = $this->getConfig()->get('emailKeepParentTeamsEntityList', []);
if ($parentId && in_array($parentType, $emailKeepParentTeamsEntityList)) {
if ($this->getEntityManager()->hasRepository($parentType)) {
$parent = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($parent) {
$parentTeamIdList = $parent->getLinkMultipleIdList('teams');
foreach ($parentTeamIdList as $parentTeamId) {
$email->addLinkMultipleId('teams', $parentTeamId);
}
}
}
}
}
$this->getEntityManager()->saveEntity($email);
foreach ($inlineAttachmentList as $attachment) {

View File

@@ -179,9 +179,10 @@ class MailMimeParser
} else {
$email->set('isHtml', false);
$email->set('body', $bodyPlain);
$email->set('bodyPlain', $bodyPlain);
}
if (!$email->get('body') && $email->get('bodyPlain')) {
if (!$email->get('body') && $email->hasBodyPlain()) {
$email->set('body', $email->get('bodyPlain'));
}

View File

@@ -165,9 +165,10 @@ class PhpMimeMailParser
} else {
$email->set('isHtml', false);
$email->set('body', $bodyPlain);
$email->set('bodyPlain', $bodyPlain);
}
if (!$email->get('body') && $email->get('bodyPlain')) {
if (!$email->get('body') && $email->hasBodyPlain()) {
$email->set('body', $email->get('bodyPlain'));
}

View File

@@ -139,7 +139,7 @@ class ZendMail
$this->importPartDataToEmail($email, $zendMessage, $inlineIds, 'text/plain', $inlineAttachmentList);
}
if (!$email->get('body') && $email->get('bodyPlain')) {
if (!$email->get('body') && $email->hasBodyPlain()) {
$email->set('body', $email->get('bodyPlain'));
}
@@ -192,7 +192,7 @@ class ZendMail
$content = $this->getContentFromPart($part);
if ($type == 'text/plain') {
$bodyPlain = '';
if ($email->get('bodyPlain')) {
if ($email->hasBodyPlain()) {
$bodyPlain .= $email->get('bodyPlain') . "\n";
}
$bodyPlain .= $content;

View File

@@ -89,8 +89,12 @@ class Sender
$this->transport = new SmtpTransport();
$config = $this->config;
$localHostName = $config->get('smtpLocalHostName', gethostname());
$opts = array(
'name' => 'admin',
'name' => $localHostName,
'host' => $params['server'],
'port' => $params['port'],
'connection_config' => array()
@@ -132,8 +136,10 @@ class Sender
$config = $this->config;
$localHostName = $config->get('smtpLocalHostName', gethostname());
$opts = array(
'name' => 'admin',
'name' => $localHostName,
'host' => $config->get('smtpServer'),
'port' => $config->get('smtpPort'),
'connection_config' => array()
@@ -260,7 +266,7 @@ class Sender
$attachment = new MimePart(file_get_contents($fileName));
$attachment->disposition = Mime::DISPOSITION_ATTACHMENT;
$attachment->encoding = Mime::ENCODING_BASE64;
$attachment->filename = $a->get('name');
$attachment->filename ='=?utf-8?B?' . base64_encode($a->get('name')) . '?=';
if ($a->get('type')) {
$attachment->type = $a->get('type');
}

View File

@@ -92,11 +92,27 @@ class Base implements Injectable
public function process(Entity $entity)
{
if (!$entity->get('assignedUserId')) return;
if (!$entity->isAttributeChanged('assignedUserId')) return;
if ($entity->hasLinkMultipleField('assignedUsers')) {
$userIdList = $entity->getLinkMultipleIdList('assignedUsers');
$fetchedAssignedUserIdList = $entity->getFetched('assignedUsersIds');
if (!is_array($fetchedAssignedUserIdList)) {
$fetchedAssignedUserIdList = [];
}
$assignedUserId = $entity->get('assignedUserId');
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
$this->processForUser($entity, $userId);
}
} else {
if (!$entity->get('assignedUserId')) return;
if (!$entity->isAttributeChanged('assignedUserId')) return;
$assignedUserId = $entity->get('assignedUserId');
$this->processForUser($entity, $assignedUserId);
}
}
protected function processForUser(Entity $entity, $assignedUserId)
{
if ($entity->hasAttribute('createdById') && $entity->hasAttribute('modifiedById')) {
if ($entity->isNew()) {
$isNotSelfAssignment = $assignedUserId !== $entity->get('createdById');

View File

@@ -53,15 +53,14 @@ class Entity extends \Espo\ORM\Entity
$repository = $this->entityManager->getRepository($parentType);
$select = ['id', 'name'];
if ($parentType === 'Lead') {
$select[] = 'accountName';
$select[] = 'emailAddress';
$select[] = 'phoneNumber';
}
$foreignEntity = $repository->select($select)->where(['id' => $parentId])->findOne();
if ($foreignEntity) {
$this->set($field . 'Name', $foreignEntity->get('name'));
} else {
$this->set($field . 'Name', null);
}
} else {
$this->set($field . 'Name', null);
}
}
@@ -105,11 +104,6 @@ class Entity extends \Espo\ORM\Entity
}
$defs['select'] = ['id', 'name'];
if ($foreignEntityType === 'Lead') {
$defs['select'][] = 'accountName';
$defs['select'][] = 'emailAddress';
$defs['select'][] = 'phoneNumber';
}
$hasType = false;
if ($this->hasField($field . 'Types')) {
@@ -143,6 +137,10 @@ class Entity extends \Espo\ORM\Entity
}
$this->set($idsAttribute, $ids);
if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
$this->setFetched($idsAttribute, $ids);
}
$this->set($field . 'Names', $names);
if ($hasType) {
$this->set($field . 'Types', $types);
@@ -157,7 +155,13 @@ class Entity extends \Espo\ORM\Entity
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Id')) return;
if ($this->getRelationType($field) !== 'hasOne' && $this->getRelationType($field) !== 'belongsTo') return;
$entity = $this->get($field);
$relatedEntityType = $this->getRelationParam($field, 'entity');
$select = ['id', 'name'];
$entity = $this->get($field, [
'select' => $select
]);
$entityId = null;
$entityName = null;
@@ -166,7 +170,13 @@ class Entity extends \Espo\ORM\Entity
$entityName = $entity->get('name');
}
$this->set($field . 'Id', $entityId);
$idAttribute = $field . 'Id';
if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
$this->setFetched($idAttribute, $entityId);
}
$this->set($idAttribute, $entityId);
$this->set($field . 'Name', $entityName);
}

View File

@@ -465,17 +465,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$data->$columnName = $foreignEntity->get($columnField);
}
$existingColumnsData->$foreignId = $data;
$entity->setFetched($columnsFieldsName, $existingColumnsData);
if (!$entity->isNew()) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
}
}
}
}
if ($entity->has($fieldName)) {
$entity->setFetched($fieldName, $existingIds);
}
if ($entity->has($columnsFieldsName) && !empty($columns)) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
if (!$entity->isNew()) {
if ($entity->has($fieldName)) {
$entity->setFetched($fieldName, $existingIds);
}
if ($entity->has($columnsFieldsName) && !empty($columns)) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
}
}
foreach ($existingIds as $id) {
@@ -540,13 +544,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$where[$foreignKey] = $entity->id;
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
if ($previousForeignEntity) {
$entity->setFetched($idFieldName, $previousForeignEntity->id);
if (!$entity->isNew()) {
$entity->setFetched($idFieldName, $previousForeignEntity->id);
}
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
$previousForeignEntity->set($foreignKey, null);
$this->getEntityManager()->saveEntity($previousForeignEntity);
}
} else {
$entity->setFetched($idFieldName, null);
if (!$entity->isNew()) {
$entity->setFetched($idFieldName, null);
}
}
if ($entity->get($idFieldName)) {

View File

@@ -40,6 +40,13 @@ class Tcpdf extends \TCPDF
protected $footerPosition = 15;
protected $useGroupNumbers = false;
public function setUseGroupNumbers($value)
{
$this->useGroupNumbers = $value;
}
public function setFooterHtml($html)
{
$this->footerHtml = $html;
@@ -58,7 +65,16 @@ class Tcpdf extends \TCPDF
$this->SetY((-1) * $this->footerPosition);
$html = str_replace('{pageNumber}', '{:pnp:}', $this->footerHtml);
$html = $this->footerHtml;
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);
}
$this->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, '', 0, false, 'T');
$this->SetAutoPageBreak($autoPageBreak, $breakMargin);
@@ -98,6 +114,9 @@ class Tcpdf extends \TCPDF
++$pagegroupnum;
$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
$pnga = $pngu;
$png_num_chars = $this->GetNumChars($pnga);
// replace page numbers
$replace = array();

View File

@@ -99,6 +99,6 @@ class Application extends \Espo\Core\Application
$this->getContainer()->get('clientManager')->display(null, 'html/portal.html', array(
'portalId' => $this->getPortal()->id
));
exit;
}
}

View File

@@ -64,6 +64,10 @@ class Base
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
const MIN_LENGTH_FOR_FULL_TEXT_SEARCH = 4;
protected $fullTextSearchDataCacheHash = [];
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config, InjectableFactory $injectableFactory)
{
$this->entityManager = $entityManager;
@@ -146,7 +150,7 @@ class Base
} else {
$orderPart = 'DESC';
}
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . 'Street', $orderPart]];
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . '_eet', $orderPart]];
return;
} else if ($type === 'enum') {
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
@@ -157,6 +161,9 @@ class Base
if ($desc) {
$list = array_reverse($list);
}
foreach ($list as $i => $listItem) {
$list[$i] = str_replace(',', '_COMMA_', $listItem);
}
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
return;
}
@@ -203,8 +210,8 @@ class Base
}
$this->applyBoolFilter($filter, $result);
}
} else if ($item['type'] == 'textFilter' && !empty($item['value'])) {
if (!empty($item['value'])) {
} else if ($item['type'] == 'textFilter') {
if (isset($item['value']) || $item['value'] !== '') {
$this->textFilter($item['value'], $result);
}
} else if ($item['type'] == 'primary' && !empty($item['value'])) {
@@ -387,8 +394,9 @@ class Base
protected function q($params, &$result)
{
if (!empty($params['q'])) {
$this->textFilter($params['q'], $result);
if (isset($params['q']) && $params['q'] !== '') {
$textFilter = $params['q'];
$this->textFilter($textFilter, $result);
}
}
@@ -401,7 +409,7 @@ class Base
public function manageTextFilter($textFilter, &$result)
{
$this->prepareResult($result);
$this->q(array('q' => $textFilter), $result);
$this->q(['q' => $textFilter], $result);
}
public function getEmptySelectParams()
@@ -484,9 +492,9 @@ class Base
{
if ($this->hasAssignedUsersField()) {
$this->setDistinct(true, $result);
$this->addLeftJoin('assignedUsers', $result);
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
$result['whereClause'][] = array(
'assignedUsers.id' => $this->getUser()->id
'assignedUsersAccess.id' => $this->getUser()->id
);
return;
}
@@ -732,7 +740,7 @@ class Base
$this->where($params['where'], $result);
}
if (!empty($params['textFilter'])) {
if (isset($params['textFilter']) && $params['textFilter'] !== '') {
$this->textFilter($params['textFilter'], $result);
}
@@ -1007,7 +1015,7 @@ class Base
foreach ($item['value'] as $i) {
$a = $this->getWherePart($i, $result);
foreach ($a as $left => $right) {
if (!empty($right) || is_null($right) || $right === '') {
if (!empty($right) || is_null($right) || $right === '' || $right === 0 || $right === false) {
$arr[] = array($left => $right);
}
}
@@ -1247,7 +1255,7 @@ class Base
$value = $item['value'];
if (is_null($value)) break;
if (is_null($value) || !$value && !is_array($value)) break;
$relationType = $seed->getRelationType($link);
@@ -1500,36 +1508,252 @@ class Base
);
}
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
{
if (array_key_exists($textFilter, $this->fullTextSearchDataCacheHash)) {
return $this->fullTextSearchDataCacheHash[$textFilter];
}
if ($this->getConfig()->get('fullTextSearchDisabled')) {
return null;
}
$result = null;
$fieldList = $this->getTextFilterFieldList();
if ($isAuxiliaryUse) {
$textFilter = str_replace('%', '', $textFilter);
}
$fullTextSearchColumnList = $this->getEntityManager()->getOrmMetadata()->get($this->getEntityType(), ['fullTextSearchColumnList']);
$useFullTextSearch = false;
if (
$this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'collection', 'fullTextSearch'])
&&
!empty($fullTextSearchColumnList)
) {
$fullTextSearchMinLength = $this->getConfig()->get('fullTextSearchMinLength', self::MIN_LENGTH_FOR_FULL_TEXT_SEARCH);
if (!$fullTextSearchMinLength) {
$fullTextSearchMinLength = 0;
}
$textFilterWoWildcards = str_replace('*', '', $textFilter);
if (mb_strlen($textFilterWoWildcards) >= $fullTextSearchMinLength) {
$useFullTextSearch = true;
}
}
$fullTextSearchFieldList = [];
if ($useFullTextSearch) {
foreach ($fieldList as $field) {
$defs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $field], []);
if (empty($defs['type'])) continue;
$fieldType = $defs['type'];
if (!empty($defs['notStorable'])) continue;
if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue;
$fullTextSearchFieldList[] = $field;
}
if (!count($fullTextSearchFieldList)) {
$useFullTextSearch = false;
}
}
if (empty($fullTextSearchColumnList)) {
$useFullTextSearch = false;
}
if ($isAuxiliaryUse) {
if (mb_strpos($textFilter, '@') !== false) {
$useFullTextSearch = false;
}
}
if ($useFullTextSearch) {
$textFilter = str_replace(['(', ')'], '', $textFilter);
if (
$isAuxiliaryUse && mb_strpos($textFilter, '*') === false
||
mb_strpos($textFilter, ' ') === false
&&
mb_strpos($textFilter, '+') === false
&&
mb_strpos($textFilter, '-') === false
&&
mb_strpos($textFilter, '*') === false
) {
$function = 'MATCH_NATURAL_LANGUAGE';
} else {
$function = 'MATCH_BOOLEAN';
}
$textFilter = str_replace('"*', '"', $textFilter);
$textFilter = str_replace('*"', '"', $textFilter);
while (strpos($textFilter, '**')) {
$textFilter = str_replace('**', '*', $textFilter);
$textFilter = trim($textFilter);
}
while (mb_substr($textFilter, -2) === ' *') {
$textFilter = mb_substr($textFilter, 0, mb_strlen($textFilter) - 2);
$textFilter = trim($textFilter);
}
$fullTextSearchColumnSanitizedList = [];
$query = $this->getEntityManager()->getQuery();
foreach ($fullTextSearchColumnList as $i => $field) {
$fullTextSearchColumnSanitizedList[$i] = $query->sanitize($query->toDb($field));
}
$where = $function . ':' . implode(',', $fullTextSearchColumnSanitizedList) . ':' . $textFilter;
$result = [
'where' => $where,
'fieldList' => $fullTextSearchFieldList,
'columnList' => $fullTextSearchColumnList
];
}
$this->fullTextSearchDataCacheHash[$textFilter] = $result;
return $result;
}
protected function textFilter($textFilter, &$result)
{
$fieldDefs = $this->getSeed()->getAttributes();
$fieldList = $this->getTextFilterFieldList();
$d = array();
$group = [];
$textFilterContainsMinLength = $this->getConfig()->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
foreach ($fieldList as $field) {
if (
strlen($textFilter) >= $textFilterContainsMinLength
&&
(
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
||
!empty($this->textFilterUseContainsAttributeList[$field])
||
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'varchar' &&
$this->getConfig()->get('textFilterUseContainsForVarchar')
)
) {
$expression = '%' . $textFilter . '%';
} else {
$expression = $textFilter . '%';
}
$d[$field . '*'] = $expression;
$fullTextSearchData = null;
$forceFullTextSearch = false;
$useFullTextSearch = !empty($result['useFullTextSearch']);
if (mb_strpos($textFilter, 'ft:') === 0) {
$textFilter = mb_substr($textFilter, 3);
$useFullTextSearch = true;
$forceFullTextSearch = true;
}
$result['whereClause'][] = array(
'OR' => $d
);
$textFilterForFullTextSearch = $textFilter;
$skipWidlcards = false;
if (mb_strpos($textFilter, '*') !== false) {
$skipWidlcards = true;
$textFilter = str_replace('*', '%', $textFilter);
} else {
if (!$useFullTextSearch) {
$textFilterForFullTextSearch .= '*';
}
}
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
$skipFullTextSearch = false;
if (!$forceFullTextSearch) {
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
$skipFullTextSearch = true;
} else if (mb_strpos($textFilterForFullTextSearch, ' *') !== false) {
$skipFullTextSearch = true;
}
}
$fullTextSearchData = null;
if (!$skipFullTextSearch) {
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
}
$fullTextGroup = [];
$fullTextSearchFieldList = [];
if ($fullTextSearchData) {
$fullTextGroup[] = $fullTextSearchData['where'];
$fullTextSearchFieldList = $fullTextSearchData['fieldList'];
}
foreach ($fieldList as $field) {
if ($useFullTextSearch) {
if (in_array($field, $fullTextSearchFieldList)) continue;
}
if ($forceFullTextSearch) continue;
$attributeType = null;
if (!empty($fieldDefs[$field]['type'])) {
$attributeType = $fieldDefs[$field]['type'];
}
if ($attributeType === 'int') {
if (is_numeric($textFilter)) {
$group[$field] = intval($textFilter);
}
continue;
}
if (!$skipWidlcards) {
if (
mb_strlen($textFilter) >= $textFilterContainsMinLength
&&
(
$attributeType == 'text'
||
in_array($field, $this->textFilterUseContainsAttributeList)
||
$attributeType == 'varchar' && $this->getConfig()->get('textFilterUseContainsForVarchar')
)
) {
$expression = '%' . $textFilter . '%';
} else {
$expression = $textFilter . '%';
}
} else {
$expression = $textFilter;
}
if ($fullTextSearchData) {
if (!$useFullTextSearch) {
if (in_array($field, $fullTextSearchFieldList)) {
if (!array_key_exists('OR', $fullTextGroup)) {
$fullTextGroup['OR'] = [];
}
$fullTextGroup['OR'][$field . '*'] = $expression;
continue;
}
}
}
$group[$field . '*'] = $expression;
}
if (!$forceFullTextSearch) {
$this->applyAdditionalToTextFilterGroup($textFilter, $group, $result);
}
if (!empty($fullTextGroup)) {
$group['AND'] = $fullTextGroup;
}
if (count($group) === 0) {
$result['whereClause'][] = [
'id' => null
];
}
$result['whereClause'][] = [
'OR' => $group
];
}
protected function applyAdditionalToTextFilterGroup($textFilter, &$group, &$result)
{
}
public function applyAccess(&$result)
@@ -1558,19 +1782,25 @@ class Base
protected function boolFilterOnlyMy(&$result)
{
if (!$this->checkIsPortal()) {
if ($this->hasAssignedUserField()) {
$result['whereClause'][] = array(
if ($this->hasAssignedUsersField()) {
$this->setDistinct(true, $result);
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
$result['whereClause'][] = [
'assignedUsersAccess.id' => $this->getUser()->id
];
} else if ($this->hasAssignedUserField()) {
$result['whereClause'][] = [
'assignedUserId' => $this->getUser()->id
);
];
} else {
$result['whereClause'][] = array(
$result['whereClause'][] = [
'createdById' => $this->getUser()->id
);
];
}
} else {
$result['whereClause'][] = array(
$result['whereClause'][] = [
'createdById' => $this->getUser()->id
);
];
}
}

View File

@@ -1,7 +1,8 @@
{
"fields": {
"name": {
"type": "personName"
"type": "personName",
"isPersonalData": true
},
"salutationName": {
"type": "enum",
@@ -24,15 +25,18 @@
"type": "text"
},
"emailAddress": {
"type": "email"
"type": "email",
"isPersonalData": true
},
"phoneNumber": {
"type": "phone",
"typeList": ["Mobile", "Office", "Home", "Fax", "Other"],
"defaultType": "Mobile"
"defaultType": "Mobile",
"isPersonalData": true
},
"address": {
"type": "address"
"type": "address",
"isPersonalData": true
},
"addressStreet": {
"type": "text",

View File

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

View File

@@ -0,0 +1,5 @@
{
"labels": {
"Create {entityType}": "Napravi {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,10 @@
{
"links": {
"meetings": "Sastanci",
"calls": "Pozivi",
"tasks": "Zadaci"
},
"labels": {
"Create {entityType}": "Kreiraj {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,15 @@
{
"fields": {
"billingAddress": "Adresa za naplatu",
"shippingAddress": "Adresa za dostavu",
"website": "Sajt"
},
"links": {
"meetings": "Sastanci",
"calls": "Pozivi",
"tasks": "Zadaci"
},
"labels": {
"Create {entityType}": "Kreiraj {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,35 @@
{
"fields": {
"parent": "Nadređen",
"dateStart": "Početni datum",
"dateEnd": "Završni datum",
"duration": "Trajanje",
"reminders": "Podsjetnici"
},
"links": {
"parent": "Nadređen"
},
"options": {
"status": {
"Planned": "Planiran",
"Held": "Održan",
"Not Held": "Nije održan"
}
},
"labels": {
"Create {entityType}": "Kreiraj {entityTypeTranslated}",
"Schedule {entityType}": "Zakaži {entityTypeTranslated}",
"Log {entityType}": "Zabilježi {entitiTipeTranslated}",
"Set Held": "Postavi kao održano",
"Set Not Held": "Postavi kao nije održano"
},
"massActions": {
"setHeld": "Postavi kao održano",
"setNotHeld": "Postavi kao nije održano"
},
"presetFilters": {
"planned": "Planiran",
"held": "Održan",
"todays": "Današnji"
}
}

View File

@@ -0,0 +1,13 @@
{
"fields": {
"address": "Adresa"
},
"links": {
"meetings": "Sastanci",
"calls": "Pozivi",
"tasks": "Zadaci"
},
"labels": {
"Create {entityType}": "Kreiraj {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,5 @@
{
"labels": {
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
}
}

View File

@@ -0,0 +1,10 @@
{
"links": {
"meetings": "találkozók",
"calls": "felhívja",
"tasks": "feladatok"
},
"labels": {
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
}
}

View File

@@ -0,0 +1,15 @@
{
"fields": {
"billingAddress": "számlázási cím",
"shippingAddress": "szállítási cím",
"website": "Weboldal"
},
"links": {
"meetings": "találkozók",
"calls": "felhívja",
"tasks": "feladatok"
},
"labels": {
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
}
}

View File

@@ -0,0 +1,27 @@
{
"fields": {
"parent": "Szülő",
"dateStart": "Dátum kezdete",
"dateEnd": "Dátum vége",
"duration": "tartam",
"status": "Állapot",
"reminders": "Emlékeztetők"
},
"links": {
"parent": "Szülő"
},
"options": {
"status": {
"Planned": "Tervezett",
"Not Held": "Nem tartott"
}
},
"labels": {
"Create {entityType}": "{EntityTypeTranslated} létrehozása",
"Schedule {entityType}": "{EntityTypeTranslated} ütemezése"
},
"presetFilters": {
"planned": "Tervezett",
"todays": "A mai"
}
}

View File

@@ -0,0 +1,13 @@
{
"fields": {
"address": "Cím"
},
"links": {
"meetings": "találkozók",
"calls": "felhívja",
"tasks": "feladatok"
},
"labels": {
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
}
}

View File

@@ -1,5 +1,5 @@
{
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}

View File

@@ -1,5 +1,10 @@
{
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}
"links": {
"meetings": "Spotkania",
"calls": "Połączenia",
"tasks": "Zadania"
},
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}

View File

@@ -1,5 +1,15 @@
{
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}
"fields": {
"billingAddress": "Adres rozliczeniowy",
"shippingAddress": "Adres dostawy",
"website": "Strona internetowa"
},
"links": {
"meetings": "Spotkania",
"calls": "Połączenia",
"tasks": "Zadania"
},
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}

View File

@@ -1,5 +1,20 @@
{
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
"fields": {
"dateStart": "Data rozpoczęcia",
"dateEnd": "Data zakończenia",
"duration": "Czas trwania",
"reminders": "Przypomnienia"
},
"options": {
"status": {
"Planned": "Planowane"
}
}
},
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}",
"Schedule {entityType}": "Zaplanuj {entityTypeTranslated} "
},
"presetFilters": {
"planned": "Planowane"
}
}

View File

@@ -1,5 +1,13 @@
{
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}
"fields": {
"address": "Adres"
},
"links": {
"meetings": "Spotkania",
"calls": "Rozmowy",
"tasks": "Zadania"
},
"labels": {
"Create {entityType}": "Utwórz {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,5 @@
{
"labels": {
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,10 @@
{
"links": {
"meetings": "Stretnutia",
"calls": "Hovory",
"tasks": "Úlohy"
},
"labels": {
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,15 @@
{
"fields": {
"billingAddress": "Fakturačná adresa",
"shippingAddress": "Dodacia adresa",
"website": "Webová stránka"
},
"links": {
"meetings": "Stretnutia",
"calls": "Hovory",
"tasks": "Úlohy"
},
"labels": {
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
}
}

View File

@@ -0,0 +1,36 @@
{
"fields": {
"parent": "Rodič",
"dateStart": "Dátum začiatku",
"dateEnd": "Dátum konca",
"duration": "Trvanie",
"status": "Stav",
"reminders": "Pripomienky"
},
"links": {
"parent": "Rodič"
},
"options": {
"status": {
"Planned": "Plánovaný",
"Held": "Pozdržaný",
"Not Held": "Nepozdržaný"
}
},
"labels": {
"Create {entityType}": "Vytvoriť {entityTypeTranslated}",
"Schedule {entityType}": "Naplánovať {entityTypeTranslated}",
"Log {entityType}": "Protokol {entityTypeTranslated}",
"Set Held": "Nastav pozdržaný",
"Set Not Held": "Nastav nepozdržaný"
},
"massActions": {
"setHeld": "Nastav pozdržaný",
"setNotHeld": "Nastav nepozdržaný"
},
"presetFilters": {
"planned": "Plánovaný",
"held": "Pozdržaný",
"todays": "Dnešné"
}
}

View File

@@ -0,0 +1,13 @@
{
"fields": {
"address": "Adresa"
},
"links": {
"meetings": "Stretnutia",
"calls": "Hovory",
"tasks": "Úlohy"
},
"labels": {
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
}
}

View File

@@ -73,7 +73,9 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
}
/* run before install script */
$this->runScript('before');
if (!isset($data['skipBeforeScript']) || !$data['skipBeforeScript']) {
$this->runScript('before');
}
/* remove files defined in a manifest "deleteBeforeCopy" */
$this->deleteFiles('deleteBeforeCopy', true);
@@ -89,8 +91,10 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
$this->deleteFiles('vendor');
$this->copyFiles('vendor');
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
}
}
//afterInstallFiles
@@ -99,7 +103,9 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
}
/* run before install script */
$this->runScript('after');
if (!isset($data['skipAfterScript']) || !$data['skipAfterScript']) {
$this->runScript('after');
}
$this->afterRunAction();

View File

@@ -53,7 +53,7 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
$this->beforeRunAction();
/* run before install script */
if (!isset($data['isNotRunScriptBefore']) || !$data['isNotRunScriptBefore']) {
if (!isset($data['skipBeforeScript']) || !$data['skipBeforeScript']) {
$this->runScript('beforeUninstall');
}
@@ -71,12 +71,14 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
}
}
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
}
}
/* run after uninstall script */
if (!isset($data['isNotRunScriptAfter']) || !$data['isNotRunScriptAfter']) {
if (!isset($data['skipAfterScript']) || !$data['skipAfterScript']) {
$this->runScript('afterUninstall');
}

View File

@@ -149,6 +149,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
'fileList' => $fileList,
'description' => $manifest['description'],
);
if (!empty($manifest['checkVersionUrl'])) {
$data['checkVersionUrl'] = $manifest['checkVersionUrl'];
}
$extensionEntity->set($data);
return $entityManager->saveEntity($extensionEntity);
@@ -199,7 +204,8 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
$this->executeAction(ExtensionManager::UNINSTALL, array(
'id' => $extensionEntity->get('id'),
'isNotRunScriptAfter' => true,
'skipSystemRebuild' => true,
'skipAfterScript' => true,
)
);
}

View File

@@ -95,7 +95,13 @@ class AdminNotificationManager
$extensionsNeedingUpgrade = $this->getExtensionsNeedingUpgrade();
if (!empty($extensionsNeedingUpgrade)) {
foreach ($extensionsNeedingUpgrade as $extensionName => $extensionDetails) {
$message = $this->getLanguage()->translate('newExtensionVersionIsAvailable', 'messages', 'Admin');
$label = 'new' . Util::toCamelCase($extensionName, ' ', true) .'VersionIsAvailable';
$message = $this->getLanguage()->get(['Admin', 'messages', $label]);
if (!$message) {
$message = $this->getLanguage()->translate('newExtensionVersionIsAvailable', 'messages', 'Admin');
}
$notificationList[] = array(
'id' => 'newExtensionVersionIsAvailable' . Util::toCamelCase($extensionName, ' ', true),
'type' => 'newExtensionVersionIsAvailable',
@@ -120,6 +126,7 @@ class AdminNotificationManager
$latestVersion = $config->get('latestVersion');
if (isset($latestVersion)) {
$currentVersion = $config->get('version');
if ($currentVersion === 'dev') return;
if (version_compare($latestVersion, $currentVersion, '>')) {
return array(
'currentVersion' => $currentVersion,

View File

@@ -58,7 +58,7 @@ class Auth extends \Slim\Middleware
$espoAuth = $req->headers('HTTP_ESPO_AUTHORIZATION');
if (isset($espoAuth)) {
list($authUsername, $authPassword) = explode(':', base64_decode($espoAuth));
list($authUsername, $authPassword) = explode(':', base64_decode($espoAuth), 2);
}
if (!isset($authUsername)) {
@@ -84,7 +84,12 @@ class Auth extends \Slim\Middleware
if (isset($routeConditions['auth']) && $routeConditions['auth'] === false) {
if ($authUsername && $authPassword) {
$isAuthenticated = $this->auth->login($authUsername, $authPassword);
try {
$isAuthenticated = $this->auth->login($authUsername, $authPassword);
} catch (\Exception $e) {
$this->processException($e);
return;
}
if ($isAuthenticated) {
$this->next->call();
return;
@@ -105,8 +110,12 @@ class Auth extends \Slim\Middleware
}
if ($authUsername && $authPassword) {
$isAuthenticated = $this->auth->login($authUsername, $authPassword);
try {
$isAuthenticated = $this->auth->login($authUsername, $authPassword);
} catch (\Exception $e) {
$this->processException($e);
return;
}
if ($isAuthenticated) {
$this->next->call();
@@ -121,23 +130,31 @@ class Auth extends \Slim\Middleware
}
}
protected function processException(\Exception $e)
{
$response = $this->app->response();
if ($e->getMessage()) {
$response->headers->set('X-Status-Reason', $e->getMessage());
}
$response->setStatus($e->getCode());
}
protected function processUnauthorized()
{
$res = $this->app->response();
$response = $this->app->response();
if ($this->showDialog) {
$res->header('WWW-Authenticate', 'Basic realm=""');
} else {
$res->header('WWW-Authenticate');
$response->headers->set('WWW-Authenticate', 'Basic realm=""');
}
$res->status(401);
$response->setStatus(401);
}
protected function isXMLHttpRequest()
{
$req = $this->app->request();
$request = $this->app->request();
$httpXRequestedWith = $req->headers('HTTP_X_REQUESTED_WITH');
$httpXRequestedWith = $request->headers('HTTP_X_REQUESTED_WITH');
if (isset($httpXRequestedWith) && strtolower($httpXRequestedWith) == 'xmlhttprequest') {
return true;
@@ -145,6 +162,4 @@ class Auth extends \Slim\Middleware
return false;
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core\Utils\Api;
class Output
{
private $slim;
@@ -41,6 +42,9 @@ class Output
500 => 'Internal Server Error',
);
protected $ignorePrintXStatusReasonExceptionClassNameList = [
'PDOException'
];
public function __construct(\Espo\Core\Utils\Api\Slim $slim)
{
@@ -68,7 +72,7 @@ class Output
echo $data;
}
public function processError($message = 'Error', $code = 500, $isPrint = false)
public function processError($message = 'Error', $code = 500, $isPrint = false, $exception = null)
{
$currentRoute = $this->getSlim()->router()->getCurrentRoute();
@@ -78,7 +82,7 @@ class Output
$GLOBALS['log']->error('API ['.$this->getSlim()->request()->getMethod().']:'.$currentRoute->getPattern().', Params:'.print_r($currentRoute->getParams(), true).', InputData: '.$inputData.' - '.$message);
}
$this->displayError($message, $code, $isPrint);
$this->displayError($message, $code, $isPrint, $exception);
}
/**
@@ -89,15 +93,21 @@ class Output
*
* @return void
*/
public function displayError($text, $statusCode = 500, $isPrint = false)
public function displayError($text, $statusCode = 500, $isPrint = false, $exception = null)
{
$GLOBALS['log']->error('Display Error: '.$text.', Code: '.$statusCode.' URL: '.$_SERVER['REQUEST_URI']);
ob_clean();
if (!empty( $this->slim)) {
$this->getSlim()->response()->status($statusCode);
$this->getSlim()->response()->header('X-Status-Reason', $text);
$toPrintXStatusReason = true;
if ($exception && in_array(get_class($exception), $this->ignorePrintXStatusReasonExceptionClassNameList)) {
$toPrintXStatusReason = false;
}
$this->getSlim()->response()->setStatus($statusCode);
if ($toPrintXStatusReason) {
$this->getSlim()->response()->headers->set('X-Status-Reason', $text);
}
if ($isPrint) {
$status = $this->getCodeDesc($statusCode);
@@ -139,4 +149,3 @@ class Output
return preg_replace('/"(.*?password.*?)":".*?"/i', '"$1":"*****"', $inputData);
}
}

View File

@@ -48,6 +48,10 @@ class Auth
const ACCESS_ANY = 3;
const FAILED_ATTEMPTS_PERIOD = '60 seconds';
const MAX_FAILED_ATTEMPT_NUMBER = 10;
private $portal;
public function __construct(\Espo\Core\Container $container, $allowAnyAccess = false)
@@ -117,20 +121,34 @@ class Auth
public function login($username, $password)
{
$authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array(
'token' => $password,
'isActive' => true
))->findOne();
$isByTokenOnly = false;
if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION_BY_TOKEN') === 'true') {
$isByTokenOnly = true;
}
if (!$isByTokenOnly) {
$this->checkFailedAttemptsLimit($username);
}
$authToken = $this->getEntityManager()->getRepository('AuthToken')->where([
'token' => $password
])->findOne();
$authTokenIsFound = false;
if ($authToken) {
$authTokenIsFound = true;
}
if ($authToken && $authToken->get('isActive')) {
if (!$this->allowAnyAccess) {
if ($this->isPortal() && $authToken->get('portalId') !== $this->getPortal()->id) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal with a token not related to portal.");
return false;
$GLOBALS['log']->info("AUTH: Trying to login to portal with a token not related to portal.");
return;
}
if (!$this->isPortal() && $authToken->get('portalId')) {
$GLOBALS['log']->debug("AUTH: Trying to login to crm with a token related to portal.");
return false;
$GLOBALS['log']->info("AUTH: Trying to login to crm with a token related to portal.");
return;
}
}
if ($this->allowAnyAccess) {
@@ -141,77 +159,190 @@ class Auth
}
}
}
} else {
$authToken = null;
}
if ($isByTokenOnly && !$authToken) {
$GLOBALS['log']->info("AUTH: Trying to login as user '{$username}' by token but token is not found.");
return;
}
$user = $this->authentication->login($username, $password, $authToken);
if ($user) {
if (!$user->isActive()) {
$GLOBALS['log']->debug("AUTH: Trying to login as user '".$user->get('userName')."' which is not active.");
return false;
}
$authLogRecord = null;
if (!$user->isAdmin() && !$this->isPortal() && $user->get('isPortalUser')) {
$GLOBALS['log']->debug("AUTH: Trying to login to crm as a portal user '".$user->get('userName')."'.");
return false;
}
if (!$authTokenIsFound) {
$authLogRecord = $this->createAuthLogRecord($username, $user);
}
if (!$user->isAdmin() && $this->isPortal() && !$user->get('isPortalUser')) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not portal user.");
return false;
}
if (!$user) {
return;
}
if ($this->isPortal()) {
if (!$user->isAdmin() && !$this->getEntityManager()->getRepository('Portal')->isRelated($this->getPortal(), 'users', $user)) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is portal user but does not belongs to portal.");
return false;
if (!$user->isActive()) {
$GLOBALS['log']->info("AUTH: Trying to login as user '".$user->get('userName')."' which is not active.");
$this->logDenied($authLogRecord, 'INACTIVE_USER');
return;
}
if (!$user->isAdmin() && !$this->isPortal() && $user->get('isPortalUser')) {
$GLOBALS['log']->info("AUTH: Trying to login to crm as a portal user '".$user->get('userName')."'.");
$this->logDenied($authLogRecord, 'IS_PORTAL_USER');
return;
}
if (!$user->isAdmin() && $this->isPortal() && !$user->get('isPortalUser')) {
$GLOBALS['log']->info("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not portal user.");
$this->logDenied($authLogRecord, 'IS_NOT_PORTAL_USER');
return;
}
if ($this->isPortal()) {
if (!$user->isAdmin() && !$this->getEntityManager()->getRepository('Portal')->isRelated($this->getPortal(), 'users', $user)) {
$GLOBALS['log']->info("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is portal user but does not belongs to portal.");
$this->logDenied($authLogRecord, 'USER_IS_NOT_IN_PORTAL');
return;
}
$user->set('portalId', $this->getPortal()->id);
} else {
$user->loadLinkMultipleField('teams');
}
$user->set('ipAddress', $_SERVER['REMOTE_ADDR']);
$this->getEntityManager()->setUser($user);
$this->getContainer()->setUser($user);
if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION')) {
if (!$authToken) {
$authToken = $this->getEntityManager()->getEntity('AuthToken');
$token = $this->generateToken();
$authToken->set('token', $token);
$authToken->set('hash', $user->get('password'));
$authToken->set('ipAddress', $_SERVER['REMOTE_ADDR']);
$authToken->set('userId', $user->id);
if ($this->isPortal()) {
$authToken->set('portalId', $this->getPortal()->id);
}
$user->set('portalId', $this->getPortal()->id);
} else {
$user->loadLinkMultipleField('teams');
}
$user->set('ipAddress', $_SERVER['REMOTE_ADDR']);
$this->getEntityManager()->setUser($user);
$this->getContainer()->setUser($user);
if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION')) {
if (!$authToken) {
$authToken = $this->getEntityManager()->getEntity('AuthToken');
$token = $this->createToken($user);
$authToken->set('token', $token);
$authToken->set('hash', $user->get('password'));
$authToken->set('ipAddress', $_SERVER['REMOTE_ADDR']);
$authToken->set('userId', $user->id);
if ($this->isPortal()) {
$authToken->set('portalId', $this->getPortal()->id);
if ($this->getConfig()->get('authTokenPreventConcurrent')) {
$concurrentAuthTokenList = $this->getEntityManager()->getRepository('AuthToken')->select(['id'])->where([
'userId' => $user->id,
'isActive' => true
])->find();
foreach ($concurrentAuthTokenList as $concurrentAuthToken) {
$concurrentAuthToken->set('isActive', false);
$this->getEntityManager()->saveEntity($concurrentAuthToken);
}
}
$authToken->set('lastAccess', date('Y-m-d H:i:s'));
$this->getEntityManager()->saveEntity($authToken);
$user->set('token', $authToken->get('token'));
$user->set('authTokenId', $authToken->id);
}
}
$authToken->set('lastAccess', date('Y-m-d H:i:s'));
return true;
$this->getEntityManager()->saveEntity($authToken);
$user->set('token', $authToken->get('token'));
$user->set('authTokenId', $authToken->id);
if ($authLogRecord) {
$authLogRecord->set('authTokenId', $authToken->id);
}
}
if ($authLogRecord) {
$this->getEntityManager()->saveEntity($authLogRecord);
}
if ($authToken && !$authLogRecord) {
$authLogRecord = $this->getEntityManager()->getRepository('AuthLogRecord')->select(['id'])->where([
'authTokenId' => $authToken->id
])->order('requestTime', true)->findOne();
}
if ($authLogRecord) {
$user->set('authLogRecordId', $authLogRecord->id);
}
return true;
}
protected function checkFailedAttemptsLimit($username = null)
{
$failedAttemptsPeriod = $this->getConfig()->get('authFailedAttemptsPeriod', self::FAILED_ATTEMPTS_PERIOD);
$maxFailedAttempts = $this->getConfig()->get('authMaxFailedAttemptNumber', self::MAX_FAILED_ATTEMPT_NUMBER);
$requestTimeFrom = (new \DateTime('@' . intval($_SERVER['REQUEST_TIME_FLOAT'])))->modify('-' . $failedAttemptsPeriod);
$failAttemptCount = $this->getEntityManager()->getRepository('AuthLogRecord')->where([
'requestTime>' => $requestTimeFrom->format('U'),
'ipAddress' => $_SERVER['REMOTE_ADDR'],
'isDenied' => true
])->count();
if ($failAttemptCount > $maxFailedAttempts) {
$GLOBALS['log']->warning("AUTH: Max failed login attempts exceeded for IP '".$_SERVER['REMOTE_ADDR']."'.");
throw new Forbidden("Max failed login attempts exceeded.");
}
}
protected function createToken($user)
protected function generateToken()
{
return md5(uniqid($user->get('id')));
$length = 16;
if (function_exists('random_bytes')) {
return bin2hex(random_bytes($length));
}
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv($length, \MCRYPT_DEV_URANDOM));
}
if (function_exists('openssl_random_pseudo_bytes')) {
return bin2hex(openssl_random_pseudo_bytes($length));
}
}
public function destroyAuthToken($token)
{
$authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $token))->findOne();
$authToken = $this->getEntityManager()->getRepository('AuthToken')->select(['id', 'isActive'])->where(['token' => $token])->findOne();
if ($authToken) {
$authToken->set('isActive', false);
$this->getEntityManager()->saveEntity($authToken);
return true;
}
}
}
protected function createAuthLogRecord($username, $user)
{
if ($username === '**logout') return;
$authLogRecord = $this->getEntityManager()->getEntity('AuthLogRecord');
$authLogRecord->set([
'username' => $username,
'ipAddress' => $_SERVER['REMOTE_ADDR'],
'requestTime' => $_SERVER['REQUEST_TIME_FLOAT'],
'requestMethod' => $this->request->getMethod(),
'requestUrl' => $this->request->getUrl() . $this->request->getPath()
]);
if ($this->isPortal()) {
$authLogRecord->set('portalId', $this->getPortal()->id);
}
if ($user) {
$authLogRecord->set('userId', $user->id);
} else {
$authLogRecord->set('isDenied', true);
$authLogRecord->set('denialReason', 'CREDENTIALS');
$this->getEntityManager()->saveEntity($authLogRecord);
}
return $authLogRecord;
}
protected function logDenied($authLogRecord, $denialReason)
{
if (!$authLogRecord) return;
$authLogRecord->set('denialReason', $denialReason);
$this->getEntityManager()->saveEntity($authLogRecord);
}
}

View File

@@ -108,7 +108,7 @@ class LDAP extends Base
$ldapClient = $this->getLdapClient();
//login LDAP system user (ldapUsername, ldapPassword)
/* Login LDAP system user (ldapUsername, ldapPassword) */
try {
$ldapClient->bind();
} catch (\Exception $e) {
@@ -119,17 +119,30 @@ class LDAP extends Base
if (!isset($adminUser)) {
return null;
}
$GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.');
}
if (!isset($adminUser)) {
$userDn = $this->findLdapUserDnByUsername($username);
$GLOBALS['log']->debug('Found DN for ['.$username.']: ['.$userDn.'].');
try {
$userDn = $this->findLdapUserDnByUsername($username);
} catch (\Exception $e) {
$GLOBALS['log']->error('Error while finding DN for ['.$username.'], details: ' . $e->getMessage() . '.');
}
if (!isset($userDn)) {
$GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: user is not found.');
return;
$adminUser = $this->adminLogin($username, $password);
if (!isset($adminUser)) {
return null;
}
$GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.');
}
$GLOBALS['log']->debug('User ['.$username.'] is found with this DN ['.$userDn.'].');
try {
$ldapClient->bind($userDn, $password);
} catch (\Exception $e) {
@@ -179,7 +192,7 @@ class LDAP extends Base
$user = $this->getEntityManager()->getRepository('User')->findOne(array(
'whereClause' => array(
'userName' => $username,
),
)
));
return $user;
@@ -201,7 +214,7 @@ class LDAP extends Base
'userName' => $username,
'password' => $hash,
'isAdmin' => 1
),
)
));
return $user;
@@ -238,6 +251,8 @@ class LDAP extends Base
$data[$fieldName] = $fieldValue;
}
$this->getAuth()->useNoAuth();
$user = $this->getEntityManager()->getEntity('User');
$user->set($data);

View File

@@ -109,8 +109,5 @@ class ClientManager
}
echo $html;
exit;
}
}

View File

@@ -29,8 +29,8 @@
namespace Espo\Core\Utils\Database;
use Espo\Core\Utils\Util,
Espo\ORM\Entity;
use Espo\Core\Utils\Util;
use Espo\ORM\Entity;
class Converter
{
@@ -38,15 +38,18 @@ class Converter
private $fileManager;
private $config;
private $schemaConverter;
private $schemaFromMetadata = null;
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager)
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config = null)
{
$this->metadata = $metadata;
$this->fileManager = $fileManager;
$this->ormConverter = new Orm\Converter($this->metadata, $this->fileManager);
$this->config = $config;
$this->ormConverter = new Orm\Converter($this->metadata, $this->fileManager, $this->config);
}
protected function getMetadata()

View File

@@ -25,16 +25,19 @@
*
* 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\Driver\PDOMySql;
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
{
public function getDatabasePlatform()
{
return new \Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform();
}
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
{
return new \Espo\Core\Utils\Database\DBAL\Schema\MySqlSchemaManager($conn);
}
}

View File

@@ -315,12 +315,142 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
$options['collate'] = 'utf8mb4_unicode_ci';
}
return parent::_getCreateTableSQL($tableName, $columns, $options);
$queryFields = $this->getColumnDeclarationListSQL($columns);
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
foreach ($options['uniqueConstraints'] as $index => $definition) {
$queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition);
}
}
// add all indexes
if (isset($options['indexes']) && ! empty($options['indexes'])) {
foreach($options['indexes'] as $index => $definition) {
$queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
}
}
// attach all primary keys
if (isset($options['primary']) && ! empty($options['primary'])) {
$keyColumns = array_unique(array_values($options['primary']));
$queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
}
$query = 'CREATE ';
if (!empty($options['temporary'])) {
$query .= 'TEMPORARY ';
}
$query .= 'TABLE ' . $this->espoQuote($tableName) . ' (' . $queryFields . ') ';
$query .= $this->buildTableOptions($options);
$query .= $this->buildPartitionOptions($options);
$sql[] = $query;
if (isset($options['foreignKeys'])) {
foreach ((array) $options['foreignKeys'] as $definition) {
$sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
}
}
return $sql;
}
public function getColumnCollationDeclarationSQL($collation)
{
return $this->getCollationFieldDeclaration($collation);
}
/**
* Build SQL for table options
*
* @param array $options
*
* @return string
*/
private function buildTableOptions(array $options)
{
if (isset($options['table_options'])) {
return $options['table_options'];
}
$tableOptions = array();
// Charset
if ( ! isset($options['charset'])) {
$options['charset'] = 'utf8';
}
$tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
// Collate
if ( ! isset($options['collate'])) {
$options['collate'] = 'utf8_unicode_ci';
}
$tableOptions[] = sprintf('COLLATE %s', $options['collate']);
// Engine
if ( ! isset($options['engine'])) {
$options['engine'] = 'InnoDB';
}
$tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
// Auto increment
if (isset($options['auto_increment'])) {
$tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
}
// Comment
if (isset($options['comment'])) {
$comment = trim($options['comment'], " '");
$tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment));
}
// Row format
if (isset($options['row_format'])) {
$tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
}
return implode(' ', $tableOptions);
}
/**
* Build SQL for partition options.
*
* @param array $options
*
* @return string
*/
private function buildPartitionOptions(array $options)
{
return (isset($options['partition_options']))
? ' ' . $options['partition_options']
: '';
}
public function getClobTypeDeclarationSQL(array $field)
{
if ( ! empty($field['length']) && is_numeric($field['length'])) {
$length = $field['length'];
if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
return 'TINYTEXT';
}
if ($length <= static::LENGTH_LIMIT_TEXT) {
return 'TEXT';
}
if ($length > static::LENGTH_LIMIT_MEDIUMTEXT) {
return 'LONGTEXT';
}
}
return 'MEDIUMTEXT';
}
//end: ESPO
}

View File

@@ -0,0 +1,82 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Schema;
use Doctrine\DBAL\Schema\Index as DBALIndex;
class Index extends \Doctrine\DBAL\Schema\Index
{
public function addFlag($flag)
{
$this->_flags[strtolower($flag)] = true;
return $this;
}
public function hasFlag($flag)
{
return isset($this->_flags[strtolower($flag)]);
}
public function removeFlag($flag)
{
unset($this->_flags[strtolower($flag)]);
}
public function isFullfilledBy(DBALIndex $other)
{
if (count($other->getColumns()) != count($this->getColumns())) {
return false;
}
$sameColumns = $this->spansColumns($other->getColumns());
if ($sameColumns) {
$flags = $this->getFlags();
$otherFlags = $other->getFlags();
if ( ! $this->isUnique() && !$this->isPrimary() && $flags === $otherFlags) {
return true;
} else if ($other->isPrimary() != $this->isPrimary()) {
return false;
} else if ($other->isUnique() != $this->isUnique()) {
return false;
}
if (count($flags) != count($otherFlags) || array_diff($flags, $otherFlags) !== array_diff($otherFlags, $flags)) {
return false;
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,144 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Schema;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
{
public function createSchema()
{
$sequences = array();
if ($this->_platform->supportsSequences()) {
$sequences = $this->listSequences();
}
$tables = $this->listTables();
return new Schema($tables, $sequences, $this->createSchemaConfig());
}
public function listTables()
{
$tableNames = $this->listTableNames();
$tables = array();
foreach ($tableNames as $tableName) {
$tables[] = $this->listTableDetails($tableName);
}
return $tables;
}
public function listTableDetails($tableName)
{
$columns = $this->listTableColumns($tableName);
$foreignKeys = array();
if ($this->_platform->supportsForeignKeyConstraints()) {
$foreignKeys = $this->listTableForeignKeys($tableName);
}
$indexes = $this->listTableIndexes($tableName);
return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
}
public function listTableIndexes($table)
{
$sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
$tableIndexes = $this->_conn->fetchAll($sql);
return $this->_getPortableTableIndexesList($tableIndexes, $table);
}
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
{
foreach($tableIndexes as $k => $v) {
$v = array_change_key_case($v, CASE_LOWER);
if($v['key_name'] == 'PRIMARY') {
$v['primary'] = true;
} else {
$v['primary'] = false;
}
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
$v['flags'] = array('FULLTEXT');
}
$tableIndexes[$k] = $v;
}
$result = array();
foreach($tableIndexes as $tableIndex) {
$indexName = $keyName = $tableIndex['key_name'];
if ($tableIndex['primary']) {
$keyName = 'primary';
}
$keyName = strtolower($keyName);
if (!isset($result[$keyName])) {
$result[$keyName] = array(
'name' => $indexName,
'columns' => array($tableIndex['column_name']),
'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
);
} else {
$result[$keyName]['columns'][] = $tableIndex['column_name'];
}
}
$eventManager = $this->_platform->getEventManager();
$indexes = array();
foreach($result as $indexKey => $data) {
$index = null;
$defaultPrevented = false;
if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
$eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
$eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
$defaultPrevented = $eventArgs->isDefaultPrevented();
$index = $eventArgs->getIndex();
}
if ( ! $defaultPrevented) {
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']);
}
if ($index) {
$indexes[$indexKey] = $index;
}
}
return $indexes;
}
}

View File

@@ -28,9 +28,9 @@
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
class Schema extends \Doctrine\DBAL\Schema\Schema
{
/**
* Creates a new table.
*

View File

@@ -28,11 +28,12 @@
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Schema\SchemaException;
class Table extends \Doctrine\DBAL\Schema\Table
{
/**
* @param string $columnName
* @param string $typeName
@@ -49,4 +50,35 @@ class Table extends \Doctrine\DBAL\Schema\Table
return $column;
}
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
{
if($indexName == null) {
$indexName = $this->_generateIdentifierName(
array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
);
}
return $this->_createIndex($columnNames, $indexName, false, false, $flags);
}
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
{
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if ( ! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
$this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags));
return $this;
}
}

View File

@@ -0,0 +1,188 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database;
use Espo\Core\Utils\Util;
use Espo\ORM\Entity;
class Helper
{
private $config;
private $connection;
protected $drivers = array(
'mysqli' => '\Espo\Core\Utils\Database\DBAL\Driver\Mysqli\Driver',
'pdo_mysql' => '\Espo\Core\Utils\Database\DBAL\Driver\PDOMySql\Driver',
);
public function __construct(\Espo\Core\Utils\Config $config = null)
{
$this->config = $config;
}
protected function getConfig()
{
return $this->config;
}
public function getDbalConnection()
{
if (!isset($this->connection)) {
if (!$this->getConfig()) {
return null;
}
$connectionParams = $this->getConfig()->get('database');
if (empty($connectionParams['dbname']) || empty($connectionParams['user'])) {
return null;
}
$connectionParams['driverClass'] = $this->drivers[ $connectionParams['driver'] ];
unset($connectionParams['driver']);
$dbalConfig = new \Doctrine\DBAL\Configuration();
$this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $dbalConfig);
}
return $this->connection;
}
/**
* Get maximum index length. If $tableName is empty get a value for all database tables
*
* @param string|null $tableName
*
* @return int
*/
public function getMaxIndexLength($tableName = null, $default = 1000)
{
$mysqlEngine = $this->getMysqlEngine($tableName);
if (!$mysqlEngine) {
return $default;
}
switch ($mysqlEngine) {
case 'InnoDB':
$mysqlVersion = $this->getMysqlVersion();
if (version_compare($mysqlVersion, '10.0.0') >= 0) {
return 767; //InnoDB, MariaDB
}
if (version_compare($mysqlVersion, '5.7.0') >= 0) {
return 3072; //InnoDB, MySQL 5.7+
}
return 767; //InnoDB
break;
}
return 1000; //MyISAM
}
public function getTableMaxIndexLength($tableName, $default = 1000)
{
return $this->getMaxIndexLength($tableName, $default);
}
protected function getMysqlVersion()
{
$connection = $this->getDbalConnection();
if (!$connection) {
return null;
}
return $connection->fetchColumn("select version()");
}
/**
* Get table/database tables engine. If $tableName is empty get a value for all database tables
*
* @param string|null $tableName
*
* @return string
*/
protected function getMysqlEngine($tableName = null, $default = null)
{
$connection = $this->getDbalConnection();
if (!$connection) {
return $default;
}
$query = "SHOW TABLE STATUS WHERE Engine = 'MyISAM'";
if (!empty($tableName)) {
$query = "SHOW TABLE STATUS WHERE Engine = 'MyISAM' AND Name = '" . $tableName . "'";
}
$result = $connection->fetchColumn($query);
if (!empty($result)) {
return 'MyISAM';
}
return 'InnoDB';
}
/**
* Check if full text supports. If $tableName is empty get a value for all database tables
*
* @param string $tableName
*
* @return boolean
*/
public function isSupportsFulltext($tableName = null, $default = false)
{
$mysqlEngine = $this->getMysqlEngine($tableName);
if (!$mysqlEngine) {
return $default;
}
switch ($mysqlEngine) {
case 'InnoDB':
$mysqlVersion = $this->getMysqlVersion();
if (version_compare($mysqlVersion, '5.6.4') >= 0) {
return true; //InnoDB, MySQL 5.6.4+
}
return false; //InnoDB
break;
}
return true; //MyISAM
}
public function isTableSupportsFulltext($tableName, $default = false)
{
return $this->isSupportsFulltext($tableName, $default);
}
}

View File

@@ -28,20 +28,28 @@
************************************************************************/
namespace Espo\Core\Utils\Database\Orm;
use Espo\Core\Utils\Util,
Espo\ORM\Entity;
use Espo\Core\Utils\Util;
use Espo\ORM\Entity;
class Converter
{
private $metadata;
private $fileManager;
private $config;
private $metadataHelper;
private $databaseHelper;
private $relationManager;
private $entityDefs;
protected $defaultFieldType = 'varchar';
protected $defaultNaming = 'postfix';
protected $defaultLength = array(
@@ -62,6 +70,7 @@ class Converter
'maxLength' => 'len',
'len' => 'len',
'notNull' => 'notNull',
'exportDisabled' => 'notExportable',
'autoincrement' => 'autoincrement',
'entity' => 'entity',
'notStorable' => 'notStorable',
@@ -98,14 +107,16 @@ class Converter
'additionalTables',
);
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager)
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config = null)
{
$this->metadata = $metadata;
$this->fileManager = $fileManager; //need to featue with ormHooks. Ex. isFollowed field
$this->config = $config;
$this->relationManager = new RelationManager($this->metadata);
$this->metadataHelper = new \Espo\Core\Utils\Metadata\Helper($this->metadata);
$this->databaseHelper = new \Espo\Core\Utils\Database\Helper($this->config);
}
protected function getMetadata()
@@ -113,6 +124,11 @@ class Converter
return $this->metadata;
}
protected function getConfig()
{
return $this->config;
}
protected function getEntityDefs($reload = false)
{
if (empty($this->entityDefs) || $reload) {
@@ -137,6 +153,11 @@ class Converter
return $this->metadataHelper;
}
protected function getDatabaseHelper()
{
return $this->databaseHelper;
}
/**
* Orm metadata convertation process
*
@@ -185,6 +206,8 @@ class Converter
$ormMetadata = Util::merge($ormMetadata, $convertedLinks);
$this->applyFullTextSearch($ormMetadata, $entityName);
if (!empty($entityMetadata['collection']) && is_array($entityMetadata['collection'])) {
$collectionDefs = $entityMetadata['collection'];
$ormMetadata[$entityName]['collection'] = array();
@@ -466,4 +489,47 @@ class Converter
return $values;
}
protected function applyFullTextSearch(&$ormMetadata, $entityType)
{
if (!$this->getDatabaseHelper()->isTableSupportsFulltext(Util::toUnderScore($entityType))) return;
if (!$this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'fullTextSearch'])) return;
$fieldList = $this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'textFilterFields'], ['name']);
$fullTextSearchColumnList = [];
foreach ($fieldList as $field) {
$defs = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field], []);
if (empty($defs['type'])) continue;
$fieldType = $defs['type'];
if (!empty($defs['notStorable'])) continue;
if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue;
$partList = $this->getMetadata()->get(['fields', $fieldType, 'fullTextSearchColumnList']);
if ($partList) {
if ($this->getMetadata()->get(['fields', $fieldType, 'naming']) === 'prefix') {
foreach ($partList as $part) {
$fullTextSearchColumnList[] = $part . ucfirst($field);
}
} else {
foreach ($partList as $part) {
$fullTextSearchColumnList[] = $field . ucfirst($part);
}
}
} else {
$fullTextSearchColumnList[] = $field;
}
}
if (!empty($fullTextSearchColumnList)) {
$ormMetadata[$entityType]['fullTextSearchColumnList'] = $fullTextSearchColumnList;
if (!array_key_exists('indexes', $ormMetadata[$entityType])) {
$ormMetadata[$entityType]['indexes'] = [];
}
$ormMetadata[$entityType]['indexes']['system_fullTextSearch'] = [
'columns' => $fullTextSearchColumnList,
'flags' => ['fulltext']
];
}
}
}

View File

@@ -37,20 +37,24 @@ class AttachmentMultiple extends Base
$entityType => array (
'fields' => array(
$fieldName.'Ids' => array(
'type' => 'varchar',
'notStorable' => true
'type' => 'jsonArray',
'notStorable' => true,
'orderBy' => [['createdAt', 'ASC'], ['name', 'ASC']],
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'type' => 'varchar',
'notStorable' => true
),
'type' => 'jsonObject',
'notStorable' => true,
'isLinkMultipleNameMap' => true
)
)
),
'unset' => array(
$entityType => array(
'fields.'.$fieldName,
),
),
)
)
);
return $data;

View File

@@ -46,45 +46,50 @@ class Email extends Base
JOIN email_address ON email_address.id = entity_email_address.email_address_id
WHERE
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
email_address.deleted = 0 AND email_address.name LIKE {value}
email_address.deleted = 0 AND email_address.lower LIKE {value}
)",
'=' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name = {value}',
'sql' => 'emailAddressesMultiple.lower = {value}',
'distinct' => true
),
'<>' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name <> {value}',
'sql' => 'emailAddressesMultiple.lower <> {value}',
'distinct' => true
),
'IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IN {value}',
'sql' => 'emailAddressesMultiple.lower IN {value}',
'distinct' => true
),
'NOT IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name NOT IN {value}',
'sql' => 'emailAddressesMultiple.lower NOT IN {value}',
'distinct' => true
),
'IS NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NULL',
'sql' => 'emailAddressesMultiple.lower IS NULL',
'distinct' => true
),
'IS NOT NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NOT NULL',
'sql' => 'emailAddressesMultiple.lower IS NOT NULL',
'distinct' => true
)
),
'orderBy' => 'emailAddresses.name {direction}',
'orderBy' => 'emailAddresses.lower {direction}',
),
$fieldName .'Data' => array(
'type' => 'text',
'notStorable' => true
),
$fieldName .'IsOptedOut' => array(
'type' => 'bool',
'notStorable' => true,
'select' => 'emailAddresses.opt_out'
)
),
'relations' => array(
'emailAddresses' => array(

View File

@@ -33,27 +33,29 @@ class LinkMultiple extends Base
{
protected function load($fieldName, $entityName)
{
$data = array(
$entityName => array (
'fields' => array(
$fieldName.'Ids' => array(
'type' => 'varchar',
$data = [
$entityName => [
'fields' => [
$fieldName.'Ids' => [
'type' => 'jsonArray',
'notStorable' => true,
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'type' => 'varchar',
'notStorable' => true
)
)
),
'unset' => array(
$entityName => array(
'fields.'.$fieldName
)
)
);
'relation' => $fieldName,
'isUnordered' => true
],
$fieldName.'Names' => [
'type' => 'jsonObject',
'notStorable' => true,
'isLinkMultipleNameMap' => true
]
]
],
'unset' => [
$entityName => [
'fields.' . $fieldName
]
]
];
$fieldParams = $this->getFieldParams();
@@ -66,10 +68,10 @@ class LinkMultiple extends Base
$columns = $this->getMetadata()->get("entityDefs.{$entityName}.fields.{$fieldName}.columns");
if (!empty($columns)) {
$data[$entityName]['fields'][$fieldName . 'Columns'] = array(
'type' => 'varchar',
$data[$entityName]['fields'][$fieldName . 'Columns'] = [
'type' => 'jsonObject',
'notStorable' => true,
);
];
}
return $data;

View File

@@ -43,11 +43,11 @@ class HasMany extends Base
$entityName => array (
'fields' => array(
$linkName.'Ids' => array(
'type' => 'varchar',
'type' => 'jsonArray',
'notStorable' => true,
),
$linkName.'Names' => array(
'type' => 'varchar',
'type' => 'jsonObject',
'notStorable' => true,
),
),

View File

@@ -50,11 +50,11 @@ class ManyMany extends Base
$entityName => array(
'fields' => array(
$linkName.'Ids' => array(
'type' => 'varchar',
'type' => 'jsonArray',
'notStorable' => true,
),
$linkName.'Names' => array(
'type' => 'varchar',
'type' => 'jsonObject',
'notStorable' => true,
),
),

View File

@@ -79,11 +79,14 @@ class Converter
'foreign'
);
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Database\Schema\Schema $databaseSchema)
protected $maxIndexLength;
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Database\Schema\Schema $databaseSchema, \Espo\Core\Utils\Config $config = null)
{
$this->metadata = $metadata;
$this->fileManager = $fileManager;
$this->databaseSchema = $databaseSchema;
$this->config = $config;
$this->typeList = array_keys(\Doctrine\DBAL\Types\Type::getTypesMap());
}
@@ -98,6 +101,11 @@ class Converter
return $this->fileManager;
}
protected function getConfig()
{
return $this->config;
}
/**
* Get schema
*
@@ -119,6 +127,15 @@ class Converter
return $this->databaseSchema;
}
protected function getMaxIndexLength()
{
if (!isset($this->maxIndexLength)) {
$this->maxIndexLength = $this->getDatabaseSchema()->getDatabaseHelper()->getMaxIndexLength();
}
return $this->maxIndexLength;
}
/**
* Schema convertation process
*
@@ -164,7 +181,7 @@ class Converter
$schema = $this->getSchema(true);
$indexList = SchemaUtils::getIndexList($ormMeta);
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getDatabaseSchema()->getMaxIndexLength());
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getMaxIndexLength());
$tables = array();
foreach ($ormMeta as $entityName => $entityParams) {
@@ -227,8 +244,10 @@ class Converter
$tables[$entityName]->setPrimaryKey($primaryColumns);
if (!empty($indexList[$entityName])) {
foreach($indexList[$entityName] as $indexName => $indexColumnList) {
$tables[$entityName]->addIndex($indexColumnList, $indexName);
foreach($indexList[$entityName] as $indexName => $indexParams) {
$indexColumnList = $indexParams['columns'];
$indexFlagList = isset($indexParams['flags']) ? $indexParams['flags'] : array();
$tables[$entityName]->addIndex($indexColumnList, $indexName, $indexFlagList);
}
}
@@ -358,7 +377,9 @@ class Converter
case 'id':
case 'foreignId':
case 'foreignType':
$fieldParams['utf8mb3'] = true;
if ($this->getMaxIndexLength() < 3072) {
$fieldParams['utf8mb3'] = true;
}
break;
case 'array':

View File

@@ -48,12 +48,7 @@ class Schema
private $converter;
private $connection;
protected $drivers = array(
'mysqli' => '\Espo\Core\Utils\Database\DBAL\Driver\Mysqli\Driver',
'pdo_mysql' => '\Espo\Core\Utils\Database\DBAL\Driver\PDOMySql\Driver',
);
private $databaseHelper;
protected $fieldTypePaths = array(
'application/Espo/Core/Utils/Database/DBAL/FieldTypes',
@@ -79,8 +74,6 @@ class Schema
*/
protected $rebuildActionClasses = null;
public function __construct(\Espo\Core\Utils\Config $config, \Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\ORM\EntityManager $entityManager, \Espo\Core\Utils\File\ClassParser $classParser, \Espo\Core\Utils\Metadata\OrmMetadata $ormMetadata)
{
$this->config = $config;
@@ -89,17 +82,17 @@ class Schema
$this->entityManager = $entityManager;
$this->classParser = $classParser;
$this->databaseHelper = new \Espo\Core\Utils\Database\Helper($this->config);
$this->comparator = new \Espo\Core\Utils\Database\DBAL\Schema\Comparator();
$this->initFieldTypes();
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager);
$this->schemaConverter = new Converter($this->metadata, $this->fileManager, $this);
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager, $this->config);
$this->schemaConverter = new Converter($this->metadata, $this->fileManager, $this, $this->config);
$this->ormMetadata = $ormMetadata;
}
protected function getConfig()
{
return $this->config;
@@ -140,26 +133,16 @@ class Schema
return $this->getConnection()->getDatabasePlatform();
}
public function getDatabaseHelper()
{
return $this->databaseHelper;
}
public function getConnection()
{
if (isset($this->connection)) {
return $this->connection;
}
$dbalConfig = new \Doctrine\DBAL\Configuration();
$connectionParams = $this->getConfig()->get('database');
$connectionParams['driverClass'] = $this->drivers[ $connectionParams['driver'] ];
unset($connectionParams['driver']);
$this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $dbalConfig);
return $this->connection;
return $this->getDatabaseHelper()->getDbalConnection();
}
protected function initFieldTypes()
{
foreach($this->fieldTypePaths as $path) {
@@ -187,8 +170,6 @@ class Schema
}
}
/*
* Rebuild database schema
*/
@@ -224,7 +205,6 @@ class Schema
return (bool) $result;
}
/*
* Get current database schema
*
@@ -248,7 +228,6 @@ class Schema
//return $schema->toSql($this->getPlatform()); //it can return with DROP TABLE
}
/*
* Get SQL queries to get from one to another schema
*
@@ -261,8 +240,6 @@ class Schema
return $this->toSql($schemaDiff); //$schemaDiff->toSql($this->getPlatform());
}
/**
* Init Rebuild Actions, get all classes and create them
* @return void
@@ -311,46 +288,4 @@ class Schema
}
}
}
public function getMaxIndexLength()
{
$connection = $this->getConnection();
$mysqlEngine = $this->getMysqlEngine();
switch ($mysqlEngine) {
case 'InnoDB':
$mysqlVersion = $this->getMysqlVersion();
if (version_compare($mysqlVersion, '10.0.0') >= 0) {
return 767; //InnoDB, MariaDB
}
if (version_compare($mysqlVersion, '5.7.0') >= 0) {
return 3072; //InnoDB, MySQL 5.7+
}
return 767; //InnoDB
break;
}
return 1000; //MyISAM
}
protected function getMysqlVersion()
{
$connection = $this->getConnection();
return $connection->fetchColumn("select version()");
}
protected function getMysqlEngine()
{
$connection = $this->getConnection();
$result = $connection->fetchColumn("SHOW TABLE STATUS WHERE Engine = 'MyISAM'");
if (!empty($result)) {
return 'MyISAM';
}
return 'InnoDB';
}
}

View File

@@ -33,7 +33,7 @@ use Espo\Core\Utils\Util;
class Utils
{
public static function getIndexList(array $ormMeta)
public static function getIndexList(array $ormMeta, array $ignoreFlags = [])
{
$indexList = array();
@@ -53,19 +53,37 @@ class Utils
if ($keyValue === true) {
$tableIndexName = static::generateIndexName($columnName);
$indexList[$entityName][$tableIndexName] = array($columnName);
$indexList[$entityName][$tableIndexName]['columns'] = array($columnName);
} else if (is_string($keyValue)) {
$tableIndexName = static::generateIndexName($keyValue);
$indexList[$entityName][$tableIndexName][] = $columnName;
$indexList[$entityName][$tableIndexName]['columns'][] = $columnName;
}
}
}
if (isset($entityParams['indexes']) && is_array($entityParams['indexes'])) {
foreach ($entityParams['indexes'] as $indexName => $indexParams) {
$tableIndexName = static::generateIndexName($indexName);
if (isset($indexParams['flags']) && is_array($indexParams['flags'])) {
$skipIndex = false;
foreach ($ignoreFlags as $ignoreFlag) {
if (($flagKey = array_search($ignoreFlag, $indexParams['flags'])) !== false) {
unset($indexParams['flags'][$flagKey]);
$skipIndex = true;
}
}
if ($skipIndex && empty($indexParams['flags'])) {
continue;
}
$indexList[$entityName][$tableIndexName]['flags'] = $indexParams['flags'];
}
if (is_array($indexParams['columns'])) {
$tableIndexName = static::generateIndexName($indexName);
$indexList[$entityName][$tableIndexName] = Util::toUnderScore($indexParams['columns']);
$indexList[$entityName][$tableIndexName]['columns'] = Util::toUnderScore($indexParams['columns']);
}
}
}
@@ -83,7 +101,7 @@ class Utils
return substr(implode('_', $nameList), 0, $maxLength);
}
public static function getFieldListExceededIndexMaxLength(array $ormMeta, $indexMaxLength = 1000, $characterLength = 4)
public static function getFieldListExceededIndexMaxLength(array $ormMeta, $indexMaxLength = 1000, array $indexList = null, $characterLength = 4)
{
$permittedFieldTypeList = [
'varchar',
@@ -91,10 +109,14 @@ class Utils
$fields = array();
$indexList = static::getIndexList($ormMeta);
if (!isset($indexList)) {
$indexList = static::getIndexList($ormMeta, ['fulltext']);
}
foreach ($indexList as $entityName => $indexes) {
foreach ($indexes as $indexName => $columnList) {
foreach ($indexes as $indexName => $indexParams) {
$columnList = $indexParams['columns'];
$indexLength = 0;
foreach ($columnList as $columnName) {
$fieldName = Util::toCamelCase($columnName);

View File

@@ -0,0 +1,80 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Schema\rebuildActions;
class FulltextIndex extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
{
public function beforeRebuild()
{
$currentSchema = $this->getCurrentSchema();
$tables = $currentSchema->getTables();
if (empty($tables)) return;
$databaseHelper = new \Espo\Core\Utils\Database\Helper($this->getConfig());
$connection = $databaseHelper->getDbalConnection();
$metadataSchema = $this->getMetadataSchema();
$tables = $metadataSchema->getTables();
foreach ($tables as $table) {
$tableName = $table->getName();
$indexes = $table->getIndexes();
foreach ($indexes as $index) {
if (!$index->hasFlag('fulltext')) {
continue;
}
$columns = $index->getColumns();
foreach ($columns as $columnName) {
$query = "SHOW FULL COLUMNS FROM `". $tableName ."` WHERE Field = '" . $columnName . "'";
try {
$row = $connection->fetchAssoc($query);
} catch (\Exception $e) {
continue;
}
switch (strtoupper($row['Type'])) {
case 'LONGTEXT':
$alterQuery = "ALTER TABLE `". $tableName ."` MODIFY `". $columnName ."` MEDIUMTEXT COLLATE ". $row['Collation'] ."";
$GLOBALS['log']->info('SCHEMA, Execute Query: ' . $alterQuery);
$connection->executeQuery($alterQuery);
break;
}
}
}
}
}
}

View File

@@ -189,4 +189,38 @@ class DateTime
{
return date($this->getInternalDateFormat());
}
public function getTodayString($timezone = null)
{
if ($timezone) {
$timezoneObj = new \DateTimeZone($timezone);
} else {
$timezoneObj = $this->timezone;
}
$dateTime = new \DateTime();
$dateTime->setTimezone($timezoneObj);
return $dateTime->format($this->getPhpDateFormat());
}
public function getNowString($timezone = null, $format = null)
{
if ($timezone) {
$timezoneObj = new \DateTimeZone($timezone);
} else {
$timezoneObj = $this->timezone;
}
$dateTime = new \DateTime();
$dateTime->setTimezone($timezoneObj);
if ($format) {
$phpFormat = $this->convertFormatToPhp($format);
} else {
$phpFormat = $this->getPhpDateTimeFormat();
}
return $dateTime->format($phpFormat);
}
}

View File

@@ -128,6 +128,28 @@ class EntityManager
return false;
}
protected function checkRelationshipExists($name)
{
$name = ucfirst($name);
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
foreach ($scopeList as $entityType) {
$relationsDefs = $this->getEntityManager()->getMetadata()->get($entityType, 'relations');
if (empty($relationsDefs)) continue;
foreach ($relationsDefs as $link => $item) {
if (empty($item['type'])) continue;
if (empty($item['relationName'])) continue;
if ($item['type'] === 'manyMany') {
if (ucfirst($item['relationName']) === $name) {
return true;
}
}
}
}
return false;
}
public function create($name, $type, $params = [], $replaceData = [])
{
$name = ucfirst($name);
@@ -172,6 +194,10 @@ class EntityManager
throw new Conflict('Entity name \''.$name.'\' is not allowed.');
}
if ($this->checkRelationshipExists($name)) {
throw new Conflict('Relationship with the same name \''.$name.'\' exists.');
}
$normalizedName = Util::normilizeClassName($name);
$templateNamespace = "\Espo\Core\Templates";
@@ -283,6 +309,10 @@ class EntityManager
$scopesData['isNotRemovable'] = true;
}
if (!empty($params['kanbanStatusIgnoreList'])) {
$scopesData['kanbanStatusIgnoreList'] = $params['kanbanStatusIgnoreList'];
}
$this->getMetadata()->set('scopes', $name, $scopesData);
$filePath = $templatePath . "/Metadata/{$type}/entityDefs.json";
@@ -302,6 +332,18 @@ class EntityManager
$clientDefsContents = str_replace('{'.$key.'}', $value, $clientDefsContents);
}
$clientDefsData = Json::decode($clientDefsContents, true);
if (array_key_exists('color', $params)) {
$clientDefsData['color'] = $params['color'];
}
if (array_key_exists('iconClass', $params)) {
$clientDefsData['iconClass'] = $params['iconClass'];
}
if (!empty($params['kanbanViewMode'])) {
$clientDefsData['kanbanViewMode'] = true;
}
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
$this->getBaseLanguage()->set('Global', 'scopeNames', $name, $labelSingular);
@@ -387,6 +429,42 @@ class EntityManager
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
if (isset($data['fullTextSearch'])) {
$entityDefsData = [
'collection' => [
'fullTextSearch' => !!$data['fullTextSearch']
]
];
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
if (array_key_exists('kanbanStatusIgnoreList', $data)) {
$scopeData['kanbanStatusIgnoreList'] = $data['kanbanStatusIgnoreList'];
$this->getMetadata()->set('scopes', $name, $scopeData);
}
if (array_key_exists('kanbanViewMode', $data)) {
$clientDefsData = [
'kanbanViewMode' => $data['kanbanViewMode']
];
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
}
if (array_key_exists('color', $data)) {
$clientDefsData = [
'color' => $data['color']
];
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
}
if (array_key_exists('iconClass', $data)) {
$clientDefsData = [
'iconClass' => $data['iconClass']
];
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
}
$this->getMetadata()->save();
$this->getLanguage()->save();
if ($isCustom) {
@@ -504,6 +582,12 @@ class EntityManager
} else {
$relationName = lcfirst($entity) . $entityForeign;
}
if ($this->getMetadata()->get(['scopes', ucfirst($relationName)])) {
throw new Conflict("Entity with the same name '{$relationName}' exists.");
}
if ($this->checkRelationshipExists($relationName)) {
throw new Conflict("Relationship with the same name '{$relationName}' exists.");
}
}
if (empty($link) || empty($linkForeign)) {
@@ -578,7 +662,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -643,7 +726,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
@@ -667,7 +749,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
@@ -690,7 +771,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
@@ -764,7 +844,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -789,7 +868,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
@@ -948,4 +1026,33 @@ class EntityManager
}
return;
}
public function resetToDefaults($scope)
{
if ($this->isCustom($scope)) {
throw new Error("Can't reset to defaults custom entity type '{$scope}.'");
}
$this->getMetadata()->delete('scopes', $scope, [
'disabled',
'stream',
'statusField',
'kanbanStatusIgnoreList'
]);
$this->getMetadata()->delete('clientDefs', $scope, [
'iconClass',
'statusField',
'kanbanViewMode'
]);
$this->getMetadata()->delete('entityDefs', $scope, [
'collection.sortBy',
'collection.asc',
'collection.textFilterFields'
]);
$this->getMetadata()->save();
$this->getLanguage()->delete('Global', 'scopeNames', $scope);
$this->getLanguage()->delete('Global', 'scopeNamesPlural', $scope);
$this->getLanguage()->save();
}
}

View File

@@ -66,7 +66,11 @@ class FieldManagerUtil
}
if ($naming == 'prefix') {
foreach ($list as $f) {
$fieldList[] = $f . ucfirst($name);
if ($f === '') {
$fieldList[] = $name;
} else {
$fieldList[] = $f . ucfirst($name);
}
}
} else {
foreach ($list as $f) {

View File

@@ -358,6 +358,14 @@ class Metadata
*/
public function saveCustom($key1, $key2, $data)
{
if (is_object($data)) {
foreach ($data as $key => $item) {
if ($item == new \stdClass()) {
unset($data->$key);
}
}
}
$filePath = array($this->paths['customPath'], $key1, $key2.'.json');
$changedData = Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
@@ -380,6 +388,14 @@ class Metadata
*/
public function set($key1, $key2, $data)
{
if (is_array($data)) {
foreach ($data as $key => $item) {
if (is_array($item) && empty($item)) {
unset($data[$key]);
}
}
}
$newData = array(
$key1 => array(
$key2 => $data,

View File

@@ -41,19 +41,28 @@ class OrmMetadata
protected $fileManager;
protected $config;
protected $useCache;
public function __construct($metadata, $fileManager, $useCache = false)
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, $config)
{
$this->metadata = $metadata;
$this->fileManager = $fileManager;
$this->useCache = $useCache;
$this->useCache = false;
if ($config instanceof \Espo\Core\Utils\Config) {
$this->config = $config;
$this->useCache = $this->config->get('useCache', false);
} elseif (is_bool($config)) {
$this->useCache = $config;
}
}
protected function getConverter()
{
if (!isset($this->converter)) {
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager);
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager, $this->config);
}
return $this->converter;
@@ -64,6 +73,11 @@ class OrmMetadata
return $this->fileManager;
}
protected function getConfig()
{
return $this->config;
}
public function clearData()
{
$this->ormData = null;

View File

@@ -86,10 +86,12 @@ class Util
return $name;
}
if($capitaliseFirstChar) {
$name[0] = strtoupper($name[0]);
$name = lcfirst($name);
if ($capitaliseFirstChar) {
$name = ucfirst($name);
}
return preg_replace_callback('/'.$symbol.'([a-z])/', 'static::toCamelCaseConversion', $name);
return preg_replace_callback('/'.$symbol.'([a-zA-Z])/', 'static::toCamelCaseConversion', $name);
}
protected static function toCamelCaseConversion($matches)

View File

@@ -70,6 +70,8 @@ return array (
'da_DK',
'de_DE',
'es_ES',
'hr_HR',
'hu_HU',
'fr_FR',
'id_ID',
'it_IT',
@@ -77,6 +79,7 @@ return array (
'nb_NO',
'nl_NL',
'tr_TR',
'sk_SK',
'sr_RS',
'ro_RO',
'ru_RU',
@@ -108,11 +111,13 @@ return array (
'adminNotifications' => true,
'adminNotificationsNewVersion' => true,
'adminNotificationsCronIsNotConfigured' => true,
'adminNotificationsNewExtensionVersion' => true,
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => ['Lead', 'Opportunity', 'Task', 'Case'],
'assignmentNotificationsEntityList' => ['Meeting', 'Call', 'Task', 'Email'],
"portalStreamEmailNotifications" => true,
'streamEmailNotificationsEntityList' => ['Case'],
'streamEmailNotificationsTypeList' => ['Post', 'Status', 'EmailReceived'],
'emailMessageMaxSize' => 10,
'notificationsCheckInterval' => 10,
'disabledCountQueryEntityList' => ['Email'],
@@ -165,6 +170,10 @@ return array (
'aclAllowDeleteCreated' => false,
'inlineAttachmentUploadMaxSize' => 20,
'textFilterUseContainsForVarchar' => false,
'tabColorsDisabled' => false,
'massPrintPdfMaxCount' => 50,
'emailKeepParentTeamsEntityList' => ['Case'],
'recordListMaxSizeLimit' => 200,
'isInstalled' => false
);

View File

@@ -0,0 +1,3 @@
[
{"name":"name", "link": true}
]

View File

@@ -154,7 +154,11 @@ return array ( 'defaultPermissions' =>
'ldapUserTeamsIds',
'ldapUserTeamsNames',
'cleanupJobPeriod',
'cleanupActionHistoryPeriod'
'cleanupActionHistoryPeriod',
'adminNotifications',
'adminNotificationsNewVersion',
'adminNotificationsCronIsNotConfigured',
'adminNotificationsNewExtensionVersion'
),
'userItems' =>
array (

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Entities;
class AuthLogRecord extends \Espo\Core\ORM\Entity
{
}

View File

@@ -72,11 +72,20 @@ class Email extends \Espo\Core\ORM\Entity
}
}
protected function _getBodyPlain()
{
return $this->getBodyPlain();
}
public function hasBodyPlain()
{
return !empty($this->valuesContainer['bodyPlain']);
}
public function getBodyPlain()
{
$bodyPlain = $this->get('bodyPlain');
if (!empty($bodyPlain)) {
return $bodyPlain;
if (!empty($this->valuesContainer['bodyPlain'])) {
return $this->valuesContainer['bodyPlain'];
}
$body = $this->get('body');
@@ -84,6 +93,34 @@ class Email extends \Espo\Core\ORM\Entity
$breaks = array("<br />","<br>","<br/>","<br />","&lt;br /&gt;","&lt;br/&gt;","&lt;br&gt;");
$body = str_ireplace($breaks, "\r\n", $body);
$body = strip_tags($body);
$reList = [
'/&(quot|#34);/i',
'/&(amp|#38);/i',
'/&(lt|#60);/i',
'/&(gt|#62);/i',
'/&(nbsp|#160);/i',
'/&(iexcl|#161);/i',
'/&(cent|#162);/i',
'/&(pound|#163);/i',
'/&(copy|#169);/i',
'/&(reg|#174);/i'
];
$replaceList = [
'',
'&',
'<',
'>',
' ',
chr(161),
chr(162),
chr(163),
chr(169),
chr(174)
];
$body = preg_replace($reList, $replaceList, $body);
return $body;
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://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\Entities;
class EmailTemplateCategory extends \Espo\Core\Templates\Entities\CategoryTree
{
}

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