Compare commits

...

281 Commits
5.2.2 ... 5.3.6

Author SHA1 Message Date
yuri
eadf7835db fix css 2018-08-03 13:16:22 +03:00
yuri
c30c4163a6 increase container max size 2018-08-02 16:21:40 +03:00
yuri
8d6db72516 icons fix 2018-08-02 15:08:32 +03:00
yuri
71864056bc navbar panel height 2018-08-02 15:05:00 +03:00
yuri
e375105a5f Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 13:28:18 +03:00
Taras Machyshyn
e066f4c6b0 Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 13:27:08 +03:00
Taras Machyshyn
b032aa4d8d Upgrades bug fixes 2018-08-02 13:26:46 +03:00
yuri
342a18e0ac fix installer css 2018-08-02 12:59:12 +03:00
yuri
e27e9de701 attachment preview resize 2018-08-02 12:37:16 +03:00
yuri
b14b199d43 preview resize fix 2018-08-02 12:28:14 +03:00
yuri
f77a7c32d5 link multiple preview size param 2018-08-02 12:04:33 +03:00
yuri
9f84d3f233 notifications attachment preview small size 2018-08-02 11:56:00 +03:00
yuri
be82244c6b showing attachments in notifications 2018-08-02 11:30:28 +03:00
yuri
7fed72d391 fix image sized download extension 2018-08-02 10:55:39 +03:00
yuri
54d615b64d fix attachment preview in list 2018-08-02 10:46:16 +03:00
yuri
2dbbd1e23d Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 10:30:55 +03:00
Taras Machyshyn
cba8867389 Bug fixes for MySQL 8 2018-08-01 18:49:40 +03:00
yuri
32d47db15a emailFolderMaxCount 2018-08-01 11:16:50 +03:00
yuri
2c65c9ff9f modal select attributes 2018-08-01 11:13:00 +03:00
yuri
fa5d63253b naming fix 2018-08-01 10:46:48 +03:00
yuri
b7ae252d3d modal forceSelectAllAttributes 2018-08-01 10:24:41 +03:00
yuri
f07b43abda version 2018-07-30 16:05:13 +03:00
yuri
276db37baf required extensions change 2018-07-30 16:03:20 +03:00
yuri
6adad84a29 installer config formatting 2018-07-30 15:52:16 +03:00
yuri
f137298c49 add exif extension 2018-07-30 15:50:17 +03:00
yuri
f8839518d5 avatar check exif read data exists 2018-07-30 15:48:00 +03:00
yuri
1ae39d1a40 fix campaign revenue empty 2018-07-30 10:20:45 +03:00
yuri
94f920324e fix case compose email 2018-07-30 10:14:17 +03:00
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
530 changed files with 14220 additions and 1591 deletions

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

@@ -89,6 +89,9 @@ class EntityManager extends \Espo\Core\Controllers\Base
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'])) {

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

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

@@ -128,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);
}
}
@@ -177,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);
@@ -227,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(
@@ -174,11 +175,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(
@@ -213,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)
@@ -228,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);
}
}
}
@@ -469,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);
}
}

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

@@ -48,6 +48,6 @@ class ContainsType extends \Espo\Core\Formula\Functions\Base
return false;
}
return strpos($string, $needle) !== false;
return mb_strpos($string, $needle) !== false;
}
}

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

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

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

View File

@@ -53,11 +53,6 @@ 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'));
@@ -109,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')) {
@@ -147,7 +137,9 @@ class Entity extends \Espo\ORM\Entity
}
$this->set($idsAttribute, $ids);
$this->setFetched($idsAttribute, $ids);
if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
$this->setFetched($idsAttribute, $ids);
}
$this->set($field . 'Names', $names);
if ($hasType) {
@@ -163,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;
@@ -172,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

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

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

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

@@ -631,7 +631,13 @@ abstract class Base
protected function systemRebuild()
{
return $this->getContainer()->get('dataManager')->rebuild();
try {
return $this->getContainer()->get('dataManager')->rebuild();
} catch (\Exception $e) {
$GLOBALS['log']->error('Database rebuild failure, details: '.$e->getMessage().'.');
}
return false;
}
/**

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

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',

View File

@@ -42,6 +42,9 @@ class Output
500 => 'Internal Server Error',
);
protected $ignorePrintXStatusReasonExceptionClassNameList = [
'PDOException'
];
public function __construct(\Espo\Core\Utils\Api\Slim $slim)
{
@@ -69,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();
@@ -79,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);
}
/**
@@ -90,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)) {
$toPrintXStatusReason = true;
if ($exception && in_array(get_class($exception), $this->ignorePrintXStatusReasonExceptionClassNameList)) {
$toPrintXStatusReason = false;
}
$this->getSlim()->response()->setStatus($statusCode);
$this->getSlim()->response()->headers->set('X-Status-Reason', $text);
if ($toPrintXStatusReason) {
$this->getSlim()->response()->headers->set('X-Status-Reason', $text);
}
if ($isPrint) {
$status = $this->getCodeDesc($statusCode);

View File

@@ -251,6 +251,8 @@ class LDAP extends Base
$data[$fieldName] = $fieldValue;
}
$this->getAuth()->useNoAuth();
$user = $this->getEntityManager()->getEntity('User');
$user->set($data);

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,154 @@ 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';
}
public function getColumnDeclarationListSQL(array $fields)
{
$queryFields = array();
foreach ($fields as $fieldName => $field) {
$quotedFieldName = $this->espoQuote($fieldName);
$queryFields[] = $this->getColumnDeclarationSQL($quotedFieldName, $field);
}
return implode(', ', $queryFields);
}
//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(
@@ -99,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()
@@ -114,6 +124,11 @@ class Converter
return $this->metadata;
}
protected function getConfig()
{
return $this->config;
}
protected function getEntityDefs($reload = false)
{
if (empty($this->entityDefs) || $reload) {
@@ -138,6 +153,11 @@ class Converter
return $this->metadataHelper;
}
protected function getDatabaseHelper()
{
return $this->databaseHelper;
}
/**
* Orm metadata convertation process
*
@@ -186,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();
@@ -467,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

@@ -40,7 +40,8 @@ class AttachmentMultiple extends Base
'type' => 'jsonArray',
'notStorable' => true,
'orderBy' => [['createdAt', 'ASC'], ['name', 'ASC']],
'isLinkMultipleIdList' => true
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'type' => 'jsonObject',

View File

@@ -46,40 +46,40 @@ 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',

View File

@@ -33,28 +33,29 @@ class LinkMultiple extends Base
{
protected function load($fieldName, $entityName)
{
$data = array(
$entityName => array (
'fields' => array(
$fieldName.'Ids' => array(
$data = [
$entityName => [
'fields' => [
$fieldName.'Ids' => [
'type' => 'jsonArray',
'notStorable' => true,
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'relation' => $fieldName,
'isUnordered' => true
],
$fieldName.'Names' => [
'type' => 'jsonObject',
'notStorable' => true,
'isLinkMultipleNameMap' => true
)
)
),
'unset' => array(
$entityName => array(
'fields.'.$fieldName
)
)
);
]
]
],
'unset' => [
$entityName => [
'fields.' . $fieldName
]
]
];
$fieldParams = $this->getFieldParams();
@@ -67,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(
$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

@@ -81,11 +81,12 @@ class Converter
protected $maxIndexLength;
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Database\Schema\Schema $databaseSchema)
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());
}
@@ -100,6 +101,11 @@ class Converter
return $this->fileManager;
}
protected function getConfig()
{
return $this->config;
}
/**
* Get schema
*
@@ -124,7 +130,7 @@ class Converter
protected function getMaxIndexLength()
{
if (!isset($this->maxIndexLength)) {
$this->maxIndexLength = $this->getDatabaseSchema()->getMaxIndexLength();
$this->maxIndexLength = $this->getDatabaseSchema()->getDatabaseHelper()->getMaxIndexLength();
}
return $this->maxIndexLength;
@@ -238,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);
}
}

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

@@ -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";
@@ -403,6 +429,16 @@ 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);
@@ -546,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)) {
@@ -620,7 +662,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -685,7 +726,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
@@ -709,7 +749,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
@@ -732,7 +771,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
@@ -806,7 +844,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -831,7 +868,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,

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

@@ -71,6 +71,7 @@ return array (
'de_DE',
'es_ES',
'hr_HR',
'hu_HU',
'fr_FR',
'id_ID',
'it_IT',
@@ -78,6 +79,7 @@ return array (
'nb_NO',
'nl_NL',
'tr_TR',
'sk_SK',
'sr_RS',
'ro_RO',
'ru_RU',
@@ -109,6 +111,7 @@ return array (
'adminNotifications' => true,
'adminNotificationsNewVersion' => true,
'adminNotificationsCronIsNotConfigured' => true,
'adminNotificationsNewExtensionVersion' => true,
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => ['Lead', 'Opportunity', 'Task', 'Case'],
'assignmentNotificationsEntityList' => ['Meeting', 'Call', 'Task', 'Email'],
@@ -168,6 +171,9 @@ return array (
'inlineAttachmentUploadMaxSize' => 20,
'textFilterUseContainsForVarchar' => false,
'tabColorsDisabled' => false,
'massPrintPdfMaxCount' => 50,
'emailKeepParentTeamsEntityList' => ['Case'],
'recordListMaxSizeLimit' => 200,
'isInstalled' => false
);

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 EmailTemplateCategory extends \Espo\Core\Templates\Entities\CategoryTree
{
}

View File

@@ -129,7 +129,7 @@ class Image extends \Espo\Core\EntryPoints\Base
}
if (!empty($size)) {
$fileName = $sourceId . '_' . $size . '.jpg';
$fileName = $size . '-' . $attachment->get('name');
} else {
$fileName = $attachment->get('name');
}
@@ -197,9 +197,10 @@ class Image extends \Espo\Core\EntryPoints\Base
break;
}
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
if (function_exists('exif_read_data')) {
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
}
return $targetImage;
}
}

View File

@@ -58,18 +58,32 @@ class AssignmentEmailNotification extends \Espo\Core\Hooks\Base
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
if ($this->getUser()->id === $userId) continue;
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
$this->createJob($entity, $userId);
}
} else {
$userId = $entity->get('assignedUserId');
if (!empty($userId) && $userId != $this->getUser()->id && $entity->isAttributeChanged('assignedUserId')) {
if (!empty($userId) && $entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)) {
$this->createJob($entity, $userId);
}
}
}
}
protected function isNotSelfAssignment(Entity $entity, $assignedUserId)
{
if ($entity->hasAttribute('createdById') && $entity->hasAttribute('modifiedById')) {
if ($entity->isNew()) {
$isNotSelfAssignment = $assignedUserId !== $entity->get('createdById');
} else {
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
}
} else {
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
}
return $isNotSelfAssignment;
}
protected function createJob(Entity $entity, $userId)
{
$job = $this->getEntityManager()->getEntity('Job');

View File

@@ -76,8 +76,8 @@ class CurrencyConverted extends \Espo\Core\Hooks\Base
$targetValue = $value;
} else {
$targetValue = $value;
$targetValue = $targetValue * (isset($rates[$defaultCurrency]) ? $rates[$defaultCurrency] : 1.0);
$targetValue = $targetValue / (isset($rates[$currency]) ? $rates[$currency] : 1.0);
$targetValue = $targetValue / (isset($rates[$baseCurrency]) ? $rates[$baseCurrency] : 1.0);
$targetValue = $targetValue * (isset($rates[$currency]) ? $rates[$currency] : 1.0);
$targetValue = round($targetValue, 2);
}
$entity->set($fieldName, $targetValue);

View File

@@ -0,0 +1,54 @@
<?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\Jobs;
use Espo\Core\Exceptions;
class CheckNewExtensionVersion extends CheckNewVersion
{
public function run()
{
if (!$this->getConfig()->get('adminNotifications') || !$this->getConfig()->get('adminNotificationsNewExtensionVersion')) {
return true;
}
$job = $this->getEntityManager()->getEntity('Job');
$job->set(array(
'name' => 'Check for new versions of installed extensions (job)',
'serviceName' => 'AdminNotifications',
'methodName' => 'jobCheckNewExtensionVersion',
'executeTime' => $this->getRunTime()
));
$this->getEntityManager()->saveEntity($job);
return true;
}
}

View File

@@ -191,7 +191,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
$collection = $this->getEntityManager()->getRepository('Attachment')->where(array(
'OR' => array(
array(
'role' => ['Export File']
'role' => ['Export File', 'Mail Merge', 'Mass Pdf']
)
),
'createdAt<' => $datetime->format('Y-m-d H:i:s')

View File

@@ -37,7 +37,7 @@ class Activities extends \Espo\Core\Controllers\Base
{
protected $maxCalendarRange = 123;
protected $maxSizeLimit = 200;
const MAX_SIZE_LIMIT = 200;
public function actionListCalendarEvents($params, $data, $request)
{
@@ -113,11 +113,12 @@ class Activities extends \Espo\Core\Controllers\Base
$futureDays = intval($request->get('futureDays'));
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
if (empty($maxSize)) {
$maxSize = $this->maxSizeLimit;
$maxSize = $maxSizeLimit;
}
if ($maxSize > $this->maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
}
return $service->getUpcomingActivities($userId, array(
@@ -175,11 +176,12 @@ class Activities extends \Espo\Core\Controllers\Base
$sortBy = $request->get('sortBy');
$where = $request->get('where');
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
if (empty($maxSize)) {
$maxSize = $this->maxSizeLimit;
$maxSize = $maxSizeLimit;
}
if ($maxSize > $this->maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
}
$scope = null;

View File

@@ -29,7 +29,27 @@
namespace Espo\Modules\Crm\Controllers;
use \Espo\Core\Exceptions\Error,
\Espo\Core\Exceptions\Forbidden,
\Espo\Core\Exceptions\BadRequest;
class Campaign extends \Espo\Core\Controllers\Record
{
public function postActionGenerateMailMergePdf($params, $data, $request)
{
if (empty($data->campaignId)) {
throw new BadRequest();
}
if (empty($data->link)) {
throw new BadRequest();
}
if (!$this->getAcl()->checkScope('Campaign', 'read')) {
throw new Forbidden();
}
return [
'id' => $this->getRecordService()->generateMailMergePdf($data->campaignId, $data->link, true)
];
}
}

View File

@@ -87,8 +87,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
$useLastStage = $request->get('useLastStage') === 'true';
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo, $useLastStage);
}
public function postActionMassConvertCurrency($params, $data, $request)

View File

@@ -63,6 +63,9 @@ class TargetList extends \Espo\Core\Controllers\Record
if (empty($data->targetId)) {
throw new BadRequest();
}
$data->id = strval($data->id);
$data->targetId = strval($data->targetId);
return $this->getRecordService()->optOut($data->id, $data->targetType, $data->targetId);
}
@@ -77,6 +80,9 @@ class TargetList extends \Espo\Core\Controllers\Record
if (empty($data->targetId)) {
throw new BadRequest();
}
$data->id = strval($data->id);
$data->targetId = strval($data->targetId);
return $this->getRecordService()->cancelOptOut($data->id, $data->targetType, $data->targetId);
}
}

View File

@@ -116,6 +116,8 @@ class EventConfirmation extends \Espo\Core\EntryPoints\Base
";
$this->getClientManager()->display($runScript);
return;
}
throw new Error();

View File

@@ -56,7 +56,7 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
if ($entity->getFetched('contactId')) {
$previousPortalUser = $this->getEntityManager()->getRepository('User')->where(array(
'contactId' => $entity->getFetched('contactId'),
'isPortal' => true
'isPortalUser' => true
))->findOne();
if ($previousPortalUser) {
$this->getInjection('serviceFactory')->create('Stream')->unfollowEntity($entity, $previousPortalUser->id);
@@ -70,9 +70,10 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
$portalUser = $this->getEntityManager()->getRepository('User')->where(array(
'contactId' => $contactId,
'isPortal' => true,
'isPortalUser' => true,
'isActive' => true
))->findOne();
if ($portalUser) {
$this->getInjection('serviceFactory')->create('Stream')->followEntity($entity, $portalUser->id);
}
@@ -97,4 +98,3 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
}
}
}

View File

@@ -41,5 +41,19 @@ class Lead extends \Espo\Core\ORM\Repositories\RDB
$this->relate($entity, 'targetLists', $entity->get('targetListId'));
}
}
}
public function handleSelectParams(&$params)
{
parent::handleSelectParams($params);
if (array_key_exists('select', $params)) {
if (in_array('name', $params['select'])) {
$additionalAttributeList = ['emailAddress', 'phoneNumber', 'accountName'];
foreach ($additionalAttributeList as $attribute) {
if (!in_array($attribute, $params['select'])) {
$params['select'][] = $attribute;
}
}
}
}
}
}

View File

@@ -42,6 +42,53 @@ class Opportunity extends \Espo\Core\ORM\Repositories\RDB
}
}
if (!$entity->isAttributeChanged('lastStage') && $entity->isAttributeChanged('stage')) {
$probability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('stage')], 0);
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
if (!$probability) {
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
if ($entity->isNew()) {
if (count($stageList)) {
$min = 100;
$minStage = null;
foreach ($stageList as $stage) {
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
if ($probabilityMap[$stage] < $min) {
$min = $probabilityMap[$stage];
$minStage = $stage;
}
}
}
if ($minStage) {
$entity->set('lastStage', $minStage);
}
}
} else {
$lastStageProbability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('lastStage')], 0);
if ($lastStageProbability === 100) {
if (count($stageList)) {
$max = 0;
$maxStage = null;
foreach ($stageList as $stage) {
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
if ($probabilityMap[$stage] > $max) {
$max = $probabilityMap[$stage];
$maxStage = $stage;
}
}
}
if ($maxStage) {
$entity->set('lastStage', $maxStage);
}
}
}
}
} else {
$entity->set('lastStage', $entity->get('stage'));
}
}
parent::beforeSave($entity, $options);
}

View File

@@ -19,7 +19,12 @@
"revenue": "Revenue",
"revenueConverted": "Revenue (converted)",
"budget": "Budget",
"budgetConverted": "Budget (converted)"
"budgetConverted": "Budget (converted)",
"contactsTemplate": "Contacts Template",
"leadsTemplate": "Leads Template",
"accountsTemplate": "Accounts Template",
"usersTemplate": "Users Template",
"mailMergeOnlyWithAddress": "Skip records w/o filled address"
},
"links": {
"targetLists": "Target Lists",
@@ -30,7 +35,11 @@
"opportunities": "Opportunities",
"campaignLogRecords": "Log",
"massEmails": "Mass Emails",
"trackingUrls": "Tracking URLs"
"trackingUrls": "Tracking URLs",
"contactsTemplate": "Contacts Template",
"leadsTemplate": "Leads Template",
"accountsTemplate": "Accounts Template",
"usersTemplate": "Users Template"
},
"options": {
"type": {
@@ -59,7 +68,9 @@
"Email Templates": "Email Templates",
"Unsubscribe again": "Unsubscribe again",
"Subscribe again": "Subscribe again",
"Create Target List": "Create Target List"
"Create Target List": "Create Target List",
"Mail Merge": "Mail Merge",
"Generate Mail Merge PDF": "Generate Mail Merge PDF"
},
"presetFilters": {
"active": "Active"

View File

@@ -1,5 +1,6 @@
{
"fields": {
"futureDays": "Next X Days"
"futureDays": "Next X Days",
"useLastStage": "Group by last reached stage"
}
}

View File

@@ -15,7 +15,8 @@
"campaign": "Campaign",
"originalLead": "Original Lead",
"amountCurrency": "Amount Currency",
"contactRole": "Contact Role"
"contactRole": "Contact Role",
"lastStage": "Last Stage"
},
"links": {
"contacts": "Contacts",

View File

@@ -3,11 +3,14 @@
"name": "Name",
"description": "Description",
"entryCount": "Entry Count",
"optedOutCount": "Opted Out Count",
"campaigns": "Campaigns",
"endDate": "End Date",
"targetLists": "Target Lists",
"includingActionList": "Including",
"excludingActionList": "Excluding"
"excludingActionList": "Excluding",
"targetStatus": "Target Status",
"isOptedOut": "Is Opted Out"
},
"links": {
"accounts": "Accounts",
@@ -23,6 +26,10 @@
"Television": "Television",
"Radio": "Radio",
"Newsletter": "Newsletter"
},
"targetStatus": {
"Opted Out": "Opted Out",
"Listed": "Listed"
}
},
"labels": {

View File

@@ -1,6 +1,7 @@
{
"layouts": {
"detailConvert": "Convertir Referencia",
"listForAccount": "Listado (por Cuentas)"
"listForAccount": "Listado (por Cuentas)",
"listForContact": "Lista (para Contactos)"
}
}

View File

@@ -16,6 +16,7 @@
"time": "hora",
"User List": "Lista de Usuarios",
"Manage Users": "Usuarios",
"View Calendar": "Ver Calendario"
"View Calendar": "Ver Calendario",
"Create Shared View": "Crear Vista Compartida"
}
}

View File

@@ -1 +1,5 @@
{}
{
"fields": {
"futureDays": "Siguientes 'n' Días"
}
}

View File

@@ -0,0 +1,104 @@
{
"fields": {
"name": "Név",
"website": "Weboldal",
"phoneNumber": "Telefon",
"billingAddress": "számlázási cím",
"shippingAddress": "szállítási cím",
"description": "Leírás",
"sicCode": "Sic kód",
"industry": "Ipar",
"type": "típus",
"contactRole": "Cím",
"campaign": "Kampány",
"targetLists": "Céllista",
"targetList": "Tűztábla",
"originalLead": "Eredeti vezető",
"contactIsInactive": "tétlen"
},
"links": {
"contacts": "Kapcsolatok",
"opportunities": "lehetőségek",
"cases": "Olyan esetek,",
"documents": "Dokumentumok",
"meetingsPrimary": "Találkozók (bővített)",
"callsPrimary": "Hívások (kibővített)",
"tasksPrimary": "Feladatok (bővítve)",
"emailsPrimary": "E-mailek (bővítve)",
"targetLists": "Céllista",
"campaignLogRecords": "Kampánynapló",
"campaign": "Kampány",
"portalUsers": "Portál felhasználók",
"originalLead": "Eredeti vezető"
},
"options": {
"type": {
"Customer": "Vevő",
"Investor": "Befektetői",
"Reseller": "viszonteladó"
},
"industry": {
"Agriculture": "Mezőgazdaság",
"Advertising": "Hirdető",
"Apparel & Accessories": "Ruházat és kiegészítők",
"Automotive": "Autóipari",
"Banking": "Banki",
"Biotechnology": "Biotechnológiai",
"Building Materials & Equipment": "Építőanyagok és berendezések",
"Chemical": "Kémiai",
"Computer": "Számítógép",
"Education": "Oktatás",
"Electronics": "Elektronika",
"Energy": "Energia",
"Entertainment & Leisure": "Szórakozás és szabadidő",
"Finance": "Pénzügy",
"Food & Beverage": "étel és ital",
"Grocery": "Élelmiszerbolt",
"Healthcare": "Egészségügy",
"Insurance": "Biztosítás",
"Legal": "Jogi",
"Manufacturing": "Gyártás",
"Publishing": "Kiadás",
"Real Estate": "Ingatlan",
"Service": "Szolgáltatás",
"Sports": "Sport",
"Software": "Szoftver",
"Technology": "Technológia",
"Telecommunications": "Távközlési",
"Television": "Televízió",
"Transportation": "Szállítás",
"Venture Capital": "Kockázati tőke",
"Aerospace": "légtér",
"Architecture": "Építészet",
"Construction": "Építés",
"Defense": "Védelem",
"Creative": "Kreatív",
"Culture": "Kultúra",
"Consulting": "Tanácsadó",
"Electric Power": "Elektromos energia",
"Hospitality": "Vendégszeretet",
"Mass Media": "Tömegmédia",
"Mining": "Bányászati",
"Music": "Zene",
"Marketing": "értékesítés",
"Petroleum": "Petróleum",
"Retail": "Kiskereskedelem",
"Shipping": "Szállítás",
"Support": "Támogatás",
"Testing, Inspection & Certification": "Vizsgálat, ellenőrzés és tanúsítás",
"Wholesale": "nagybani",
"Water": "Víz",
"Travel": "Utazás"
}
},
"labels": {
"Create Account": "Fiók létrehozása",
"Copy Billing": "Számlázás másolása",
"Set Primary": "Állítsa be az Elsődleges beállítást"
},
"presetFilters": {
"customers": "Az ügyfelek",
"partners": "partnerek",
"recentlyCreated": "A közelmúltban létrehozott"
}
}

View File

@@ -0,0 +1,7 @@
{
"layouts": {
"detailConvert": "Átalakító",
"listForAccount": "Lista (a fiókhoz)",
"listForContact": "Lista (az elérhetőséghez)"
}
}

View File

@@ -0,0 +1,22 @@
{
"modes": {
"month": "Hónap",
"week": "Hét",
"agendaWeek": "Hét",
"day": "Nap",
"agendaDay": "Nap",
"timeline": "Idővonal"
},
"labels": {
"Today": "Ma",
"Create": "Teremt",
"Shared": "megosztott",
"Add User": "Felhasználó hozzáadása",
"current": "jelenlegi",
"time": "idő",
"User List": "Felhasználói lista",
"Manage Users": "Felhasználók kezelése",
"View Calendar": "Naptár megtekintése",
"Create Shared View": "Megosztott nézet létrehozása"
}
}

View File

@@ -0,0 +1,42 @@
{
"fields": {
"name": "Név",
"parent": "Szülő",
"status": "Állapot",
"dateStart": "Dátum kezdete",
"dateEnd": "Dátum vége",
"direction": "Irány",
"duration": "tartam",
"description": "Leírás",
"users": "felhasználók",
"contacts": "Kapcsolatok",
"leads": "vezet",
"reminders": "Emlékeztetők",
"account": "számla",
"acceptanceStatus": "Átvételi állapot"
},
"options": {
"status": {
"Planned": "Tervezett",
"Not Held": "Nem tartott"
},
"direction": {
"Outbound": "induló",
"Inbound": "Bejövő"
},
"acceptanceStatus": {
"None": "Egyik sem",
"Accepted": "Elfogadott",
"Declined": "Elutasította",
"Tentative": "Kísérleti"
}
},
"labels": {
"Create Call": "Hívás létrehozása",
"Send Invitations": "Küldjön meghívókat"
},
"presetFilters": {
"planned": "Tervezett",
"todays": "A mai"
}
}

View File

@@ -0,0 +1,73 @@
{
"fields": {
"name": "Név",
"description": "Leírás",
"status": "Állapot",
"type": "típus",
"startDate": "Kezdő dátum",
"endDate": "Befejezés dátuma",
"targetLists": "Céllista",
"excludingTargetLists": "Céllista kivételével",
"sentCount": "Küldött",
"openedCount": "Nyitott",
"clickedCount": "Kattintott",
"optedOutCount": "Elutasított",
"bouncedCount": "Visszafordulást",
"softBouncedCount": "Lágy visszahúzódott",
"leadCreatedCount": "Leads létrehozva",
"revenue": "jövedelem",
"revenueConverted": "Bevétel (átváltva)",
"budget": "Költségvetés",
"budgetConverted": "Költségkeret (átváltva)"
},
"links": {
"targetLists": "Céllista",
"excludingTargetLists": "Céllista kivételével",
"accounts": "Fiókok",
"contacts": "Kapcsolatok",
"leads": "vezet",
"opportunities": "lehetőségek",
"campaignLogRecords": "Bejelentkezés",
"massEmails": "Tömeges e-mailek",
"trackingUrls": "Nyomon követési URL-ek"
},
"options": {
"type": {
"Web": "háló",
"Television": "Televízió",
"Radio": "Rádió",
"Newsletter": "Hírlevél",
"Mail": "Levél"
},
"status": {
"Planning": "Tervezés",
"Active": "Aktív",
"Inactive": "tétlen",
"Complete": "teljes"
}
},
"labels": {
"Create Campaign": "Kampány létrehozása",
"Target Lists": "Céllista",
"Statistics": "Statisztika",
"hard": "kemény",
"soft": "puha",
"Unsubscribe": "Leiratkozás",
"Mass Emails": "Tömeges e-mailek",
"Email Templates": "E-mail sablonok",
"Unsubscribe again": "Újrairatkozás újra",
"Subscribe again": "Feliratkozás újra",
"Create Target List": "Céllista létrehozása"
},
"presetFilters": {
"active": "Aktív"
},
"messages": {
"unsubscribed": "Leiratkozott a levelezési listáról.",
"subscribedAgain": "Újra feliratkozol."
},
"tooltips": {
"targetLists": "Olyan célok, amelyeknek üzeneteket kell kapniuk.",
"excludingTargetLists": "Olyan célzások, amelyeknek nem kell üzeneteket kapnia."
}
}

View File

@@ -0,0 +1,40 @@
{
"fields": {
"action": "Akció",
"actionDate": "Dátum",
"data": "Adat",
"campaign": "Kampány",
"parent": "Cél",
"object": "Tárgy",
"application": "Alkalmazás",
"queueItem": "Sor tétel",
"stringData": "String adatok",
"stringAdditionalData": "String kiegészítő adatok",
"isTest": "A teszt"
},
"links": {
"queueItem": "Sor tétel",
"parent": "Szülő",
"object": "Tárgy",
"campaign": "Kampány"
},
"options": {
"action": {
"Sent": "Küldött",
"Opened": "Nyitott",
"Opted Out": "Elutasított",
"Bounced": "Visszafordulást",
"Clicked": "Kattintott"
}
},
"labels": {
"All": "Minden"
},
"presetFilters": {
"sent": "Küldött",
"opened": "Nyitott",
"optedOut": "Elutasított",
"bounced": "Visszafordulást",
"clicked": "Kattintott"
}
}

View File

@@ -0,0 +1,12 @@
{
"fields": {
"urlToUse": "Kód helyett URL helyett",
"campaign": "Kampány"
},
"links": {
"campaign": "Kampány"
},
"labels": {
"Create CampaignTrackingUrl": "Követési URL létrehozása"
}
}

View File

@@ -0,0 +1,61 @@
{
"fields": {
"name": "Név",
"number": "Szám",
"status": "Állapot",
"account": "számla",
"contact": "Kapcsolatba lépni",
"contacts": "Kapcsolatok",
"priority": "Kiemelten fontos",
"type": "típus",
"description": "Leírás",
"lead": "Vezet",
"attachments": "Mellékletek",
"inboundEmail": "Csoportos e-mail fiók"
},
"links": {
"account": "számla",
"contact": "Kapcsolat (elsődleges)",
"Contacts": "Kapcsolatok",
"meetings": "találkozók",
"calls": "felhívja",
"tasks": "feladatok",
"emails": "e-mailek",
"articles": "Tudásbázis-cikkek",
"lead": "Vezet",
"attachments": "Mellékletek",
"inboundEmail": "Csoportos e-mail fiók"
},
"options": {
"status": {
"New": "Új",
"Assigned": "Kijelölt",
"Pending": "Függőben levő",
"Closed": "Zárva",
"Rejected": "Elutasítva",
"Duplicate": "Másolat"
},
"priority": {
"Low": "Alacsony",
"Normal": "Normál",
"High": "Magas",
"Urgent": "Sürgős"
},
"type": {
"Question": "Kérdés",
"Incident": "Incidens",
"Problem": "Probléma"
}
},
"labels": {
"Create Case": "Létrehozás",
"Close": "Bezárás",
"Reject": "Elutasít",
"Closed": "Zárva",
"Rejected": "Elutasítva"
},
"presetFilters": {
"open": "Nyisd ki",
"closed": "Zárva"
}
}

View File

@@ -0,0 +1,54 @@
{
"fields": {
"name": "Név",
"title": "Cím",
"accountRole": "Cím",
"account": "számla",
"accounts": "Fiókok",
"phoneNumber": "Telefon",
"accountType": "Fiók Típus",
"doNotCall": "Ne hívj",
"address": "Cím",
"opportunityRole": "Lehetőség szerep",
"description": "Leírás",
"campaign": "Kampány",
"targetLists": "Céllista",
"targetList": "Tűztábla",
"portalUser": "Portál felhasználó",
"originalLead": "Eredeti vezető",
"acceptanceStatus": "Átvételi állapot",
"accountIsInactive": "inaktív fiók",
"acceptanceStatusMeetings": "Átvételi állapot (ülések)",
"acceptanceStatusCalls": "Átvételi állapot (hívások)"
},
"links": {
"opportunities": "lehetőségek",
"cases": "Olyan esetek,",
"targetLists": "Céllista",
"campaignLogRecords": "Kampánynapló",
"campaign": "Kampány",
"account": "Fiók (elsődleges)",
"accounts": "Fiókok",
"casesPrimary": "Ügyek (elsődleges)",
"portalUser": "Portál felhasználó",
"originalLead": "Eredeti vezető",
"documents": "Dokumentumok",
"tasksPrimary": "Feladatok (bővítve)"
},
"labels": {
"Create Contact": "Kapcsolat létrehozása"
},
"options": {
"opportunityRole": {
"": "--Egyik sem--",
"Decision Maker": "Döntéshozó",
"Evaluator": "Kiértékelő",
"Influencer": "Befolyásoló"
}
},
"presetFilters": {
"portalUsers": "Portál felhasználók",
"notPortalUsers": "Nem Portal felhasználók",
"accountActive": "Aktív"
}
}

View File

@@ -0,0 +1,5 @@
{
"fields": {
"futureDays": "Következő X napok"
}
}

View File

@@ -0,0 +1,39 @@
{
"labels": {
"Create Document": "Dokumentum létrehozása",
"Details": "Részletek"
},
"fields": {
"name": "Név",
"status": "Állapot",
"file": "fájl",
"type": "típus",
"publishDate": "Megjelenési dátum",
"expirationDate": "Lejárati dátum",
"description": "Leírás",
"accounts": "Fiókok"
},
"links": {
"accounts": "Fiókok",
"opportunities": "lehetőségek",
"leads": "vezet",
"contacts": "Kapcsolatok"
},
"options": {
"status": {
"Active": "Aktív",
"Draft": "vázlat",
"Expired": "Lejárt",
"Canceled": "Törölve"
},
"type": {
"": "Egyik sem",
"Contract": "Szerződés",
"License Agreement": "Licencszerződés"
}
},
"presetFilters": {
"active": "Aktív",
"draft": "vázlat"
}
}

View File

@@ -0,0 +1,10 @@
{
"labels": {
"Create DocumentFolder": "Dokumentummappa létrehozása",
"Manage Categories": "Mappák kezelése",
"Documents": "Dokumentumok"
},
"links": {
"documents": "Dokumentumok"
}
}

View File

@@ -0,0 +1,10 @@
{
"labels": {
"Create Lead": "Lead létrehozása",
"Create Contact": "Kapcsolat létrehozása",
"Create Task": "Feladat létrehozása",
"Create Case": "Létrehozás",
"Add to Contact": "Hozzáadás a kapcsolatokhoz",
"Add to Lead": "Add az ólomhoz"
}
}

View File

@@ -0,0 +1,29 @@
{
"fields": {
"name": "Név",
"status": "Állapot",
"target": "Cél",
"sentAt": "Dátum elküldve",
"attemptCount": "Kísérletek",
"emailAddress": "Email cím",
"massEmail": "Tömeges e-mail",
"isTest": "A teszt"
},
"links": {
"target": "Cél",
"massEmail": "Tömeges e-mail"
},
"options": {
"status": {
"Pending": "Függőben levő",
"Sent": "Küldött",
"Failed": "nem sikerült",
"Sending": "elküldés"
}
},
"presetFilters": {
"pending": "Függőben levő",
"sent": "Küldött",
"failed": "nem sikerült"
}
}

View File

@@ -0,0 +1,117 @@
{
"links": {
"parent": "Szülő",
"contacts": "Kapcsolatok",
"opportunities": "lehetőségek",
"leads": "vezet",
"meetings": "találkozók",
"calls": "felhívja",
"tasks": "feladatok",
"emails": "e-mailek",
"accounts": "Fiókok",
"cases": "Olyan esetek,",
"documents": "Dokumentumok",
"account": "számla",
"opportunity": "Lehetőség",
"contact": "Kapcsolatba lépni"
},
"scopeNames": {
"Account": "számla",
"Contact": "Kapcsolatba lépni",
"Lead": "Vezet",
"Target": "Cél",
"Opportunity": "Lehetőség",
"Meeting": "Találkozó",
"Calendar": "Naptár",
"Call": "Hívás",
"Task": "Feladat",
"Case": "Ügy",
"Document": "Dokumentum",
"DocumentFolder": "Dokumentum mappa",
"Campaign": "Kampány",
"TargetList": "Tűztábla",
"MassEmail": "Tömeges e-mail",
"EmailQueueItem": "E-mail soros tétel",
"CampaignTrackingUrl": "Követési URL",
"Activities": "Tevékenységek",
"KnowledgeBaseArticle": "Tudásbázis cikk",
"KnowledgeBaseCategory": "Tudásbázis-kategória",
"CampaignLogRecord": "Kampánynaplófelvétel"
},
"scopeNamesPlural": {
"Account": "Fiókok",
"Contact": "Kapcsolatok",
"Lead": "vezet",
"Target": "célok",
"Opportunity": "lehetőségek",
"Meeting": "találkozók",
"Calendar": "Naptár",
"Call": "felhívja",
"Task": "feladatok",
"Case": "Olyan esetek,",
"Document": "Dokumentumok",
"DocumentFolder": "Dokumentummappák",
"Campaign": "kampányok",
"TargetList": "Céllista",
"MassEmail": "Tömeges e-mailek",
"EmailQueueItem": "E-mail soros elemek",
"CampaignTrackingUrl": "Nyomon követési URL-ek",
"Activities": "Tevékenységek",
"KnowledgeBaseArticle": "Tudásbázis",
"KnowledgeBaseCategory": "Tudásbázis kategóriák",
"CampaignLogRecord": "Kampánynaplórekordok"
},
"dashlets": {
"Leads": "Az én vezetem",
"Opportunities": "Lehetőségek",
"Tasks": "Saját feladatok",
"Cases": "Az én ügyeim",
"Calendar": "Naptár",
"Calls": "Saját hívások",
"Meetings": "Találkozásom",
"OpportunitiesByStage": "Lehetőségek színpadon",
"OpportunitiesByLeadSource": "Lehetőségek a vezető forrásból",
"SalesByMonth": "Havi eladások",
"SalesPipeline": "Értékesítési csővezeték",
"Activities": "Tevékenységem"
},
"labels": {
"Create InboundEmail": "Bejövő e-mail létrehozása",
"Activities": "Tevékenységek",
"History": "Történelem",
"Attendees": "résztvevők",
"Schedule Meeting": "Menetrend Találkozó",
"Schedule Call": "Hívás ütemezése",
"Compose Email": "E-mail írása",
"Log Meeting": "Naplózás",
"Log Call": "Naplóhívás",
"Archive Email": "Archívum e-mailben",
"Create Task": "Feladat létrehozása",
"Tasks": "feladatok"
},
"fields": {
"billingAddressCity": "Város",
"addressCity": "Város",
"billingAddressCountry": "Ország",
"addressCountry": "Ország",
"billingAddressPostalCode": "Irányítószám",
"addressPostalCode": "Irányítószám",
"billingAddressState": "Állapot",
"addressState": "Állapot",
"billingAddressStreet": "utca",
"addressStreet": "utca",
"billingAddressMap": "Térkép",
"addressMap": "Térkép",
"shippingAddressCity": "Város (Szállítás)",
"shippingAddressStreet": "Utca (Szállítás)",
"shippingAddressCountry": "Ország (szállítás)",
"shippingAddressState": "Állam (szállítás)",
"shippingAddressPostalCode": "Irányítószám (Szállítás)",
"shippingAddressMap": "Térkép (Szállítás)"
},
"options": {
"reminderTypes": {
"Popup": "Felugrik"
}
}
}

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