Compare commits

...

523 Commits
4.0.2 ... 4.2.6

Author SHA1 Message Date
yuri
3a24784980 fix email filter 2016-10-07 12:46:46 +03:00
yuri
b1dbd173ad fix mail filters 2016-10-07 11:14:37 +03:00
yuri
a92c294255 fix layout manager reset to default 2016-10-05 12:37:38 +03:00
yuri
27624ead8d number fix 2 2016-09-29 17:25:25 +03:00
yuri
15c980e57f rename number to number util 2016-09-29 17:20:52 +03:00
yuri
a98465f30e skip notification checking while upgrade 2016-09-26 12:04:10 +03:00
yuri
8862fd279e fix language enum translation 2016-09-20 15:47:58 +03:00
yuri
bd6eafd291 version 2016-09-16 17:44:26 +03:00
yuri
a63306603a fix email filter 2016-09-16 17:44:10 +03:00
yuri
0ea0c09007 fix acl 2016-09-13 13:12:39 +03:00
yuri
d69f994f4c fix insert template issue with signature 2016-09-13 10:28:24 +03:00
yuri
7a2a9de174 Merge branch 'hotfix/4.2.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.2.5 2016-09-12 11:17:15 +03:00
Taras Machyshyn
7d2655f757 Fixed warnings on PHP 7 2016-09-07 15:04:06 +03:00
yuri
e58a4c4bbf fix stream status update message 2016-09-02 12:43:26 +03:00
yuri
8432e9dcc8 fix endWidth search 2016-08-30 13:05:41 +03:00
yuri
76602aede1 email import: fix for emails w/o subject header 2016-08-25 11:45:56 +03:00
Ayman Alkom
77411702a7 remove repeated key (#205) 2016-08-25 10:44:29 +03:00
yuri
84e587fcaf mass email: automatically fill name 2016-08-24 10:49:53 +03:00
yuri
f8e75dccdf version 2016-08-23 15:41:32 +03:00
yuri
b6eca1db17 fix email entityDefs 2016-08-23 15:39:00 +03:00
yuri
6ff414ffcd fix users teams field 2016-08-23 15:26:00 +03:00
yuri
7acbf3084d template: related attributes 2016-08-23 12:05:29 +03:00
yuri
87847446a8 css cleanup 2016-08-23 11:18:20 +03:00
yuri
59f250b386 allow template entity 2016-08-23 11:17:39 +03:00
yuri
f93ce594d0 add mobile phone type to account 2016-08-23 11:13:58 +03:00
yuri
c41f4e51e5 fix filter fields css 2016-08-23 11:01:17 +03:00
yuri
6efd355864 remove tabs 2016-08-23 10:11:37 +03:00
Ayman Alkom
7ee2ee9d35 small syntax fixes (#197)
* fix variable name

* init $number variable

* another fix
2016-08-23 10:09:15 +03:00
yuri
1f8a14aca0 fix warning 2016-08-22 15:50:21 +03:00
yuri
f518ae59c0 activities small refactoring 2016-08-22 12:27:18 +03:00
yuri
4d4bde46f8 version 2016-08-22 11:02:41 +03:00
yuri
cb89ad59af cleanup 2016-08-22 11:00:03 +03:00
yuri
092b5024fe it_IT language 2016-08-22 10:36:17 +03:00
yuri
517edf2ced undo comment 2016-08-19 17:52:09 +03:00
yuri
219c28313c fix mass select 2016-08-19 17:48:14 +03:00
yuri
311d92202b fix assignment notification if user is removed 2016-08-19 14:43:44 +03:00
yuri
05cb2ee272 email: fix move to trash update counts 2016-08-18 17:07:16 +03:00
yuri
f896a2d71a fix calendar range issue 2016-08-18 11:13:24 +03:00
yuri
dd6704ace5 fix moving to trash 2016-08-18 10:40:16 +03:00
yuri
1dc4d44a65 es_ES lang fix 2016-08-17 17:52:22 +03:00
yuri
bfb28ea178 Merge branch 'hotfix/4.2.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.2.3 2016-08-17 17:43:43 +03:00
yuri
ec6f3a22f2 version 2016-08-17 17:30:34 +03:00
yuri
5a0c7c330c fix email imported error in php4 2016-08-17 17:28:17 +03:00
Taras Machyshyn
f64df5af87 LDAP fixed admin login 2016-08-17 11:54:59 +03:00
Taras Machyshyn
d4dc7a4051 LDAP fixes 2016-08-16 16:53:40 +03:00
Taras Machyshyn
d676f85c8f LDAP fixes 2016-08-16 16:01:32 +03:00
Taras Machyshyn
ab382f2387 LDAP: changed ldapUserObjectClass attribute. 2016-08-15 16:46:55 +03:00
yuri
206219c738 Merge branch 'hotfix/4.2.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.2.2 2016-08-15 15:51:40 +03:00
Taras Machyshyn
37d1c707cb LDAP: label corrections 2016-08-15 15:51:10 +03:00
Taras Machyshyn
93af1c9bfc LDAP improvements: added possibility to define user objectClass 2016-08-15 15:47:25 +03:00
yuri
a021c4c8d5 open attachments in new window 2016-08-15 11:08:45 +03:00
yuri
a125244cdf Merge branch 'hotfix/4.2.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.2.2 2016-08-15 10:50:10 +03:00
yuri
1cfd251c4c copy attachments for duplicate 2016-08-12 12:51:02 +03:00
Taras Machyshyn
d2f4f312e5 Improvements 2016-08-11 16:21:35 +03:00
Taras Machyshyn
c468b061d9 Bug fixes for installation 2016-08-11 16:21:03 +03:00
yuri
7bf945f0b6 v 2016-08-11 15:31:19 +03:00
yuri
fecbb26cbf email acl fix 2016-08-11 15:30:47 +03:00
yuri
a5ae33ab81 fix dateTime exception 2016-08-11 13:01:25 +03:00
yuri
c6fa0e464e prevent stream request after remove record 2016-08-11 12:49:56 +03:00
yuri
38bae6238a portal: disable teams field 2016-08-11 12:28:14 +03:00
yuri
79de4c874f portal: follow created 2016-08-11 12:16:51 +03:00
yuri
814748ec61 fix map 2016-08-11 11:48:46 +03:00
yuri
1f0ad0cbec fix inbound email duplicate 2016-08-11 11:12:36 +03:00
yuri
7224f566d6 email import: bad date catch 2016-08-10 16:50:22 +03:00
yuri
eefb01ec4f fix salesByMonth 2016-08-10 16:22:17 +03:00
yuri
24d46ed81d fix currency rates 2016-08-10 15:51:20 +03:00
yuri
37c749faf8 remove messageIdInternal index 2016-08-10 15:34:23 +03:00
yuri
4306a3131e fix inbound email ui 2016-08-10 15:32:59 +03:00
yuri
8fa95fcce3 email: fix whitespace subject 2016-08-09 11:12:30 +03:00
yuri
b7c41ce640 email fix 2016-08-08 13:09:51 +03:00
yuri
a21be94ed3 fix acl 2016-08-08 13:06:40 +03:00
yuri
9d59edcae2 v 2016-08-08 11:51:54 +03:00
yuri
deaa26a355 es_ES lang fix 2016-08-08 10:29:49 +03:00
yuri
25d6fb6d82 theme fixes 2016-08-08 10:18:06 +03:00
yuri
c1bcc44f04 fix error 2016-08-08 10:03:00 +03:00
yuri
fdf8183385 fix massRemove acl check 2016-08-08 09:57:06 +03:00
yuri
db40426f00 lang fix 2016-08-05 11:00:24 +03:00
yuri
eb92e648a0 dashlet action url 2016-08-04 12:52:37 +03:00
yuri
5df26c324f fix scheduled job running 2016-08-04 12:30:26 +03:00
yuri
c36a064fdc lang 2016-08-04 11:01:39 +03:00
yuri
a28baa6a75 fix install footer date 2016-08-03 17:29:19 +03:00
yuri
27aed29ddf Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-03 17:26:43 +03:00
yuri
744fb176cc fix code to meet standards 2016-08-03 17:25:29 +03:00
Taras Machyshyn
5f8a0736e4 LDAP corrections 2016-08-03 16:25:33 +03:00
yuri
7fb179e769 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-03 16:09:55 +03:00
Taras Machyshyn
48239c53ca Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-03 12:41:50 +03:00
Taras Machyshyn
4e18afb80f LDAP: added 'Test Connection' button 2016-08-03 12:41:33 +03:00
yuri
e9d3d7c807 fix date stringify 2016-08-03 12:23:58 +03:00
Taras Machyshyn
60923197e5 Ldap: added user teams, user default team 2016-08-03 10:56:08 +03:00
Taras Machyshyn
a9f7c90323 Improvements 2016-08-03 10:37:48 +03:00
yuri
cc723095c2 fix file manager 2016-08-02 15:04:53 +03:00
yuri
373b77f83f fix tests 2016-08-02 14:57:47 +03:00
Taras Machyshyn
68ab589f3e Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-02 13:09:26 +03:00
Taras Machyshyn
23ccbb226f Test fixes 2016-08-02 13:09:14 +03:00
yuri
48edf2a2b5 selectManager test 2016-08-02 13:03:56 +03:00
yuri
082c65ef05 fix unit test 2016-08-02 10:54:12 +03:00
yuri
6553d8ec6c Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-01 17:45:07 +03:00
yuri
c54b6fcc7c test addition 2016-08-01 17:44:43 +03:00
yuri
655ee740e0 importer test 2016-08-01 17:40:47 +03:00
Taras Machyshyn
b02f77b8f9 LDAP improvements 2016-08-01 16:29:26 +03:00
yuri
351c46af06 fix kb order 2016-08-01 15:36:24 +03:00
yuri
d3db25d98a fix portal user password email 2016-08-01 15:24:01 +03:00
yuri
ef1fe1bd1d Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-08-01 11:12:51 +03:00
Taras Machyshyn
6c2cd93826 Default config 2016-08-01 11:12:26 +03:00
yuri
4859f54f42 email import: get rid of file manager 2016-08-01 10:16:09 +03:00
Taras Machyshyn
749c2dc1e9 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-07-29 17:54:02 +03:00
Taras Machyshyn
3d13026084 LDAP fixes 2016-07-29 17:53:49 +03:00
yuri
6cf66aa88e account: recently created filter 2016-07-29 11:51:23 +03:00
yuri
d10ef7038b meetings and assignmentPermissions 2016-07-29 11:41:37 +03:00
yuri
cda612810d Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-07-28 11:31:42 +03:00
yuri
a7aaac513d composer.lock 2016-07-28 11:30:57 +03:00
yuri
329dbdf408 it_IT lang 2016-07-28 11:28:47 +03:00
Taras Machyshyn
007f705904 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-07-27 16:53:24 +03:00
Taras Machyshyn
36cddbe0cf Fixed LDAP authorization 2016-07-27 16:53:11 +03:00
yuri
e9409ccf72 remove tab 2016-07-27 16:44:46 +03:00
yuri
b5a1ede962 Merge branch 'hotfix/4.1.7' 2016-07-27 11:26:28 +03:00
yuri
2dc6951ef9 v 2016-07-27 11:26:07 +03:00
yuri
66f686c013 fix kb order 2016-07-27 10:56:20 +03:00
yuri
937aab2b1c massAction successMessage 2016-07-26 15:31:49 +03:00
yuri
43bee97055 massAction fix 2016-07-26 12:22:36 +03:00
yuri
a31f5ea87a email address book filter by portal permissions 2016-07-26 12:06:50 +03:00
yuri
0c4d5f3405 mass action defs 2016-07-26 11:31:07 +03:00
yuri
4e70ea0586 fix email 2016-07-25 17:13:34 +03:00
yuri
7736f6cd76 fix email update 2016-07-25 17:07:14 +03:00
yuri
2db6eaa344 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-07-25 14:48:08 +03:00
Taras Machyshyn
38b6cbda0e Merge pull request #159 from ecm4u/master
Support AD with LDAP Auth
2016-07-25 14:47:36 +03:00
Taras Machyshyn
9292f18b5b WARNING fixes 2016-07-25 12:35:52 +03:00
Taras Machyshyn
193cf2438a Cron manager bug fixes 2016-07-25 12:31:27 +03:00
yuri
155e0e3841 import: allow created at and created by 2016-07-25 12:01:03 +03:00
yuri
019c03ed2f email-to-task name 2016-07-25 11:24:23 +03:00
yuri
316e54df92 fix message id issue 2016-07-25 11:17:00 +03:00
yuri
40691ae899 fix entity manager text filter list 2016-07-25 11:16:53 +03:00
yuri
ab8ba168ac move kb articles 2016-07-22 14:50:09 +03:00
yuri
b066d91cf5 fix warning 2016-07-21 15:24:01 +03:00
yuri
2833581cde email folders side panel: remove edit link 2016-07-21 13:28:53 +03:00
yuri
279c8b3188 email folder and email filter email link 2016-07-21 13:24:20 +03:00
yuri
eca6408415 prevent changing users email address and phone numbers for non admins 2016-07-21 13:07:13 +03:00
yuri
35cfb1a480 scheduled job mass actions 2016-07-21 10:58:09 +03:00
yuri
30f3d4ab5e stream notifications 2016-07-20 16:40:21 +03:00
yuri
7e5424e40a activities and history listen to save 2016-07-19 12:23:46 +03:00
Heiko Robert
82612326bf fixed wrong method syntax 2016-07-18 09:04:41 +02:00
Heiko Robert
da2e5c835a removed unnecessary comments 2016-07-14 15:06:31 +02:00
Heiko Robert
af8eb51c76 fix: setting ldap user fields array after constructor in createUser to support getConfig() 2016-07-14 14:51:26 +02:00
Heiko Robert
7253e257f1 moved hard coded ldap config to conf.php, added default config for Active Directory (cn --> samaccountname) 2016-07-13 17:50:43 +02:00
Heiko Robert
37e93edf25 Merge pull request #2 from espocrm/master
Update from original
2016-07-13 17:40:48 +02:00
yuri
36a56f050f multiple smtp 2016-07-08 16:25:27 +03:00
yuri
31c2d1360d fix warnings 2016-07-08 11:18:28 +03:00
yuri
4addc48339 fix import 2016-07-08 11:00:19 +03:00
yuri
a7267bc920 fix import of noJoin relations 2016-07-08 10:49:06 +03:00
yuri
93e94f40c3 fix move to folder 2016-07-07 17:36:58 +03:00
yuri
bd75790c29 change email list layout 2016-07-07 17:26:43 +03:00
yuri
f1cffcae38 email actions 2016-07-07 17:08:05 +03:00
yuri
9111bce47a move to folder email 2016-07-07 16:30:08 +03:00
yuri
73ec161ac4 fix RDB findRelated if new 2016-07-07 15:55:53 +03:00
yuri
495a07639e email folders dev 2016-07-07 15:55:37 +03:00
yuri
044223e70e fix RDB findRelated if new 2016-07-07 15:27:53 +03:00
yuri
42c9250995 email folders url 2016-07-07 12:07:51 +03:00
yuri
8b4a1d96fd replyInHtml 2016-07-07 11:32:42 +03:00
yuri
fff06d7e92 email folder dev 2016-07-06 17:14:08 +03:00
yuri
726adc550c email folders 2016-07-06 16:38:29 +03:00
yuri
f5bde670f6 fix mapper fatal error 2016-07-06 10:26:22 +03:00
yuri
050873dd3a Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-07-06 10:12:50 +03:00
yuri
d3886c1c94 dev 2016-07-06 10:12:40 +03:00
yuri
84f03cf3f9 email improvements 2 2016-07-05 12:41:57 +03:00
yuri
f8953d56f4 fix link multiple with role 2016-07-05 10:23:01 +03:00
yuri
ebbf14160b email folder changes 2016-07-04 17:29:46 +03:00
yuri
4df0a09de0 lang 2016-07-04 10:22:58 +03:00
yuri
c485aa9a62 email folder entity 2016-07-01 17:18:32 +03:00
yuri
ae474022b9 email filters skip 2016-07-01 15:23:41 +03:00
yuri
f994cdf7c4 email filter changes 2016-06-30 16:56:04 +03:00
yuri
7ff514e5a7 email sentBy field 2016-06-30 11:48:53 +03:00
yuri
344403de54 fix email sent filter 2016-06-30 11:18:15 +03:00
yuri
836243d170 fix email sent filter 2016-06-30 11:17:31 +03:00
yuri
0dc9129d79 notification improvements 2 2016-06-29 16:54:23 +03:00
yuri
61c9d07ad8 notifications improvements 2016-06-29 16:50:01 +03:00
yuri
a238295ba8 control followers after reassignment 2016-06-29 16:23:30 +03:00
yuri
dbabd0dd45 Merge branch 'hotfix/4.1.6' 2016-06-29 16:03:10 +03:00
yuri
f87ff8db8a fix 2016-06-29 15:38:21 +03:00
yuri
60d121f1e1 portal acl fixes 2016-06-29 15:17:52 +03:00
yuri
1497abff07 v 2016-06-29 13:12:09 +03:00
yuri
935ba7d4e4 fix 2016-06-29 13:10:36 +03:00
yuri
fba79cde8f notification chanfes 2016-06-29 11:51:30 +03:00
yuri
ce79d14a29 duplicate by default 2016-06-28 15:53:18 +03:00
yuri
14b2083226 google maps key 2016-06-28 15:28:50 +03:00
yuri
779a841817 kba ordering 2016-06-28 11:56:29 +03:00
yuri
3647523150 naming fix 2016-06-28 10:59:32 +03:00
yuri
eb5b941fe6 email accounts massUpdate 2016-06-28 10:56:25 +03:00
yuri
c542d99536 fix stream header long texts 2016-06-27 10:50:48 +03:00
yuri
918031a9c6 fix long texts in stream 2016-06-27 10:49:00 +03:00
yuri
163e0cac2a drag and drop additional 2016-06-24 17:08:49 +03:00
Taras Machyshyn
95096dd8fd Correct translations for installer 2016-06-24 15:28:28 +03:00
yuri
67bd98bf4b iso week numbers 2016-06-24 15:22:03 +03:00
yuri
ac434c00d6 stream drag and drop 2016-06-24 15:04:54 +03:00
yuri
0fb8330ecf preferenses: hide notifications if not enabled 2016-06-24 12:36:55 +03:00
yuri
3f969cee5b Merge branch 'hotfix/4.1.6' 2016-06-24 12:15:59 +03:00
yuri
ea5f6300f3 fix text overflow in list expanded 2016-06-24 12:15:51 +03:00
yuri
0d5e75db23 activities dashlet: show only todays and next day 2016-06-23 17:18:49 +03:00
yuri
7d667a1d1b notification about mention 2016-06-23 16:18:32 +03:00
yuri
610922c8e0 fix client notification 2016-06-23 15:20:17 +03:00
yuri
36e0882542 rename method 2016-06-23 11:44:13 +03:00
yuri
b9979e87af email notifications: dont notify old emails 2016-06-23 11:21:38 +03:00
yuri
bcd38dd853 fix email filters 2016-06-23 10:52:22 +03:00
yuri
b018580c0b fix email reminder 2016-06-23 10:36:08 +03:00
yuri
38a7f42a1e remove ob_clean and flush 2016-06-22 16:26:52 +03:00
yuri
1387d856ba remove ob_clean from installer 2016-06-22 16:20:11 +03:00
yuri
f2bbc872d6 lang fixes 2016-06-22 16:00:45 +03:00
yuri
c74e7b416e lang 2016-06-22 15:03:33 +03:00
yuri
f008688c14 orm: fix order by list 2016-06-22 12:14:15 +03:00
yuri
dff0d5a992 fix email notification from person 2016-06-22 12:01:38 +03:00
yuri
6fe6f8960f fix stream note.js 2016-06-22 10:28:25 +03:00
yuri
0b0184098d fix gender in stream 2016-06-21 17:25:18 +03:00
yuri
647515d21a gender support 2016-06-21 17:21:45 +03:00
yuri
2ee944ef7b change assignment templates 2016-06-21 12:23:51 +03:00
yuri
f4d2325b8b reminder and invitations change 2016-06-21 12:08:55 +03:00
yuri
c0904125eb notification changes and stream message changes 2016-06-20 17:35:57 +03:00
yuri
7dad30ad0c fix notices 2016-06-20 11:24:15 +03:00
yuri
d6acb6dfdc fix notice 2016-06-20 11:06:26 +03:00
yuri
b6da94fe2e fix modal backdrop close 2016-06-17 11:54:09 +03:00
yuri
dc5e292a02 fix modal backdrop close 2016-06-17 11:53:30 +03:00
yuri
4bf938107d portal id 2016-06-16 16:59:10 +03:00
yuri
ec7d49cdbc fix validate message if empty element 2016-06-15 17:20:19 +03:00
yuri
3c73062b91 email: is replied 2016-06-15 16:38:01 +03:00
yuri
5122f112d1 task dashlet: dont show if date start is future 2016-06-15 12:43:47 +03:00
yuri
5e20fa6717 orm: skip text fields param 2016-06-15 11:41:52 +03:00
yuri
d4599d9377 fix mapper test 2016-06-15 11:40:26 +03:00
yuri
adb9ce4d7e email: rename name label 2016-06-15 11:17:16 +03:00
yuri
e777413a7b enum order 2016-06-15 11:15:02 +03:00
yuri
2cb0ac6221 email: forward info 2016-06-14 17:30:24 +03:00
yuri
8f7fafb990 email import: fetch parent from replied 2016-06-14 16:37:54 +03:00
yuri
158c911787 fix label 2016-06-14 13:01:58 +03:00
yuri
aa67575ecb internal post dev 2016-06-14 12:56:18 +03:00
yuri
81cf82c99f fix stream posting 2016-06-14 11:56:42 +03:00
yuri
e638bf2eec Merge branch 'hotfix/4.1.6' 2016-06-14 11:44:55 +03:00
yuri
6c9d1dbb3d fix notification sounds 2016-06-14 11:43:31 +03:00
yuri
fbb034ef92 intenal post dev 2016-06-14 11:24:42 +03:00
yuri
335601d6b8 fix multienum validate message 2016-06-13 15:57:37 +03:00
yuri
f6ad51ca74 fix calendar colors 2016-06-13 15:30:16 +03:00
yuri
e119f8b008 fix entity manager tabList 2016-06-13 15:23:29 +03:00
yuri
5af0eeff3b entity manager: event type 2016-06-13 13:12:41 +03:00
yuri
f685feb312 Merge branch 'hotfix/4.1.6' 2016-06-13 10:56:38 +03:00
yuri
c78254a8fa no dashboard layout for portal useres 2016-06-13 10:52:20 +03:00
yuri
9c3758b92c Merge branch 'stable' 2016-06-10 16:10:48 +03:00
yuri
4ca2a7fa1a fix notice 2016-06-10 16:03:10 +03:00
yuri
d6814b1601 Merge branch 'hotfix/4.1.5' 2016-06-10 15:55:57 +03:00
yuri
55e5a21dcd Merge branch 'hotfix/4.1.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.1.5 2016-06-10 15:54:14 +03:00
Taras Machyshyn
792f422f76 Bug fixing 2016-06-10 15:53:37 +03:00
yuri
8cac546087 merge with hotfix/4.1.5 2016-06-10 15:05:41 +03:00
yuri
0cfe91d960 loadAdditionalFieldsForPdf 2016-06-10 14:44:09 +03:00
yuri
4dea1762e8 stream textarea fix 2016-06-10 13:08:22 +03:00
Taras Machyshyn
b1a9b8d8b1 Code improvement 2016-06-10 10:57:56 +03:00
yuri
6787acce61 fix next number 2016-06-10 10:50:40 +03:00
yuri
eb0adf7c28 fix afterMassRemove 2016-06-09 14:03:23 +03:00
yuri
783cccaa1b Number field type 2016-06-09 12:47:17 +03:00
yuri
36a0a22996 naming fix 2016-06-09 10:49:56 +03:00
yuri
a3f3357b2e Merge branch 'hotfix/4.1.5' 2016-06-08 17:58:29 +03:00
yuri
cbbfa44174 fix field manager ui 2016-06-08 17:58:18 +03:00
yuri
812d2ec9bc fix field manager hook 2016-06-08 17:52:20 +03:00
yuri
b51ba96bb7 number field type prepare 2016-06-08 17:38:12 +03:00
yuri
4c60cf79f6 rdb: rename methods 2016-06-08 15:56:04 +03:00
yuri
8451949805 database charset param 2016-06-08 11:47:52 +03:00
yuri
af5750cdf6 color change 2016-06-07 19:00:57 +03:00
yuri
1a4dfd6f67 version 2016-06-07 12:29:29 +03:00
yuri
6aa800d453 fix fetchOnModelAfterRelate 2016-06-07 12:26:20 +03:00
yuri
82aa3b9508 fix massRelate 2016-06-07 12:15:04 +03:00
yuri
6910a113da email filter: filters 2016-06-06 16:49:07 +03:00
yuri
bd20aaa577 fix htmlizer 2016-06-06 12:55:04 +03:00
yuri
0aef3c0b04 htmlizer and password 2016-06-06 12:53:10 +03:00
yuri
2f97010b54 htmlizer improvements 2016-06-06 12:31:26 +03:00
yuri
5bcdad2996 wysiwyg htmlToPlain fix 2016-06-06 11:59:49 +03:00
yuri
1e8a8d94c2 fix timeline timezone 2016-06-06 11:51:21 +03:00
yuri
9aeefd7685 contact: fix accounts field 2016-06-03 11:29:46 +03:00
yuri
f72385471c naming fix 2016-06-03 11:27:41 +03:00
yuri
f7a542560a check duplicates: all email 2016-06-03 11:19:03 +03:00
yuri
400f43447d v 2016-06-02 17:22:15 +03:00
yuri
866593a831 Storage Message: define ErrorHandler 2016-06-02 17:15:30 +03:00
yuri
c90cdc62fb global search changes 2016-06-02 12:39:06 +03:00
yuri
d5c93f21b5 audited for miltiEnum and arrays 2016-06-02 12:26:47 +03:00
yuri
b58d78a29e preferences: dashboardLayout 2016-05-31 12:05:37 +03:00
yuri
f5b41eb78b field manager: translatedOptions param 2016-05-31 11:01:17 +03:00
yuri
c2a7d90944 fix view-helper options 2016-05-27 13:17:51 +03:00
yuri
b2a4ec238c fix preferences fields 2016-05-27 12:11:17 +03:00
yuri
df69584c7d v 2016-05-27 11:46:31 +03:00
yuri
574da55be7 Merge branch 'hotfix/4.1.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.1.3 2016-05-26 17:15:40 +03:00
Taras Machyshyn
52b808b902 Removed unnecessary debugging messages 2016-05-26 16:53:52 +03:00
Taras Machyshyn
714c7b0a33 Fixed an issue for MySQL 5.7 2016-05-26 16:41:50 +03:00
yuri
3d774e3afa fix wysywyg modals 2016-05-26 16:26:16 +03:00
yuri
c91db1699b upgrade bootstrap 2016-05-26 15:39:04 +03:00
yuri
2b5695d8dc fix wysywig field detail view height 2016-05-26 12:23:52 +03:00
yuri
b562fc33bd dont allow email to modify 2016-05-26 11:43:52 +03:00
yuri
425414b8f3 fix add email address 2016-05-26 11:24:21 +03:00
yuri
3b363d5ee3 cleanup 2016-05-26 11:10:34 +03:00
yuri
4fc3b9a99e fix kb massUpdate layout 2016-05-25 15:23:12 +03:00
yuri
ab2b9bafeb fix meeting/call select manager 2016-05-24 15:53:09 +03:00
yuri
e99fc6302f reminders for all users 2016-05-24 12:35:46 +03:00
yuri
82996a807f calendar: getCalendarSelectParams method 2016-05-24 11:38:14 +03:00
yuri
86b31b1f26 clear role cach if isAdmin changed 2016-05-23 17:25:58 +03:00
yuri
19c9b38f82 applicationName in settings 2016-05-23 12:24:18 +03:00
yuri
b1d0d1cd27 activities: status not required 2016-05-23 11:47:04 +03:00
yuri
c916011530 add id_ID language 2016-05-23 11:36:58 +03:00
yuri
5db6327272 de_DE 2016-05-23 11:10:03 +03:00
yuri
3c5fe4c778 v 2016-05-20 15:41:08 +03:00
yuri
f6f3f05aa5 fix meeting acl 2016-05-20 15:39:51 +03:00
yuri
10f34564d0 load teams for user 2016-05-20 15:38:40 +03:00
yuri
216ec2329d fix record isPermittedTeams 2016-05-20 15:30:49 +03:00
yuri
c3cb4619cf disable formatting for autoincrement 2016-05-20 11:21:06 +03:00
yuri
fcf2929b5b fix record/base 2016-05-19 17:56:19 +03:00
yuri
15c10ca553 fix notice 2016-05-18 16:38:40 +03:00
yuri
9bb417ab38 v 2016-05-18 16:32:25 +03:00
yuri
5b110ee7b2 fix warning 2016-05-18 16:30:46 +03:00
yuri
f3951966f3 fix popup 2016-05-18 16:25:20 +03:00
yuri
f580f0ba60 namimng 2016-05-18 12:34:53 +03:00
yuri
a9e4ad3833 sidePanels refactoring 2016-05-18 11:22:18 +03:00
yuri
3e6c88eb27 lead-document relationship 2016-05-17 17:16:33 +03:00
yuri
7c4ef3f00f query: use distinct instead of group by id 2016-05-17 15:51:08 +03:00
yuri
762d7f71c6 fix date time 2016-05-17 11:18:30 +03:00
yuri
50cc658c20 fix campaign image 2016-05-16 16:09:18 +03:00
yuri
21a59cb198 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-05-16 12:56:36 +03:00
Taras Machyshyn
38ba3461be Fixed SemVer for PHP 7 2016-05-16 12:55:51 +03:00
yuri
f70ee27809 remove notes from user layout 2016-05-16 10:59:08 +03:00
yuri
674c1588d9 iOS 9 support 2016-05-13 12:40:26 +03:00
yuri
e9eb5402cc fix selector 2016-05-11 17:35:55 +03:00
yuri
7f746a72bb ui performance oprtimization 2016-05-11 16:22:02 +03:00
yuri
bde6a810c4 cleanup 2016-05-11 11:42:12 +03:00
yuri
ff3e7d1c5b fit add dashlet modal height 2016-05-11 11:41:27 +03:00
yuri
7ecb63189a fix full form from quick edit 2016-05-11 10:56:33 +03:00
yuri
eb1e16d6bd phone/email search more optimized way 2016-05-10 16:45:43 +03:00
yuri
f17b5d7244 small fix in RDB 2016-05-10 13:04:04 +03:00
yuri
a5b44e9fdf entity manager: text filter fields 2016-05-10 12:34:29 +03:00
yuri
734219dbd4 customUrl portal layout 2016-05-09 11:26:02 +03:00
yuri
45c351ab39 user entity small fixes 2016-05-09 11:22:06 +03:00
yuri
74aa55a8aa store sent emails 2016-05-09 11:19:14 +03:00
yuri
64b3a88ba0 fix shared calendar 2016-05-09 11:06:12 +03:00
yuri
08ff00f602 fix user select manager 2016-05-09 11:05:52 +03:00
yuri
0d9853311a inbox dashlet layout change 2016-05-06 12:54:24 +03:00
yuri
b1d17f9e84 fix shared calendar store 2016-05-06 12:02:00 +03:00
yuri
3a6041a8df shared calendar improvements 2016-05-06 11:57:37 +03:00
yuri
023171f19e merge 2016-05-05 15:17:29 +03:00
yuri
546fed83c8 version 4.0.6 2016-05-05 15:15:54 +03:00
yuri
6b37ce80fd fix kb 2016-05-05 15:14:32 +03:00
yuri
a98c8b3bcd naming 2016-05-05 13:08:28 +03:00
yuri
7bd3dd26cf notificationSoundsDisabled 2016-05-05 13:03:15 +03:00
yuri
fe7684b46c naming 2016-05-05 12:56:16 +03:00
yuri
ea5dcfe630 entity manager trimming 2016-05-05 12:12:40 +03:00
yuri
ad5f29338b field manager: trim 2016-05-05 11:59:35 +03:00
yuri
89d2b26613 cleanup 2016-05-05 11:10:56 +03:00
yuri
7206dc38bc meeting/call acl fix: allow read own record if no assigned 2016-05-05 11:08:26 +03:00
yuri
4b9ff21743 version 4.2.0 2016-05-04 17:05:09 +03:00
yuri
fbe7910349 merge 2016-05-04 16:32:19 +03:00
yuri
7b098095cb v 4.0.5 2016-05-04 16:18:36 +03:00
yuri
7966be90e7 fix acl 2016-05-04 16:18:12 +03:00
yuri
b64db89307 fix acl 2016-05-04 16:17:25 +03:00
yuri
cb9b7c3e72 code fix suite 2016-05-04 16:16:57 +03:00
yuri
a268962c15 dont save email reminder 2016-05-04 15:16:29 +03:00
yuri
36c1b56916 scheduled job mass update 2016-05-04 14:51:49 +03:00
yuri
39a2e30e3f version 2016-05-04 11:29:12 +03:00
yuri
b91d237f0b portal users activities 2016-05-04 11:25:12 +03:00
yuri
f7a0a0daa0 config textFilterUseContainsForVarchar 2016-05-04 10:52:25 +03:00
yuri
4f270d003b linkParent field entityList filtered by disabled 2016-05-04 10:23:34 +03:00
yuri
e3ebc8c2e2 portal customUrl and fix 2016-05-04 09:57:42 +03:00
yuri
c31c5618dd fix portal user sendPassword 2016-05-04 09:47:10 +03:00
yuri
341d5f8d7c fix InboundEmail namespace 2016-05-04 09:45:24 +03:00
yuri
04d2bd69c1 fix jobs 2016-04-29 16:40:05 +03:00
yuri
47efa792ee kb/documents buttons 2016-04-29 12:15:04 +03:00
yuri
a71c6789bc fix dateTimeOptional required 2016-04-29 11:49:50 +03:00
yuri
3a41aad935 fix job.php 2016-04-28 17:14:42 +03:00
yuri
fad56c1c0a email craetedAt filter 2016-04-28 17:09:08 +03:00
yuri
d6ad7f16c1 cron populate logic 2016-04-28 16:16:31 +03:00
yuri
ae89a9216d fix email importer 2016-04-28 15:18:14 +03:00
yuri
6927c82a0f Application setupSystemUser 2016-04-28 11:00:12 +03:00
yuri
e06b553653 fix avatar 2016-04-27 16:24:11 +03:00
yuri
e9a63c0c36 merge all links 2016-04-27 12:26:06 +03:00
yuri
582c23ecf4 metadata get by array 2016-04-27 12:24:42 +03:00
yuri
d86ff1e905 user mass update layout change 2016-04-27 11:43:33 +03:00
yuri
773bca8448 user mass update fix 2016-04-27 11:41:45 +03:00
yuri
a6258cd334 afterMassUpdate afterMassRemove methods 2016-04-27 11:41:35 +03:00
yuri
1cdf88ef9b cleanup 2016-04-27 11:41:14 +03:00
yuri
58ffbad422 fix jobs 2016-04-27 11:40:57 +03:00
yuri
aafcd5767e emails dashlet 2016-04-26 11:59:22 +03:00
yuri
2bfac8352b fix wysiwyg 2016-04-26 11:39:07 +03:00
yuri
954eff0c94 acl change 2016-04-26 11:36:42 +03:00
yuri
9692076ba8 int format 2016-04-26 11:08:30 +03:00
yuri
5ae64989eb system avatar color 2016-04-25 17:45:57 +03:00
yuri
3fd34fc48d show more after remove issue fix 2016-04-25 15:28:39 +03:00
yuri
94c207677f int count 2016-04-25 15:23:04 +03:00
yuri
39188f0b3a fix phone number import 2016-04-25 11:07:44 +03:00
yuri
7665ec2730 naming 2016-04-22 11:20:12 +03:00
yuri
bc57ac3065 fix task dashlet 2016-04-22 11:20:07 +03:00
yuri
6cf62736bb wysiwyg fix base tag 2016-04-20 17:59:46 +03:00
yuri
d5e78c1a7a field manager: foreign fielld 2016-04-20 15:37:18 +03:00
yuri
4a0aa1bca0 global search fix 2016-04-20 10:32:05 +03:00
yuri
2d06e8a801 calendar custom entity support 2016-04-19 16:32:40 +03:00
yuri
25b1479c23 fix layouts 2016-04-19 16:09:10 +03:00
yuri
09ab17f18b naming fix 2016-04-19 16:03:38 +03:00
yuri
be438a2f18 pl_PL fix 2016-04-19 11:51:48 +03:00
yuri
c9f91bb8da merge fix 2016-04-19 11:51:06 +03:00
yuri
cd01178151 fix activities service 2016-04-19 11:48:09 +03:00
yuri
3e8ed72246 mass update keep emails and phones 2016-04-19 11:32:56 +03:00
yuri
c500520130 remove dashlet confirmation 2016-04-18 16:45:48 +03:00
yuri
0fc2eb6817 fix lang 2016-04-18 16:42:43 +03:00
yuri
4db2f49233 fix json 2016-04-15 16:30:45 +03:00
yuri
8215c44be9 fix address format 2016-04-15 15:35:10 +03:00
yuri
dbee926a94 address formats 2016-04-15 15:28:54 +03:00
yuri
d85e13cae0 timeline today by default 2016-04-14 16:42:31 +03:00
yuri
370eee8d9d fix timeline lang 2016-04-14 16:34:26 +03:00
yuri
98f317cb1c calendar scope list for create 2016-04-14 12:43:17 +03:00
yuri
3ec9613b0e timeline and calendar less 2016-04-14 12:02:37 +03:00
yuri
8e27d50d5c about.tpl 2016-04-13 17:17:53 +03:00
yuri
fc50991317 fix deprecated ajax usage 2016-04-13 16:37:19 +03:00
yuri
52382bb958 fix deprecated moment usage 2016-04-13 16:22:41 +03:00
yuri
66fdd40659 timeline dashlet fix 2016-04-13 15:50:20 +03:00
yuri
f0f402a8b3 calendar timeline dashlet 2016-04-13 12:40:45 +03:00
yuri
591cbf4484 opp dashlet sorting 2016-04-13 10:00:10 +03:00
yuri
44d813bf5b fix person name template 2016-04-13 09:36:02 +03:00
yuri
fceff4ad7f calendar changes 2016-04-12 17:19:15 +03:00
yuri
f47b41928e fix view field 2016-04-12 17:14:58 +03:00
yuri
c559a97953 timeline dev 2016-04-11 17:16:14 +03:00
yuri
d1d64c84b0 fix typo 2016-04-11 12:46:16 +03:00
yuri
cb1f32ec3a Merge branch 'hotfix/4.0.5' 2016-04-11 11:36:13 +03:00
yuri
e76116810f fix calendar weekStart 2016-04-11 11:36:01 +03:00
yuri
974b69eed0 timeline dev 2016-04-08 17:20:38 +03:00
yuri
bb379c7a0e fix invitation 2016-04-08 10:30:00 +03:00
yuri
5664ede648 fix invitation 2016-04-08 10:28:31 +03:00
yuri
881a3db412 dev timeline 2016-04-08 10:26:53 +03:00
yuri
b74f1cc5d6 Merge branch 'hotfix/4.0.5' 2016-04-07 12:51:38 +03:00
yuri
265ec60614 remove empty side panel box 2016-04-07 12:51:22 +03:00
yuri
d79d716612 download file name escape 2016-04-07 12:51:09 +03:00
yuri
aa61f322bc fix email attachment filename parsing 2016-04-07 12:50:42 +03:00
yuri
c8d2f08c13 fix naming 2016-04-07 10:41:36 +03:00
yuri
920b6e2eba dev 2016-04-06 16:09:11 +03:00
yuri
6e7908de52 timeline dev 1 2016-04-05 16:22:13 +03:00
yuri
10de6aedbd Merge branch 'hotfix/4.0.5' 2016-04-05 11:23:46 +03:00
yuri
ce0efed7b8 fix calendar 2016-04-05 11:23:32 +03:00
yuri
204b9aa49d dev 2016-04-05 10:55:02 +03:00
yuri
ae1db7990e Merge branch 'hotfix/4.0.5' 2016-04-04 16:37:11 +03:00
yuri
18f13acfa9 assigner user field: dont display avatar in list view 2016-04-04 16:10:06 +03:00
yuri
a15b247952 fix lang 2016-04-01 16:34:00 +03:00
yuri
2e457e1f6f fix entity manager link conflicts 2016-04-01 16:30:53 +03:00
yuri
e0375a52f9 fix relationship manager ui 2016-04-01 16:30:38 +03:00
yuri
351a70015a fix email address and phone number duplicates 2016-04-01 12:27:52 +03:00
yuri
8faa001a56 use teams view 2016-04-01 11:34:48 +03:00
yuri
182fc8b65e Merge branch 'hotfix/4.0.5' 2016-04-01 11:16:34 +03:00
yuri
49b581dafd assigned user view 2016-04-01 11:16:25 +03:00
yuri
1319c54365 fix assignment permission no 2016-04-01 11:12:33 +03:00
yuri
c9db2687f5 fetch only header for emails w/ exceeding size 2016-03-31 12:38:37 +03:00
yuri
21252a6eb7 Merge branch 'hotfix/4.0.5' 2016-03-31 11:27:21 +03:00
yuri
1054050542 fix list modal 2 2016-03-31 11:27:12 +03:00
yuri
90e2d7ef0b cleanup 2016-03-31 11:24:56 +03:00
yuri
de9c1e7a20 merge 2016-03-31 11:24:30 +03:00
yuri
08b5f09c55 fix list modal 2016-03-31 11:21:46 +03:00
yuri
23a0ed86e0 cleanup 2016-03-31 10:34:50 +03:00
yuri
0ca7da454b fix notice 2016-03-31 10:33:16 +03:00
yuri
ffc22673f7 Merge branch 'hotfix/4.0.5' 2016-03-30 14:59:08 +03:00
yuri
1faa75c303 add email to quick create list 2016-03-30 12:30:01 +03:00
yuri
8136eed152 orm improvements 2016-03-30 12:29:07 +03:00
yuri
13dc6f0d76 email quick create 2016-03-30 11:19:46 +03:00
yuri
dd55141422 Merge branch 'hotfix/4.0.5' 2016-03-30 10:36:14 +03:00
yuri
d3bcadce13 fix contact filter is empty account 2016-03-30 10:36:06 +03:00
yuri
99533a5416 dont show notification about note if no access to entity 2016-03-29 16:20:05 +03:00
yuri
4a288434bc lead - case 2016-03-29 16:00:40 +03:00
yuri
9218bce3e4 kb email addition 2016-03-29 15:36:50 +03:00
yuri
b566413b78 Merge branch 'hotfix/4.0.5' 2016-03-29 15:30:46 +03:00
yuri
f624b441e4 KB: send email 2016-03-29 15:30:01 +03:00
yuri
ebe7834092 error message if restore password with empty smtp 2016-03-28 15:34:34 +03:00
yuri
9c8f54fd24 fix send test email button 2016-03-28 15:34:01 +03:00
yuri
4a20d74258 Merge branch 'hotfix/4.0.4' 2016-03-25 16:02:53 +02:00
yuri
f47915d077 fix list 2016-03-25 15:20:19 +02:00
yuri
eb2305712a user smtp info 2016-03-25 12:30:10 +02:00
yuri
f75414a5d2 fix intaller 2016-03-25 12:30:02 +02:00
yuri
a88cb05897 fix installer 2016-03-25 11:55:30 +02:00
yuri
2435ae67c9 v 2016-03-25 11:51:02 +02:00
yuri
4c49be5203 installer fix 2016-03-25 11:50:58 +02:00
yuri
2d8a1dad80 fix installer 2016-03-25 05:30:16 -04:00
yuri
a15b009133 user filters 2016-03-25 05:11:11 -04:00
yuri
25d2033b7c user select default filter active 2016-03-25 05:08:08 -04:00
yuri
9abeb6aec8 Merge branch 'hotfix/4.0.4' 2016-03-25 05:01:28 -04:00
yuri
c509eeae49 fix notices 2016-03-25 05:01:05 -04:00
yuri
2a4b0dbcb4 remove task dublicate index 2016-03-25 04:56:47 -04:00
yuri
e45a863e8d fix invitation 2016-03-24 11:27:53 +02:00
yuri
2ca40a6b4e Merge branch 'hotfix/4.0.4' 2016-03-23 17:01:30 +02:00
yuri
c4819e29e0 fix inv email date time 2016-03-23 17:01:00 +02:00
yuri
176fae228c Merge branch 'hotfix/4.0.4' 2016-03-23 16:57:33 +02:00
yuri
c0242a18e3 fix invitation email and email template 2016-03-23 16:57:18 +02:00
yuri
7a953c9a47 use word-break for long field 2016-03-23 16:09:07 +02:00
yuri
27cc0d812e url field title 2016-03-23 16:06:45 +02:00
Yuri Kuznetsov
2a1df998eb Merge pull request #123 from ecm4u/master
allow "Ends With" and "Like (%)" on varchar field
2016-03-23 14:59:56 +02:00
yuri
78573b85b5 Merge branch 'hotfix/4.0.4' 2016-03-23 14:59:09 +02:00
yuri
7a433c1890 fix list remove 2016-03-23 13:03:44 +02:00
yuri
de26d87400 list title 2016-03-23 12:02:03 +02:00
yuri
07da707503 Record controller fix sort 2016-03-23 11:40:24 +02:00
yuri
b473b19b45 fix opportunity dashlet acl 2016-03-22 16:19:44 +02:00
yuri
6c291999f4 keep meeting duration if rescheduled 2016-03-22 13:07:14 +02:00
yuri
2e92d0d3a5 case email compose fix 2016-03-22 12:03:21 +02:00
yuri
2ac5695e45 case email compose fix 2016-03-22 12:02:44 +02:00
yuri
0af4ab54f9 calendar quick view 2016-03-22 11:50:46 +02:00
yuri
8da92966de Merge branch 'hotfix/4.0.4' 2016-03-22 10:41:47 +02:00
yuri
bc3dfc7ff9 fix warnings 2016-03-18 16:05:41 +02:00
yuri
e81d5707f2 fix warnings 2016-03-18 15:26:04 +02:00
yuri
4cb42f8762 Merge branch 'hotfix/4.0.4' 2016-03-18 14:47:51 +02:00
yuri
1d2e3aff89 fix job rep 2016-03-18 14:47:21 +02:00
yuri
e36c8d6053 change year 2016-03-18 11:25:45 +02:00
yuri
4ea1d50caf fix detail getFieldViews 2016-03-18 11:19:14 +02:00
yuri
a50361126f Merge branch 'hotfix/4.0.4' 2016-03-17 12:32:49 +02:00
yuri
865a8e2abc fix E_STRICT notices 2016-03-16 17:34:50 +02:00
yuri
037ef7ea78 Merge branch 'hotfix/4.0.4' 2016-03-16 12:49:17 +02:00
yuri
5783f4e708 fix autoincrement 2016-03-16 12:48:44 +02:00
yuri
9991dd350f fix autoincrement 2016-03-16 12:48:18 +02:00
yuri
557a48ec6a isDraggable 2016-03-15 16:01:43 +02:00
yuri
d5fa18975e try catch for send password 2016-03-14 16:54:35 +02:00
yuri
23ee06e123 hide panels and buttons from portal user 2016-03-14 16:50:44 +02:00
yuri
e47eab0ce7 fix select manager 2016-03-14 15:52:06 +02:00
yuri
17ae5b6b5a fix file permissions 2016-03-14 15:51:58 +02:00
yuri
fa29bf3309 v 2016-03-14 11:43:24 +02:00
Heiko Robert
e839e505c8 en localisation for new search filter 2016-03-11 15:50:47 +01:00
Heiko Robert
7dc9c3e6b6 added search filter "Ends With", "Like" for varchar fields 2016-03-11 15:46:15 +01:00
yuri
684585278d fix email import 2016-03-11 12:02:21 +02:00
yuri
e303be8155 fix german salutations 2016-03-10 15:17:40 +02:00
yuri
f1285f0615 fix default side panel tpl 2016-03-10 12:46:21 +02:00
yuri
3fd1974d86 fix build permissions 2016-03-09 15:57:13 +02:00
yuri
d48716e65a account: text search by email address 2016-03-09 11:20:57 +02:00
yuri
a0432051f4 fix lead service 2016-03-09 11:03:56 +02:00
yuri
076c3aa65b trim search 2016-03-09 10:44:32 +02:00
yuri
92abd16032 remove notification if record removed 2016-03-04 12:40:30 +02:00
yuri
e6632066a6 aclPortal case: default status readOnly 2016-03-04 12:28:43 +02:00
986 changed files with 32966 additions and 9253 deletions

View File

@@ -34,7 +34,7 @@ module.exports = function (grunt) {
'client/lib/jquery.autocomplete.js',
'client/lib/bootstrap.min.js',
'client/lib/bootstrap-datepicker.js',
'client/lib/bull.min.js',
'client/lib/bull.js',
'client/src/namespace.js',
'client/src/exceptions.js',
'client/src/loader.js',
@@ -214,8 +214,11 @@ module.exports = function (grunt) {
},
src: [
'build/EspoCRM-<%= pkg.version %>/install',
'build/EspoCRM-<%= pkg.version %>/portal',
'build/EspoCRM-<%= pkg.version %>/api',
'build/EspoCRM-<%= pkg.version %>/api/v1',
'build/EspoCRM-<%= pkg.version %>/api/v1/portal-access',
'build/EspoCRM-<%= pkg.version %>',
]
}
},

View File

@@ -29,12 +29,12 @@
namespace Espo\Acl;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Attachment extends \Espo\Core\Acl\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) {
return true;
@@ -82,7 +82,7 @@ class Attachment extends \Espo\Core\Acl\Base
return false;
}
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;

View File

@@ -29,13 +29,13 @@
namespace Espo\Acl;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Email extends \Espo\Core\Acl\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
@@ -60,7 +60,7 @@ class Email extends \Espo\Core\Acl\Base
return false;
}
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('assignedUserId')) {
return true;
@@ -76,5 +76,46 @@ class Email extends \Espo\Core\Acl\Base
return false;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) {
return true;
}
if ($data === false) {
return false;
}
if ($data->delete === 'own') {
if ($user->id === $entity->get('assignedUserId')) {
return true;
}
if ($user->id === $entity->get('createdById')) {
return true;
}
$assignedUserIdList = $entity->getLinkMultipleIdList('assignedUsers');
if (count($assignedUserIdList) === 1 && $entity->hasLinkMultipleId('assignedUsers', $user->id)) {
return true;
}
return false;
}
if ($this->checkEntity($user, $entity, $data, 'delete')) {
return true;
}
if ($data->edit !== 'no' || $data->create !== 'no') {
if ($entity->get('createdById') === $user->id) {
if ($entity->get('status') !== 'Sent' && $entity->get('status') !== 'Archived') {
return true;
}
}
}
return false;
}
}

View File

@@ -29,12 +29,12 @@
namespace Espo\Acl;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class EmailFilter extends \Espo\Core\Acl\Base
{
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($entity->has('parentId') && $entity->has('parentType')) {
$parentType = $entity->get('parentType');
@@ -42,11 +42,14 @@ class EmailFilter extends \Espo\Core\Acl\Base
if (!$parentType || !$parentId) return;
$parent = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($parent->getEntityType() === 'User') {
return $parent->id === $user->id;
}
if ($parent && $parent->has('assignedUserId') && $parent->get('assignedUserId') === $user->id) {
return true;
}
}
return;
}
}

View File

@@ -29,12 +29,12 @@
namespace Espo\Acl;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Notification extends \Espo\Core\Acl\Base
{
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
return true;

View File

@@ -29,12 +29,12 @@
namespace Espo\AclPortal;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Attachment extends \Espo\Core\AclPortal\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) {
return true;
@@ -82,7 +82,7 @@ class Attachment extends \Espo\Core\AclPortal\Base
return false;
}
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;

View File

@@ -29,13 +29,13 @@
namespace Espo\AclPortal;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Email extends \Espo\Core\AclPortal\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
@@ -60,7 +60,7 @@ class Email extends \Espo\Core\AclPortal\Base
return false;
}
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;

View File

@@ -29,12 +29,12 @@
namespace Espo\AclPortal;
use \Espo\Entities\User;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Notification extends \Espo\Core\AclPortal\Base
{
public function checkIsOwner(User $user, Entity $entity)
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
return true;

View File

@@ -47,8 +47,21 @@ class App extends \Espo\Core\Controllers\Base
$user->loadLinkMultipleField('accounts');
}
$userData = $user->getValues();
$emailAddressList = [];
foreach ($user->get('emailAddresses') as $emailAddress) {
if ($emailAddress->get('invalid')) continue;
if ($user->get('emailAddrses') === $emailAddress->get('name')) continue;
$emailAddressList[] = $emailAddress->get('name');
}
if ($user->get('emailAddrses')) {
array_unshift($emailAddressList, $user->get('emailAddrses'));
}
$userData['emailAddressList'] = $emailAddressList;
return array(
'user' => $user->getValues(),
'user' => $userData,
'acl' => $this->getAcl()->getMap(),
'preferences' => $preferences,
'token' => $this->getUser()->get('token')

View File

@@ -32,6 +32,7 @@ namespace Espo\Controllers;
use \Espo\Core\Exceptions\BadRequest;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\NotFound;
class Email extends \Espo\Core\Controllers\Record
{
@@ -51,22 +52,48 @@ class Email extends \Espo\Core\Controllers\Record
throw new BadRequest();
}
if (!$this->getAcl()->checkScope('Email')) {
throw new Forbidden();
}
if (is_null($data['password'])) {
if ($data['type'] == 'preferences') {
if (!$this->getUser()->isAdmin() && $data['id'] != $this->getUser()->id) {
if (!$this->getUser()->isAdmin() && $data['id'] !== $this->getUser()->id) {
throw new Forbidden();
}
$preferences = $this->getEntityManager()->getEntity('Preferences', $data['id']);
if (!$preferences) {
throw new Error();
throw new NotFound();
}
$data['password'] = $this->getContainer()->get('crypt')->decrypt($preferences->get('smtpPassword'));
if (is_null($data['password'])) {
$data['password'] = $this->getContainer()->get('crypt')->decrypt($preferences->get('smtpPassword'));
}
} else if ($data['type'] == 'emailAccount') {
if (!$this->getAcl()->checkScope('EmailAccount')) {
throw new Forbidden();
}
if (!empty($data['id'])) {
$emailAccount = $this->getEntityManager()->getEntity('EmailAccount', $data['id']);
if (!$emailAccount) {
throw new NotFound();
}
if (!$this->getUser()->isAdmin()) {
if ($emailAccount->get('assigniedUserId') !== $this->getUser()->id) {
throw new Forbidden();
}
}
if (is_null($data['password'])) {
$data['password'] = $this->getContainer()->get('crypt')->decrypt($emailAccount->get('smtpPassword'));
}
}
} else {
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$data['password'] = $this->getConfig()->get('smtpPassword');
if (is_null($data['password'])) {
$data['password'] = $this->getConfig()->get('smtpPassword');
}
}
}
@@ -161,5 +188,38 @@ class Email extends \Espo\Core\Controllers\Record
}
return $this->getRecordService()->retrieveFromTrashByIdList($ids);
}
public function getActionGetFoldersNotReadCounts(&$params, $request, $data)
{
return $this->getRecordService()->getFoldersNotReadCounts();
}
protected function fetchListParamsFromRequest(&$params, $request, $data)
{
parent::fetchListParamsFromRequest($params, $request, $data);
$folderId = $request->get('folderId');
if ($folderId) {
$params['folderId'] = $request->get('folderId');
}
}
public function postActionMoveToFolder($params, $data)
{
if (!empty($data['ids'])) {
$ids = $data['ids'];
} else {
if (!empty($data['id'])) {
$ids = [$data['id']];
} else {
throw new BadRequest();
}
}
if (empty($data['folderId'])) {
throw new BadRequest();
}
return $this->getRecordService()->moveToFolderByIdList($ids, $data['folderId']);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\BadRequest;
class EmailFolder extends \Espo\Core\Controllers\Record
{
public function postActionMoveUp($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
$this->getRecordService()->moveUp($data['id']);
return true;
}
public function postActionMoveDown($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
$this->getRecordService()->moveDown($data['id']);
return true;
}
public function getActionListAll()
{
return $this->getRecordService()->listAll();
}
}

View File

@@ -78,14 +78,20 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (!empty($data['sortDirection'])) {
$params['asc'] = $data['sortDirection'] === 'asc';
}
if (isset($data['textFilterFields']) && is_array($data['textFilterFields'])) {
$params['textFilterFields'] = $data['textFilterFields'];
}
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
if ($result) {
$tabList = $this->getConfig()->get('tabList', []);
$tabList[] = $name;
$this->getConfig()->set('tabList', $tabList);
$this->getConfig()->save();
if (!in_array($name, $tabList)) {
$tabList[] = $name;
$this->getConfig()->set('tabList', $tabList);
$this->getConfig()->save();
}
$this->getContainer()->get('dataManager')->rebuild();
} else {

View File

@@ -94,17 +94,17 @@ class Extension extends \Espo\Core\Controllers\Record
return true;
}
public function actionCreate($params, $data)
public function actionCreate($params, $data, $request)
{
throw new Forbidden();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
throw new Forbidden();
}
@@ -139,12 +139,12 @@ class Extension extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
public function actionCreateLink($params, $data)
public function actionCreateLink($params, $data, $request)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data)
public function actionRemoveLink($params, $data, $request)
{
throw new Forbidden();
}

View File

@@ -43,12 +43,12 @@ class Import extends \Espo\Core\Controllers\Record
}
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
throw new BadRequest();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
throw new BadRequest();
}
@@ -58,12 +58,12 @@ class Import extends \Espo\Core\Controllers\Record
throw new BadRequest();
}
public function actionCreateLink($params, $data)
public function actionCreateLink($params, $data, $request)
{
throw new BadRequest();
}
public function actionRemoveLink($params, $data)
public function actionRemoveLink($params, $data, $request)
{
throw new BadRequest();
}

View File

@@ -41,17 +41,17 @@ class Job extends \Espo\Core\Controllers\Record
}
}
public function actionCreate($params, $data)
public function actionCreate($params, $data, $request)
{
throw new Forbidden();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
throw new Forbidden();
}
@@ -66,12 +66,12 @@ class Job extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
public function actionCreateLink($params, $data)
public function actionCreateLink($params, $data, $request)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data)
public function actionRemoveLink($params, $data, $request)
{
throw new Forbidden();
}

View File

@@ -37,9 +37,6 @@ class Notification extends \Espo\Core\Controllers\Record
public function actionList($params, $data, $request)
{
$scope = $params['scope'];
$id = $params['id'];
$userId = $this->getUser()->id;
$offset = intval($request->get('offset'));

View File

@@ -132,6 +132,7 @@ class Preferences extends \Espo\Core\Controllers\Base
$entity->set('smtpEmailAddress', $user->get('emailAddress'));
$entity->set('name', $user->get('name'));
$entity->set('isPortalUser', $user->get('isPortalUser'));
$entity->clear('smtpPassword');

View File

@@ -90,4 +90,23 @@ class Settings extends \Espo\Core\Controllers\Base
return $this->getConfigData();
}
public function postActionTestLdapConnection($params, $data)
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
if (!isset($data['password'])) {
$data['password'] = $this->getConfig()->get('ldapPassword');
}
$ldapUtils = new \Espo\Core\Utils\Authentication\LDAP\Utils();
$options = $ldapUtils->normalizeOptions($data);
$ldapClient = new \Espo\Core\Utils\Authentication\LDAP\Client($options);
$ldapClient->bind(); //an exception if no connection
return true;
}
}

View File

@@ -118,5 +118,15 @@ class Acl
{
return $this->getAclManager()->getScopeForbiddenFieldList($this->getUser(), $scope, $action, $thresholdLevel);
}
public function checkUserPermission($target, $permissionType = 'userPermission')
{
return $this->getAclManager()->checkUserPermission($this->getUser(), $target, $permissionType);
}
public function checkAssignmentPermission($target)
{
return $this->getAclManager()->checkAssignmentPermission($this->getUser(), $target);
}
}

View File

@@ -66,6 +66,13 @@ class Base implements Injectable
return $this->injections[$name];
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
protected function addDependency($name)
{
$this->dependencies[] = $name;

View File

@@ -97,8 +97,6 @@ class Table
throw new Error('User must be fetched before ACL check.');
}
$this->user->loadLinkMultipleField('teams');
if ($fileManager) {
$this->fileManager = $fileManager;
}
@@ -119,7 +117,7 @@ class Table
protected function initCacheFilePath()
{
$this->cacheFilePath = 'data/cache/application/acl/' . $this->user->id . '.php';
$this->cacheFilePath = 'data/cache/application/acl/' . $this->getUser()->id . '.php';
}
protected function getUser()
@@ -192,7 +190,7 @@ class Table
$aclTableList = [];
$fieldTableList = [];
if (!$this->user->isAdmin()) {
if (!$this->getUser()->isAdmin()) {
$roleList = $this->getRoleList();
foreach ($roleList as $role) {
@@ -209,6 +207,7 @@ class Table
$this->applyDefault($aclTable, $fieldTable);
$this->applyDisabled($aclTable, $fieldTable);
$this->applyMandatory($aclTable, $fieldTable);
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
} else {
$aclTable = (object) [];
foreach ($this->getScopeList() as $scope) {
@@ -242,7 +241,7 @@ class Table
$this->fillFieldTableQuickAccess();
if (!$this->user->isAdmin()) {
if (!$this->getUser()->isAdmin()) {
foreach ($this->valuePermissionList as $permission) {
$this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get('app.'.$this->type.'.default.' . $permission, 'yes'));
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
@@ -266,11 +265,17 @@ class Table
$roleList = [];
$userRoleList = $this->getUser()->get('roles');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$teamList = $this->getUser()->get('teams');
if (!(is_array($teamList) || $teamList instanceof \Traversable)) {
throw new Error();
}
foreach ($teamList as $team) {
$teamRoleList = $team->get('roles');
foreach ($teamRoleList as $role) {
@@ -396,7 +401,7 @@ class Table
protected function applyDefault(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
@@ -461,7 +466,7 @@ class Table
protected function applyMandatory(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
@@ -508,7 +513,7 @@ class Table
protected function applyDisabled(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
@@ -520,6 +525,19 @@ class Table
}
}
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists)
{
if ($this->getUser()->get('isPortalUser')) {
foreach ($this->getScopeList() as $scope) {
$table->$scope = false;
unset($fieldTable->$scope);
}
foreach ($this->valuePermissionList as $permission) {
$valuePermissionLists->{$permission}[] = 'no';
}
}
}
private function mergeValueList(array $list, $defaultValue)
{
$result = null;

View File

@@ -98,7 +98,10 @@ class AclManager
protected function getTable(User $user)
{
$key = spl_object_hash($user);
$key = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
@@ -181,7 +184,7 @@ class AclManager
return $this->getImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity, $action)
public function checkInTeam(User $user, Entity $entity)
{
return $this->getImplementation($entity->getEntityType())->checkInTeam($user, $entity);
}
@@ -232,5 +235,40 @@ class AclManager
if ($user->isAdmin()) return [];
return $this->getTable($user)->getScopeForbiddenFieldList($scope, $action, $thresholdLevel);
}
public function checkUserPermission(User $user, $target, $permissionType = 'userPermission')
{
$permission = $this->get($user, $permissionType);
if (is_object($target)) {
$userId = $target->id;
} else {
$userId = $target;
}
if ($user->id === $userId) return true;
if ($permission === 'no') {
return false;
}
if ($permission === 'yes') {
return true;
}
if ($permission === 'team') {
$teamIdList = $user->getLinkMultipleIdList('teams');
if (!$this->getContainer()->get('entityManager')->getRepository('User')->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
return false;
}
}
return true;
}
public function checkAssignmentPermission(User $user, $target)
{
return $this->checkUserPermission($user, $target, 'assignmentPermission');
}
}

View File

@@ -76,11 +76,17 @@ class Table extends \Espo\Core\Acl\Table
$roleList = [];
$userRoleList = $this->getUser()->get('portalRoles');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$portalRoleList = $this->getPortal()->get('portalRoles');
if (!(is_array($portalRoleList) || $portalRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($portalRoleList as $role) {
$roleList[] = $role;
}
@@ -115,11 +121,15 @@ class Table extends \Espo\Core\Acl\Table
{
foreach ($this->getScopeList() as $scope) {
$d = $this->getMetadata()->get('scopes.' . $scope);
if ($d['disabled'] || $d['portalDisabled']) {
if (!empty($d['disabled']) || !empty($d['portalDisabled'])) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists)
{
}
}

View File

@@ -315,7 +315,7 @@ class Application
return $_GET['portalId'];
}
if (!empty($_COOKIE['auth-token'])) {
$token = $this->getContainer()->get('entityManager')->getRepository('AuthToken')->where(array('token'=>$_COOKIE['auth-token']))->findOne();
$token = $this->getContainer()->get('entityManager')->getRepository('AuthToken')->where(array('token' => $_COOKIE['auth-token']))->findOne();
if ($token && $token->get('portalId')) {
return $token->get('portalId');
@@ -323,5 +323,12 @@ class Application
}
return null;
}
public function setupSystemUser()
{
$user = $this->getContainer()->get('entityManager')->getEntity('User', 'system');
$this->getContainer()->setUser($user);
$this->getContainer()->get('entityManager')->setUser($user);
}
}

View File

@@ -181,7 +181,7 @@ class Container
protected function loadNumber()
{
return new \Espo\Core\Utils\Number(
return new \Espo\Core\Utils\NumberUtil(
$this->get('config')->get('decimalMark'),
$this->get('config')->get('thousandSeparator')
);
@@ -200,7 +200,9 @@ class Container
$this->get('entityManager'),
$this->get('user'),
$this->get('acl'),
$this->get('metadata')
$this->get('aclManager'),
$this->get('metadata'),
$this->get('config')
);
}
@@ -293,7 +295,8 @@ class Container
{
return new \Espo\Core\Utils\FieldManager(
$this->get('metadata'),
$this->get('language')
$this->get('language'),
$this
);
}

View File

@@ -64,7 +64,7 @@ class Record extends Base
return $service;
}
public function actionRead($params)
public function actionRead($params, $data, $request)
{
$id = $params['id'];
$entity = $this->getRecordService()->getEntity($id);
@@ -128,12 +128,10 @@ class Record extends Base
$where = $request->get('where');
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$asc = $request->get('asc') === 'true';
$asc = $request->get('asc', 'true') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$primaryFilter = $request->get('primaryFilter');
$textFilter = $request->get('textFilter');
$boolFilterList = $request->get('boolFilterList');
if (empty($maxSize)) {
$maxSize = self::MAX_SIZE_LIMIT;
@@ -151,12 +149,8 @@ class Record extends Base
'q' => $q,
'textFilter' => $textFilter
);
if ($request->get('primaryFilter')) {
$params['primaryFilter'] = $request->get('primaryFilter');
}
if ($request->get('boolFilterList')) {
$params['boolFilterList'] = $request->get('boolFilterList');
}
$this->fetchListParamsFromRequest($params, $request, $data);
$result = $this->getRecordService()->findEntities($params);
@@ -166,6 +160,16 @@ class Record extends Base
);
}
protected function fetchListParamsFromRequest(&$params, $request, $data)
{
if ($request->get('primaryFilter')) {
$params['primaryFilter'] = $request->get('primaryFilter');
}
if ($request->get('boolFilterList')) {
$params['boolFilterList'] = $request->get('boolFilterList');
}
}
public function actionListLinked($params, $data, $request)
{
$id = $params['id'];
@@ -174,7 +178,7 @@ class Record extends Base
$where = $request->get('where');
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$asc = $request->get('asc') === 'true';
$asc = $request->get('asc', 'true') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$textFilter = $request->get('textFilter');
@@ -195,12 +199,8 @@ class Record extends Base
'q' => $q,
'textFilter' => $textFilter
);
if ($request->get('primaryFilter')) {
$params['primaryFilter'] = $request->get('primaryFilter');
}
if ($request->get('boolFilterList')) {
$params['boolFilterList'] = $request->get('boolFilterList');
}
$this->fetchListParamsFromRequest($params, $request, $data);
$result = $this->getRecordService()->findLinkedEntities($id, $link, $params);
@@ -237,9 +237,11 @@ class Record extends Base
$ids = $request->get('ids');
$where = $request->get('where');
$byWhere = $request->get('byWhere');
$selectData = $request->get('selectData');
$params = array();
if ($byWhere) {
$params['selectData'] = $selectData;
$params['where'] = $where;
} else {
$params['ids'] = $ids;
@@ -266,6 +268,9 @@ class Record extends Base
$params = array();
if (array_key_exists('where', $data) && !empty($data['byWhere'])) {
$params['where'] = json_decode(json_encode($data['where']), true);
if (array_key_exists('selectData', $data)) {
$params['selectData'] = json_decode(json_encode($data['selectData']), true);
}
} else if (array_key_exists('ids', $data)) {
$params['ids'] = $data['ids'];
}
@@ -290,9 +295,11 @@ class Record extends Base
if (array_key_exists('where', $data) && !empty($data['byWhere'])) {
$where = json_decode(json_encode($data['where']), true);
$params['where'] = $where;
if (array_key_exists('selectData', $data)) {
$params['selectData'] = json_decode(json_encode($data['selectData']), true);
}
}
if (array_key_exists('ids', $data)) {
$where = json_decode(json_encode($data['where']), true);
$params['ids'] = $data['ids'];
}
@@ -319,7 +326,13 @@ class Record extends Base
throw new BadRequest();
}
$where = json_decode(json_encode($data['where']), true);
return $this->getRecordService()->linkEntityMass($id, $link, $where);
$selectData = null;
if (isset($data['selectData']) && is_array($data['selectData'])) {
$selectData = json_decode(json_encode($data['selectData']), true);
}
return $this->getRecordService()->linkEntityMass($id, $link, $where, $selectData);
} else {
$foreignIdList = array();
if (isset($data['id'])) {
@@ -411,17 +424,34 @@ class Record extends Base
throw new BadRequest();
}
if (empty($data['targetId']) || empty($data['sourceIds']) || !is_array($data['sourceIds'])) {
if (empty($data['targetId']) || empty($data['sourceIds']) || !is_array($data['sourceIds']) || !($data['attributes'] instanceof \StdClass)) {
throw new BadRequest();
}
$targetId = $data['targetId'];
$sourceIds = $data['sourceIds'];
$attributes = get_object_vars($data['attributes']);
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
}
return $this->getRecordService()->merge($targetId, $sourceIds);
return $this->getRecordService()->merge($targetId, $sourceIds, $attributes);
}
public function postActionGetDuplicateAttributes($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'create')) {
throw new Forbidden();
}
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
}
return $this->getRecordService()->getDuplicateAttributes($data['id']);
}
}

View File

@@ -35,15 +35,21 @@ use Espo\Core\Exceptions\NotFound;
class CronManager
{
private $container;
private $config;
private $fileManager;
private $entityManager;
private $scheduledJobUtil;
const PENDING = 'Pending';
const RUNNING = 'Running';
const SUCCESS = 'Success';
const FAILED = 'Failed';
protected $lastRunTime = 'data/cache/application/cronLastRunTime.php';
@@ -149,19 +155,15 @@ class CronManager
$this->setLastRunTime(time());
$entityManager = $this->getEntityManager();
$cronJob = $this->getCronJob();
$cronScheduledJob = $this->getCronScheduledJob();
//Check scheduled jobs and create related jobs
$this->getCronJob()->markFailedJobs();
$this->getCronJob()->updateFailedJobAttempts();
$this->createJobsFromScheduledJobs();
$this->getCronJob()->removePendingJobDuplicates();
$pendingJobs = $cronJob->getPendingJobs();
$pendingJobList = $this->getCronJob()->getPendingJobList();
foreach ($pendingJobs as $job) {
$jobEntity = $entityManager->getEntity('Job', $job['id']);
foreach ($pendingJobList as $job) {
$jobEntity = $this->getEntityManager()->getEntity('Job', $job['id']);
if (!isset($jobEntity)) {
$GLOBALS['log']->error('CronManager: empty Job entity ['.$job['id'].'].');
@@ -169,7 +171,7 @@ class CronManager
}
$jobEntity->set('status', self::RUNNING);
$entityManager->saveEntity($jobEntity);
$this->getEntityManager()->saveEntity($jobEntity);
$isSuccess = true;
@@ -187,11 +189,10 @@ class CronManager
$status = $isSuccess ? self::SUCCESS : self::FAILED;
$jobEntity->set('status', $status);
$entityManager->saveEntity($jobEntity);
$this->getEntityManager()->saveEntity($jobEntity);
//set status in the schedulerJobLog
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], $status);
$this->getCronScheduledJob()->addLogRecord($job['scheduled_job_id'], $status, null, $job['target_id'], $job['target_type']);
}
}
}
@@ -213,12 +214,20 @@ class CronManager
}
$jobClass = new $className($this->container);
$method = $this->getScheduledJobUtil()->getMethodName();
$method = 'run';
if (!method_exists($jobClass, $method)) {
throw new NotFound();
}
$jobClass->$method();
$data = null;
if (!empty($job['data'])) {
$data = $job['data'];
if (Json::isJSON($data)) {
$data = Json::decode($data, true);
}
}
$jobClass->$method($data, $job['target_id'], $job['target_type']);
}
/**
@@ -248,7 +257,7 @@ class CronManager
$data = Json::decode($data, true);
}
$service->$serviceMethod($data);
$service->$serviceMethod($data, $job['target_id'], $job['target_type']);
}
/**
@@ -258,55 +267,62 @@ class CronManager
*/
protected function createJobsFromScheduledJobs()
{
$entityManager = $this->getEntityManager();
$activeScheduledJobList = $this->getCronScheduledJob()->getActiveScheduledJobList();
$activeScheduledJobs = $this->getCronScheduledJob()->getActiveJobs();
$runningScheduledJobIdList = $this->getCronJob()->getRunningScheduledJobIdList();
$cronJob = $this->getCronJob();
$runningScheduledJobs = $cronJob->getActiveJobs('scheduled_job_id', self::RUNNING, PDO::FETCH_COLUMN);
$createdJobIdList = array();
foreach ($activeScheduledJobList as $scheduledJob) {
$scheduling = $scheduledJob['scheduling'];
$createdJobs = array();
foreach ($activeScheduledJobs as $scheduledJob) {
if (in_array($scheduledJob['id'], $runningScheduledJobs)) {
try {
$cronExpression = \Cron\CronExpression::factory($scheduling);
} catch (\Exception $e) {
$GLOBALS['log']->error('CronManager (ScheduledJob ['.$scheduledJob['id'].']): Scheduling string error - '. $e->getMessage() . '.');
continue;
}
$scheduling = $scheduledJob['scheduling'];
$cronExpression = \Cron\CronExpression::factory($scheduling);
try {
$prevDate = $cronExpression->getPreviousRunDate()->format('Y-m-d H:i:s');
$previousDate = $cronExpression->getPreviousRunDate()->format('Y-m-d H:i:s');
} catch (\Exception $e) {
$GLOBALS['log']->error('CronManager: ScheduledJob ['.$scheduledJob['id'].']: CronExpression - Impossible CRON expression ['.$scheduling.']');
$GLOBALS['log']->error('CronManager (ScheduledJob ['.$scheduledJob['id'].']): Unsupported CRON expression ['.$scheduling.']');
continue;
}
if ($cronExpression->isDue()) {
$prevDate = date('Y-m-d H:i:s');
$previousDate = date('Y-m-d H:i:s');
}
$existsJob = $cronJob->getJobByScheduledJob($scheduledJob['id'], $prevDate);
$existingJob = $this->getCronJob()->getJobByScheduledJob($scheduledJob['id'], $previousDate);
if ($existingJob) continue;
if (!isset($existsJob) || empty($existsJob)) {
//create a new job
$jobEntity = $entityManager->getEntity('Job');
$jobEntity->set(array(
'name' => $scheduledJob['name'],
'status' => self::PENDING,
'scheduledJobId' => $scheduledJob['id'],
'executeTime' => $prevDate,
'method' => $scheduledJob['job'],
));
$jobEntityId = $entityManager->saveEntity($jobEntity);
if (!empty($jobEntityId)) {
$createdJobs[] = $jobEntityId;
$className = $this->getScheduledJobUtil()->get($scheduledJob['job']);
if ($className) {
if (method_exists($className, 'prepare')) {
$implementation = new $className($this->container);
$implementation->prepare($scheduledJob, $previousDate);
continue;
}
}
if (in_array($scheduledJob['id'], $runningScheduledJobIdList)) {
continue;
}
$jobEntity = $this->getEntityManager()->getEntity('Job');
$jobEntity->set(array(
'name' => $scheduledJob['name'],
'status' => self::PENDING,
'scheduledJobId' => $scheduledJob['id'],
'executeTime' => $previousDate,
'method' => $scheduledJob['job']
));
$this->getEntityManager()->saveEntity($jobEntity);
$createdJobIdList[] = $jobEntity->id;
}
return $createdJobs;
return $createdJobIdList;
}
}

View File

@@ -131,7 +131,7 @@ class HookManager
}
return $hook;
}
$GLOBALS['log']->error("Hook class '{$name}' does not exist.");
$GLOBALS['log']->error("Hook class '{$className}' does not exist.");
}
/**

View File

@@ -29,15 +29,16 @@
namespace Espo\Core\Hooks;
use \Espo\Core\Interfaces\Injectable;
use Espo\Core\Interfaces\Injectable;
abstract class Base implements Injectable
{
protected $dependencies = array(
'container',
'entityManager',
'config',
'metadata',
'acl',
'aclManager',
'user',
);
@@ -59,6 +60,13 @@ abstract class Base implements Injectable
return $this->dependencies;
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
protected function addDependency($name)
{
$this->dependencies[] = $name;
@@ -74,29 +82,39 @@ abstract class Base implements Injectable
$this->injections[$name] = $object;
}
protected function getContainer()
{
return $this->getInjection('container');
}
protected function getEntityManager()
{
return $this->injections['entityManager'];
return $this->getInjection('entityManager');
}
protected function getUser()
{
return $this->injections['user'];
return $this->getInjection('user');
}
protected function getAcl()
{
return $this->injections['acl'];
return $this->getContainer()->get('acl');
}
protected function getAclManager()
{
return $this->getInjection('aclManager');
}
protected function getConfig()
{
return $this->injections['config'];
return $this->getInjection('config');
}
protected function getMetadata()
{
return $this->injections['metadata'];
return $this->getInjection('metadata');
}
protected function getRepository()

View File

@@ -34,7 +34,7 @@ use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\DateTime;
use Espo\Core\Utils\Number;
use Espo\Core\Utils\NumberUtil;
require('vendor/zordius/lightncandy/src/lightncandy.php');
@@ -46,11 +46,19 @@ class Htmlizer
protected $config;
public function __construct(FileManager $fileManager, DateTime $dateTime, Number $number)
protected $acl;
public function __construct(FileManager $fileManager, DateTime $dateTime, NumberUtil $number, $acl = null)
{
$this->fileManager = $fileManager;
$this->dateTime = $dateTime;
$this->number = $number;
$this->acl = $acl;
}
protected function getAcl()
{
return $this->acl;
}
protected function formatNumber($value)
@@ -68,20 +76,25 @@ class Htmlizer
return $value;
}
protected function getDataFromEntity(Entity $entity)
protected function getDataFromEntity(Entity $entity, $skipLinks = false)
{
$data = $entity->toArray();
$fieldDefs = $entity->getFields();
$fieldList = array_keys($fieldDefs);
$forbidenAttributeList = [];
if ($this->getAcl()) {
$forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read');
}
foreach ($fieldList as $field) {
$type = null;
if (!empty($fieldDefs[$field]['type'])) {
$type = $fieldDefs[$field]['type'];
}
if (in_array($field, $forbidenAttributeList)) continue;
$type = $entity->getAttributeType($field);
if ($type == Entity::DATETIME) {
if (!empty($data[$field])) {
$data[$field] = $this->dateTime->convertSystemDateTime($data[$field]);
@@ -116,6 +129,8 @@ class Htmlizer
$data[$field][$k] = $this->format($data[$field][$k]);
}
}
} else if ($type === Entity::PASSWORD) {
unset($data[$field]);
}
if (array_key_exists($field, $data)) {
@@ -123,19 +138,52 @@ class Htmlizer
}
}
if (!$skipLinks) {
$relationDefs = $entity->getRelations();
foreach ($entity->getRelationList() as $relation) {
if (
!empty($relationDefs[$relation]['type'])
&&
($entity->getRelationType($relation) === 'belongsTo' || $entity->getRelationType($relation) === 'belongsToParent')
) {
$relatedEntity = $entity->get($relation);
if (!$relatedEntity) continue;
if ($this->getAcl()) {
if (!$this->getAcl()->check($relatedEntity, 'read')) continue;
}
$data[$relation] = $this->getDataFromEntity($relatedEntity, true);
}
}
}
return $data;
}
public function render(Entity $entity, $template)
public function render(Entity $entity, $template, $id = null, $additionalData = array(), $skipLinks = false)
{
$code = \LightnCandy::compile($template);
$id = uniqid('', true);
$fileName = 'data/cache/template-' . $id;
$this->fileManager->putContents($fileName, $code);
$renderer = include($fileName);
$this->fileManager->removeFile($fileName);
$data = $this->getDataFromEntity($entity);
$toRemove = false;
if ($id === null) {
$id = uniqid('', true);
$toRemove = true;
}
$fileName = 'data/cache/templates/' . $id . '.php';
$this->fileManager->putContents($fileName, $code);
$renderer = $this->fileManager->getPhpContents($fileName);
if ($toRemove) {
$this->fileManager->removeFile($fileName);
}
$data = $this->getDataFromEntity($entity, $skipLinks);
foreach ($additionalData as $k => $value) {
$data[$k] = $value;
}
$html = $renderer($data);

View File

@@ -25,14 +25,14 @@
*
* 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\Interfaces;
interface Injectable
{
public function getDependencyList();
public function inject($name, $object);
}

View File

@@ -70,7 +70,5 @@ abstract class Base
$this->container = $container;
}
abstract public function run();
}

View File

@@ -0,0 +1,43 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Loaders;
class EmailFilterManager extends Base
{
public function load()
{
$emailFilterManager = new \Espo\Core\Utils\EmailFilterManager(
$this->getContainer()->get('entityManager')
);
return $emailFilterManager;
}
}

View File

@@ -40,6 +40,7 @@ class EntityManager extends Base
'port' => $config->get('database.port'),
'dbname' => $config->get('database.dbname'),
'user' => $config->get('database.user'),
'charset' => $config->get('database.charset', 'utf8'),
'password' => $config->get('database.password'),
'metadata' => $this->getContainer()->get('metadata')->getOrmMetadata(),
'repositoryFactoryClassName' => '\\Espo\\Core\\ORM\\RepositoryFactory',

View File

@@ -39,51 +39,83 @@ class FiltersMatcher
}
public function match(Email $email, $filterList = [])
protected function matchTo(Email $email, $filter)
{
foreach ($filterList as $filter) {
if ($filter->get('from')) {
if ($this->matchString(strtolower($filter->get('from')), strtolower($email->get('from')))) {
return true;
}
}
if ($filter->get('to')) {
if ($email->get('to')) {
$toArr = explode(';', $email->get('to'));
foreach ($toArr as $to) {
if ($this->matchString(strtolower($filter->get('to')), strtolower($to))) {
return true;
}
}
}
}
if ($filter->get('subject')) {
if ($this->matchString($filter->get('subject'), $email->get('name'))) {
if ($email->get('to')) {
$toArr = explode(';', $email->get('to'));
foreach ($toArr as $to) {
if ($this->matchString(strtolower($filter->get('to')), strtolower($to))) {
return true;
}
}
}
}
public function match(Email $email, $subject, $skipBody = false)
{
if (is_array($subject) || $subject instanceof \Traversable) {
$filterList = $subject;
} else {
$filterList = [$subject];
}
foreach ($filterList as $filter) {
$filterCount = 0;
if ($filter->get('from')) {
$filterCount++;
if (!$this->matchString(strtolower($filter->get('from')), strtolower($email->get('from')))) {
continue;
}
}
if ($filter->get('to')) {
$filterCount++;
if (!$this->matchTo($email, $filter)) {
continue;
}
}
if ($filter->get('subject')) {
$filterCount++;
if (!$this->matchString($filter->get('subject'), $email->get('name'))) {
continue;
}
}
$wordList = $filter->get('bodyContains');
if (!empty($wordList)) {
$filterCount++;
if ($skipBody) {
continue;
}
if (!$this->matchBody($email, $filter)) {
continue;
}
}
if ($filterCount) {
return true;
}
}
return false;
}
public function matchBody(Email $email, $filterList = [])
protected function matchBody(Email $email, $filter)
{
foreach ($filterList as $filter) {
if ($filter->get('bodyContains')) {
$phraseList = $filter->get('bodyContains');
$body = $email->get('body');
$bodyPlain = $email->get('bodyPlain');
foreach ($phraseList as $phrase) {
if (stripos($bodyPlain, $phrase) !== false) {
return true;
}
if (stripos($body, $phrase) !== false) {
return true;
}
}
$phraseList = $filter->get('bodyContains');
$body = $email->get('body');
$bodyPlain = $email->get('bodyPlain');
foreach ($phraseList as $phrase) {
if (stripos($bodyPlain, $phrase) !== false) {
return true;
}
if (stripos($body, $phrase) !== false) {
return true;
}
}
return false;
}
protected function matchString($pattern, $value)

View File

@@ -38,16 +38,13 @@ class Importer
{
private $entityManager;
private $fileManager;
private $config;
private $filtersMatcher;
public function __construct($entityManager, $fileManager, $config)
public function __construct($entityManager, $config)
{
$this->entityManager = $entityManager;
$this->fileManager = $fileManager;
$this->config = $config;
$this->filtersMatcher = new FiltersMatcher();
}
@@ -56,27 +53,31 @@ class Importer
{
return $this->entityManager;
}
protected function getConfig()
{
return $this->config;
}
protected function getFileManager()
{
return $this->fileManager;
}
protected function getFiltersMatcher()
{
return $this->filtersMatcher;
}
public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [])
public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null)
{
try {
$email = $this->getEntityManager()->getEntity('Email');
$subject = $message->subject;
$email->set('isBeingImported', true);
$subject = '';
if (isset($message->subject)) {
$subject = $message->subject;
}
if (!empty($subject) && is_string($subject)) {
$subject = trim($subject);
}
if ($subject !== '0' && empty($subject)) {
$subject = '(No Subject)';
}
@@ -87,12 +88,14 @@ class Importer
$email->set('attachmentsIds', []);
if ($assignedUserId) {
$email->set('assignedUserId', $assignedUserId);
$email->set('assignedUsersIds', [$assignedUserId]);
$email->addLinkMultipleId('assignedUsers', $assignedUserId);
}
$email->set('teamsIds', $teamsIdList);
if (!empty($userIdList)) {
$email->set('usersIds', $userIdList);
foreach ($userIdList as $uId) {
$email->addLinkMultipleId('users', $uId);
}
}
$fromArr = $this->getAddressListFromMessage($message, 'from');
@@ -112,7 +115,13 @@ class Importer
$email->set('cc', implode(';', $ccArr));
$email->set('replyTo', implode(';', $replyToArr));
if ($this->getFiltersMatcher()->match($email, $filterList)) {
if ($folderData) {
foreach ($folderData as $uId => $folderId) {
$email->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
}
}
if ($this->getFiltersMatcher()->match($email, $filterList, true)) {
return false;
}
@@ -137,7 +146,15 @@ class Importer
}
}
$this->getEntityManager()->saveEntity($duplicate);
if ($folderData) {
foreach ($folderData as $uId => $folderId) {
$email->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
}
}
$duplicate->set('isBeingImported', true);
$this->getEntityManager()->saveEntity($duplicate);
if (!empty($teamsIdList)) {
foreach ($teamsIdList as $teamId) {
@@ -154,8 +171,8 @@ class Importer
$email->set('dateSent', $dateSent);
}
} else {
$email->set('dateSent', date('Y-m-d H:i:s'));
}
$email->set('dateSent', date('Y-m-d H:i:s'));
}
if (isset($message->deliveryDate)) {
$dt = new \DateTime($message->deliveryDate);
if ($dt) {
@@ -166,32 +183,42 @@ class Importer
$inlineIds = array();
if ($message->isMultipart()) {
foreach (new \RecursiveIteratorIterator($message) as $part) {
$this->importPartDataToEmail($email, $part, $inlineIds);
if (!$fetchOnlyHeader) {
if ($message->isMultipart()) {
foreach (new \RecursiveIteratorIterator($message) as $part) {
$this->importPartDataToEmail($email, $part, $inlineIds);
}
} else {
$this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain');
}
if (!$email->get('body') && $email->get('bodyPlain')) {
$email->set('body', $email->get('bodyPlain'));
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
if (strpos($body, 'cid:' . $cid) !== false) {
$body = str_replace('cid:' . $cid, '?entryPoint=attachment&amp;id=' . $attachmentId, $body);
} else {
$email->addLinkMultipleId('attachments', $attachmentId);
}
}
$email->set('body', $body);
}
if ($this->getFiltersMatcher()->match($email, $filterList)) {
return false;
}
} else {
$this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain');
}
if (!$email->get('body') && $email->get('bodyPlain')) {
$email->set('body', $email->get('bodyPlain'));
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
$body = str_replace('cid:' . $cid, '?entryPoint=attachment&amp;id=' . $attachmentId, $body);
}
$email->set('body', $body);
}
if ($this->getFiltersMatcher()->matchBody($email, $filterList)) {
return false;
$email->set('body', '(Not fetched)');
$email->set('isHtml', false);
}
$parentFound = false;
$replied = null;
if (isset($message->inReplyTo) && !empty($message->inReplyTo)) {
$arr = explode(' ', $message->inReplyTo);
$inReplyTo = $arr[0];
@@ -209,6 +236,7 @@ class Importer
$reference = str_replace(array('/', '@'), " ", trim($reference, '<>'));
$parentType = $parentId = null;
$emailSent = PHP_INT_MAX;
$number = null;
$n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number);
if ($n == 4 && $emailSent < time()) {
if (!empty($parentType) && !empty($parentId)) {
@@ -241,6 +269,15 @@ class Importer
}
}
if (!$parentFound) {
if ($replied && $replied->get('parentId') && $replied->get('parentType')) {
$parentFound = $this->getEntityManager()->getEntity($replied->get('parentType'), $replied->get('parentId'));
if ($parentFound) {
$email->set('parentType', $replied->get('parentType'));
$email->set('parentId', $replied->get('parentId'));
}
}
}
if (!$parentFound) {
$from = $email->get('from');
if ($from) {
@@ -291,15 +328,15 @@ class Importer
$email->set('parentId', $account->id);
return true;
} else {
$lead = $this->getEntityManager()->getRepository('Lead')->where(array(
'emailAddress' => $emailAddress
))->findOne();
if ($lead) {
$email->set('parentType', 'Lead');
$email->set('parentId', $lead->id);
return true;
}
}
$lead = $this->getEntityManager()->getRepository('Lead')->where(array(
'emailAddress' => $emailAddress
))->findOne();
if ($lead) {
$email->set('parentType', 'Lead');
$email->set('parentId', $lead->id);
return true;
}
}
}
}
@@ -352,6 +389,8 @@ class Importer
} else if (strpos(strtolower($part->ContentDisposition), 'inline') === 0) {
$contentDisposition = 'inline';
}
} else if (isset($part->contentID)) {
$contentDisposition = 'inline';
}
if (empty($type)) {
@@ -395,7 +434,6 @@ class Importer
$contentId = null;
if ($contentDisposition) {
if ($contentDisposition === 'attachment') {
$fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition);
if ($fileName) {
@@ -436,13 +474,10 @@ class Importer
$content = base64_decode($content);
}
$attachment->set('size', strlen($content));
$attachment->set('contents', $content);
$this->getEntityManager()->saveEntity($attachment);
$path = $this->getEntityManager()->getRepository('Attachment')->getFilePath($attachment);
$this->getFileManager()->putContents($path, $content);
if ($disposition == 'attachment') {
$attachmentsIds = $email->get('attachmentsIds');
$attachmentsIds[] = $attachment->id;
@@ -454,24 +489,61 @@ class Importer
} catch (\Exception $e) {}
}
protected function fetchFileNameFromContentDisposition($contentDisposition)
protected function decodeAttachmentFileName($fileName)
{
$m = array();
if (preg_match('/filename="?([^"]+)"?/i', $contentDisposition, $m)) {
$fileName = $m[1];
return $fileName;
} else if (preg_match('/filename\*="?([^"]+)"?/i', $contentDisposition, $m)) {
$fileName = $m[1];
if ($fileName && stripos($fileName, "''") !== false) {
list($encoding, $fileName) = explode("''", $fileName);
$fileName = rawurldecode($fileName);
if (strtoupper($encoding) !== 'UTF-8') {
if ($fileName && stripos($fileName, "''") !== false) {
list($encoding, $fileName) = explode("''", $fileName);
$fileName = rawurldecode($fileName);
if (strtoupper($encoding) !== 'UTF-8') {
if ($encoding) {
$fileName = mb_convert_encoding($fileName, 'UTF-8', $encoding);
}
return $fileName;
}
}
return false;
return $fileName;
}
protected function fetchFileNameFromContentDisposition($contentDisposition)
{
$contentDisposition = preg_replace('/\\\\"/', "{{_!Q!U!O!T!E!_}}", $contentDisposition);
$fileName = false;
$m = array();
if (preg_match('/filename="([^"]+)";?/i', $contentDisposition, $m)) {
$fileName = $m[1];
} else if (preg_match('/filename=([^";]+);?/i', $contentDisposition, $m)) {
$fileName = $m[1];
} else if (preg_match('/filename\*="([^"]+)";?/i', $contentDisposition, $m)) {
$fileName = $m[1];
$fileName = $this->decodeAttachmentFileName($fileName);
} else if (preg_match('/filename\*=([^";]+);?/i', $contentDisposition, $m)) {
$fileName = $m[1];
$fileName = $this->decodeAttachmentFileName($fileName);
} else {
$fileName = '';
foreach (['0', '1'] as $i) {
if (preg_match('/filename\*'.$i.'[\*]?="([^"]+)";?/i', $contentDisposition, $m)) {
$part = $m[1];
$fileName .= $part;
} else if (preg_match('/filename\*'.$i.'[\*]?=([^";]+);?/i', $contentDisposition, $m)) {
$part = $m[1];
$fileName .= $part;
}
}
if ($fileName === '') {
$fileName = null;
} else {
$fileName = $this->decodeAttachmentFileName($fileName);
}
}
if ($fileName) {
$fileName = str_replace('{{_!Q!U!O!T!E!_}}', '"', $fileName);
}
return $fileName;
}
protected function getContentFromPart($part)

View File

@@ -34,6 +34,7 @@ use Zend\Mail\Header\HeaderInterface;
use Zend\Mime;
use Zend\Mail\Storage\Exception;
use Zend\Mail\Storage\AbstractStorage;
use Zend\Stdlib\ErrorHandler;
class Message extends \Zend\Mail\Storage\Message
{

View File

@@ -104,10 +104,10 @@ class Sender
$opts['connection_config']['ssl'] = strtolower($params['security']);
}
if (in_array('fromName', $params)) {
if (array_key_exists('fromName', $params)) {
$this->params['fromName'] = $params['fromName'];
}
if (in_array('fromAddress', $params)) {
if (array_key_exists('fromAddress', $params)) {
$this->params['fromAddress'] = $params['fromAddress'];
}
@@ -340,15 +340,12 @@ class Sender
$message->setEncoding('UTF-8');
try {
$rand = mt_rand(1000, 9999);
if ($email->get('parentType') && $email->get('parentId')) {
$messageId = '' . $email->get('parentType') .'/' . $email->get('parentId') . '/' . time() . '/' . $rand . '@espo';
$messageId = $email->get('messageId');
if (empty($messageId) || !is_string($messageId) || strlen($messageId) < 4) {
$messageId = $this->generateMessageId($email);
$email->set('messageId', '<' . $messageId . '>');
} else {
$messageId = '' . md5($email->get('name')) . '/' . time() . '/' . $rand . '@espo';
}
if ($email->get('isSystem')) {
$messageId .= '-system';
$messageId = substr($messageId, 1, strlen($messageId) - 2);
}
$messageIdHeader = new \Zend\Mail\Header\MessageId();
@@ -357,7 +354,6 @@ class Sender
$this->transport->send($message);
$email->set('messageId', '<' . $messageId . '>');
$email->set('status', 'Sent');
$email->set('dateSent', date("Y-m-d H:i:s"));
} catch (\Exception $e) {
@@ -366,5 +362,21 @@ class Sender
$this->useGlobal();
}
static public function generateMessageId(Email $email)
{
$rand = mt_rand(1000, 9999);
if ($email->get('parentType') && $email->get('parentId')) {
$messageId = '' . $email->get('parentType') .'/' . $email->get('parentId') . '/' . time() . '/' . $rand . '@espo';
} else {
$messageId = '' . md5($email->get('name')) . '/' . time() . '/' . $rand . '@espo';
}
if ($email->get('isSystem')) {
$messageId .= '-system';
}
return $messageId;
}
}

View File

@@ -53,6 +53,13 @@ class Base implements Injectable
{
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
protected function addDependency($name)
{
$this->dependencies[] = $name;

View File

@@ -105,7 +105,27 @@ class Entity extends \Espo\ORM\Entity
}
}
}
}
public function setLinkMultipleColumn($field, $column, $id, $value)
{
$columnsField = $field . 'Columns';
if (!$this->hasField($columnsField)) {
return;
}
$object = $this->get($columnsField);
if (!isset($object) || !($object instanceof \StdClass)) {
$object = (object) [];
}
if (!isset($object->$id)) {
$object->$id = (object) [];
}
if (!isset($object->$id->$column)) {
$object->$id->$column = (object) [];
}
$object->$id->$column = $value;
$this->set($columnsField, $object);
}
public function setLinkMultipleIdList($field, array $idList)

View File

@@ -52,6 +52,13 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$this->dependencies[] = $name;
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
public function inject($name, $object)
{
$this->injections[$name] = $object;
@@ -72,6 +79,16 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
return $this->getInjection('metadata');
}
public function __construct($entityType, EntityManager $entityManager, EntityFactory $entityFactory)
{
parent::__construct($entityType, $entityManager, $entityFactory);
$this->init();
}
protected function init()
{
}
public function handleSelectParams(&$params)
{
$this->handleEmailAddressParams($params);
@@ -81,7 +98,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function handleCurrencyParams(&$params)
{
$entityName = $this->entityName;
$entityType = $this->entityType;
$metadata = $this->getMetadata();
@@ -89,7 +106,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
return;
}
$defs = $metadata->get('entityDefs.' . $entityName);
$defs = $metadata->get('entityDefs.' . $entityType);
foreach ($defs['fields'] as $field => $d) {
if (isset($d['type']) && $d['type'] == 'currency') {
@@ -101,7 +118,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
$alias = Util::toUnderScore($field) . "_currency_alias";
$params['customJoin'] .= "
LEFT JOIN currency AS `{$alias}` ON {$alias}.id = ".Util::toUnderScore($entityName).".".Util::toUnderScore($field)."_currency
LEFT JOIN currency AS `{$alias}` ON {$alias}.id = ".Util::toUnderScore($entityType).".".Util::toUnderScore($field)."_currency
";
}
}
@@ -110,9 +127,9 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function handleEmailAddressParams(&$params)
{
$entityName = $this->entityName;
$entityType = $this->entityType;
$defs = $this->getEntityManager()->getMetadata()->get($entityName);
$defs = $this->getEntityManager()->getMetadata()->get($entityType);
if (!empty($defs['relations']) && array_key_exists('emailAddresses', $defs['relations'])) {
if (empty($params['leftJoins'])) {
$params['leftJoins'] = array();
@@ -132,9 +149,9 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function handlePhoneNumberParams(&$params)
{
$entityName = $this->entityName;
$entityType = $this->entityType;
$defs = $this->getEntityManager()->getMetadata()->get($entityName);
$defs = $this->getEntityManager()->getMetadata()->get($entityType);
if (!empty($defs['relations']) && array_key_exists('phoneNumbers', $defs['relations'])) {
if (empty($params['leftJoins'])) {
$params['leftJoins'] = array();
@@ -155,7 +172,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function beforeRemove(Entity $entity, array $options = array())
{
parent::beforeRemove($entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeRemove', $entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeRemove', $entity, $options);
$nowString = date('Y-m-d H:i:s', time());
if ($entity->hasAttribute('modifiedAt')) {
@@ -169,14 +186,14 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function afterRemove(Entity $entity, array $options = array())
{
parent::afterRemove($entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterRemove', $entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterRemove', $entity, $options);
}
public function remove(Entity $entity, array $options = array())
{
$result = parent::remove($entity, $options);
if ($result) {
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterRemove', $entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterRemove', $entity, $options);
}
return $result;
}
@@ -185,7 +202,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
{
parent::beforeSave($entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeSave', $entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeSave', $entity, $options);
}
protected function afterSave(Entity $entity, array $options = array())
@@ -196,12 +213,12 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
parent::afterSave($entity, $options);
$this->handleEmailAddressSave($entity);
$this->handlePhoneNumberSave($entity);
$this->handleSpecifiedRelations($entity);
$this->handleFileFields($entity);
$this->processEmailAddressSave($entity);
$this->processPhoneNumberSave($entity);
$this->processSpecifiedRelationsSave($entity);
$this->processFileFieldsSave($entity);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterSave', $entity, $options);
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterSave', $entity, $options);
}
public function save(Entity $entity, array $options = array())
@@ -215,13 +232,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
if ($entity->hasAttribute('createdAt')) {
$entity->set('createdAt', $nowString);
if (empty($options['import']) || !$entity->has('createdAt')) {
$entity->set('createdAt', $nowString);
}
}
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasAttribute('createdById')) {
$entity->set('createdById', $this->entityManager->getUser()->id);
if (empty($options['import']) || !$entity->has('createdById')) {
$entity->set('createdById', $this->entityManager->getUser()->id);
}
}
if ($entity->has('modifiedById')) {
@@ -242,13 +263,18 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
if ($entity->has('createdById')) {
$restoreData['createdById'] = $entity->get('createdById');
if (empty($options['import'])) {
$restoreData['createdById'] = $entity->get('createdById');
$entity->clear('createdById');
}
}
if ($entity->has('createdAt')) {
$restoreData['createdAt'] = $entity->get('createdAt');
if (empty($options['import'])) {
$restoreData['createdAt'] = $entity->get('createdAt');
$entity->clear('createdAt');
}
}
$entity->clear('createdById');
$entity->clear('createdAt');
}
$this->restoreData = $restoreData;
@@ -257,7 +283,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
return $result;
}
protected function handleFileFields(Entity $entity)
protected function processFileFieldsSave(Entity $entity)
{
foreach ($entity->getRelations() as $name => $defs) {
if (!isset($defs['type']) || !isset($defs['entity'])) continue;
@@ -278,23 +304,22 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
}
protected function handleEmailAddressSave(Entity $entity)
protected function processEmailAddressSave(Entity $entity)
{
if ($entity->hasRelation('emailAddresses') && $entity->hasAttribute('emailAddress')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('EmailAddress')->storeEntityEmailAddress($entity);
}
}
protected function handlePhoneNumberSave(Entity $entity)
protected function processPhoneNumberSave(Entity $entity)
{
if ($entity->hasRelation('phoneNumbers') && $entity->hasAttribute('phoneNumber')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('PhoneNumber')->storeEntityPhoneNumber($entity);
}
}
protected function handleSpecifiedRelations(Entity $entity)
protected function processSpecifiedRelationsSave(Entity $entity)
{
$relationTypeList = [$entity::HAS_MANY, $entity::MANY_MANY, $entity::HAS_CHILDREN];
foreach ($entity->getRelations() as $name => $defs) {
if (in_array($defs['type'], $relationTypeList)) {
@@ -358,15 +383,16 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
} else {
if (!empty($columns)) {
foreach ($columns as $columnName => $columnField) {
if ($columnData->$id->$columnName != $existingColumnsData->$id->$columnName) {
$toUpdateIds[] = $id;
if (isset($columnData->$id)) {
if ($columnData->$id->$columnName !== $existingColumnsData->$id->$columnName) {
$toUpdateIds[] = $id;
}
}
}
}
}
}
foreach ($specifiedIds as $id) {
if (!in_array($id, $existingIds)) {
$data = null;

View File

@@ -31,12 +31,18 @@ namespace Espo\Core\ORM;
use \Espo\Core\Interfaces\Injectable;
use \Espo\ORM\EntityFactory;
abstract class Repository extends \Espo\ORM\Repository implements Injectable
{
protected $dependencies = array();
protected $injections = array();
protected function init()
{
}
public function inject($name, $object)
{
$this->injections[$name] = $object;
@@ -51,5 +57,23 @@ abstract class Repository extends \Espo\ORM\Repository implements Injectable
{
return $this->dependencies;
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
protected function addDependency($name)
{
$this->dependencies[] = $name;
}
public function __construct($entityType, EntityManager $entityManager, EntityFactory $entityFactory)
{
parent::__construct($entityType, $entityManager, $entityFactory);
$this->init();
}
}

View File

@@ -37,6 +37,10 @@ class AclManager extends \Espo\Core\AclManager
{
protected $tableClassName = '\\Espo\\Core\\AclPortal\\Table';
private $mainManager = null;
private $portal = null;
public function getImplementation($scope)
{
if (empty($this->implementationHashMap[$scope])) {
@@ -70,16 +74,42 @@ class AclManager extends \Espo\Core\AclManager
return $this->implementationHashMap[$scope];
}
public function setMainManager($mainManager)
{
$this->mainManager = $mainManager;
}
protected function getMainManager()
{
return $this->mainManager;
}
public function setPortal($portal)
{
$this->portal = $portal;
}
protected function getPortal()
{
if ($this->portal) {
return $this->portal;
}
return $this->getContainer()->get('portal');
}
protected function getTable(User $user)
{
$key = spl_object_hash($user);
$key = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManager');
$portal = $this->getContainer()->get('portal');
$portal = $this->getPortal();
$this->tableHashMap[$key] = new $this->tableClassName($user, $portal, $config, $fileManager, $metadata, $fieldManager);
}
@@ -115,5 +145,116 @@ class AclManager extends \Espo\Core\AclManager
return $this->getImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
}
public function getMap(User $user)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getMap($user);
}
return parent::getMap($user);
}
public function getLevel(User $user, $scope, $action)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getLevel($user, $scope, $action);
}
return parent::getLevel($user, $scope, $action);
}
public function get(User $user, $permission)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->get($user, $permission);
}
return parent::get($user, $permission);
}
public function checkReadOnlyTeam(User $user, $scope)
{
if ($this->checkUserIsNotPortal($user)) {
$data = $this->getTable($user)->getScopeData($scope);
return $this->getMainManager()->checkReadOnlyTeam($user, $data);
}
return parent::checkReadOnlyTeam($user, $scope);
}
public function checkReadOnlyOwn(User $user, $scope)
{
if ($this->checkUserIsNotPortal($user)) {
$data = $this->getTable($user)->getScopeData($scope);
return $this->getMainManager()->checkReadOnlyOwn($user, $data);
}
return parent::checkReadOnlyOwn($user, $scope);
}
public function check(User $user, $subject, $action = null)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->check($user, $subject, $action);
}
return parent::check($user, $subject, $action);
}
public function checkEntity(User $user, Entity $entity, $action = 'read')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkEntity($user, $entity, $action);
}
return parent::checkEntity($user, $entity, $action);
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkIsOwner($user, $entity);
}
return parent::checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkInTeam($user, $entity);
}
return parent::checkInTeam($user, $entity);
}
public function checkScope(User $user, $scope, $action = null)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkScope($user, $scope, $action);
}
return parent::checkScope($user, $scope, $action);
}
public function checkUser(User $user, $permission, User $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkUser($user, $permission, $entity);
}
return parent::checkUser($user, $permission, $entity);
}
public function getScopeForbiddenAttributeList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
public function getScopeForbiddenFieldList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
protected function checkUserIsNotPortal($user)
{
return !$user->get('isPortalUser');
}
}

View File

@@ -49,6 +49,12 @@ class Application extends \Espo\Core\Application
$portal = $this->getContainer()->get('entityManager')->getEntity('Portal', $portalId);
if (!$portal) {
$portal = $this->getContainer()->get('entityManager')->getRepository('Portal')->where(array(
'customId' => $portalId
))->findOne();
}
if (!$portal) {
throw new NotFound();
}

View File

@@ -38,12 +38,27 @@ class Container extends \Espo\Core\Container
return $className;
}
protected function getServiceMainClassName($name, $default)
{
$metadata = $this->get('metadata');
$className = $metadata->get('app.serviceContainer.classNames.' . $name, $default);
return $className;
}
protected function loadAclManager()
{
$className = $this->getServiceClassName('aclManager', '\\Espo\\Core\\Portal\\AclManager');
return new $className(
$mainClassName = $this->getServiceMainClassName('aclManager', '\\Espo\\Core\\AclManager');
$obj = new $className(
$this->get('container')
);
$objMain = new $mainClassName(
$this->get('container')
);
$obj->setMainManager($objMain);
return $obj;
}
protected function loadAcl()

View File

@@ -29,11 +29,11 @@
namespace Espo\Core\Repositories;
use \Espo\Core\Entities\CategoryTreeItem as Entity;
use \Espo\ORM\Entity;
class CategoryTree extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, $options)
protected function afterSave(Entity $entity, array $options = array())
{
parent::afterSave($entity, $options);
@@ -86,7 +86,7 @@ class CategoryTree extends \Espo\Core\ORM\Repositories\RDB
}
}
public function afterRemove(Entity $entity, $options)
protected function afterRemove(Entity $entity, array $options = array())
{
parent::afterRemove($entity, $options);

View File

@@ -43,12 +43,14 @@ class SelectManagerFactory
private $metadata;
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Utils\Metadata $metadata, Utils\Config $config)
{
$this->entityManager = $entityManager;
$this->user = $user;
$this->acl = $acl;
$this->aclManager = $aclManager;
$this->metadata = $metadata;
$this->config = $config;
}
public function create($entityType)
@@ -68,7 +70,7 @@ class SelectManagerFactory
}
}
$selectManager = new $className($this->entityManager, $this->user, $this->acl, $this->metadata);
$selectManager = new $className($this->entityManager, $this->user, $this->acl, $this->aclManager, $this->metadata, $this->config);
$selectManager->setEntityType($entityType);
return $selectManager;

View File

@@ -33,6 +33,9 @@ use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Acl;
use \Espo\Core\AclManager;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\Config;
class Base
{
@@ -48,6 +51,8 @@ class Base
protected $metadata;
private $config;
private $seed = null;
private $userTimeZone = null;
@@ -56,13 +61,15 @@ class Base
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config)
{
$this->entityManager = $entityManager;
$this->user = $user;
$this->acl = $acl;
$this->aclManager = $aclManager;
$this->metadata = $metadata;
$this->config = $config;
}
protected function getEntityManager()
@@ -70,6 +77,11 @@ class Base
return $this->entityManager;
}
protected function getMetadata()
{
return $this->metadata;
}
protected function getUser()
{
return $this->user;
@@ -80,6 +92,16 @@ class Base
return $this->acl;
}
protected function getConfig()
{
return $this->config;
}
protected function getAclManager()
{
return $this->aclManager;
}
public function setEntityType($entityType)
{
$this->entityType = $entityType;
@@ -100,18 +122,30 @@ class Base
}
}
protected function order($sortBy, $asc, &$result)
protected function order($sortBy, $desc = false, &$result)
{
if (!empty($sortBy)) {
$result['orderBy'] = $sortBy;
$type = $this->metadata->get("entityDefs.{$this->entityType}.fields." . $result['orderBy'] . ".type");
if ($type == 'link') {
$type = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'type']);
if ($type === 'link') {
$result['orderBy'] .= 'Name';
} else if ($type == 'linkParent') {
} else if ($type === 'linkParent') {
$result['orderBy'] .= 'Type';
} else if ($type === 'enum') {
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
if ($list && is_array($list) && count($list)) {
if ($this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'isSorted'])) {
$list = asort($list);
}
if ($desc) {
$list = array_reverse($list);
}
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
return;
}
}
}
if ($asc) {
if (!$desc) {
$result['order'] = 'ASC';
} else {
$result['order'] = 'DESC';
@@ -120,7 +154,7 @@ class Base
protected function getTextFilterFieldList()
{
return $this->metadata->get("entityDefs.{$this->entityType}.collection.textFilterFields", ['name']);
return $this->getMetadata()->get("entityDefs.{$this->entityType}.collection.textFilterFields", ['name']);
}
protected function getSeed()
@@ -595,6 +629,11 @@ class Base
return $result;
}
public function buildSelectParams(array $params, $withAcl = false, $checkWherePermission = false)
{
return $this->getSelectParams($params, $withAcl, $checkWherePermission);
}
public function getSelectParams(array $params, $withAcl = false, $checkWherePermission = false)
{
$result = array();
@@ -604,7 +643,7 @@ class Base
if (!array_key_exists('asc', $params)) {
$params['asc'] = true;
}
$this->order($params['sortBy'], $params['asc'], $result);
$this->order($params['sortBy'], !$params['asc'], $result);
}
if (!isset($params['offset'])) {
@@ -667,18 +706,22 @@ class Base
}
}
protected function getUserTimeZone()
public function getUserTimeZone()
{
if (empty($this->userTimeZone)) {
$preferences = $this->getEntityManager()->getEntity('Preferences', $this->getUser()->id);
$timeZone = $preferences->get('timeZone');
$this->userTimeZone = $timeZone;
if ($preferences) {
$timeZone = $preferences->get('timeZone');
$this->userTimeZone = $timeZone;
} else {
$this->userTimeZone = 'UTC';
}
}
return $this->userTimeZone;
}
protected function convertDateTimeWhere($item)
public function convertDateTimeWhere($item)
{
$format = 'Y-m-d H:i:s';
@@ -875,6 +918,9 @@ class Base
case 'startsWith':
$part[$item['field'] . '*'] = $item['value'] . '%';
break;
case 'endsWith':
$part[$item['field'] . '*'] = '%' . $item['value'];
break;
case 'contains':
$part[$item['field'] . '*'] = '%' . $item['value'] . '%';
break;
@@ -1019,10 +1065,10 @@ class Base
return $part;
}
public function applyOrder($sortBy, $asc, &$result)
public function applyOrder($sortBy, $desc, &$result)
{
$this->prepareResult($result);
$this->order($sortBy, $asc, $result);
$this->order($sortBy, $desc, $result);
}
public function applyLimit($offset, $maxSize, &$result)
@@ -1064,12 +1110,36 @@ class Base
public function hasJoin($join, &$result)
{
return in_array($join, $result['joins']);
if (in_array($join, $result['joins'])) {
return true;
}
foreach ($result['joins'] as $item) {
if (is_array($item) && count($item) > 1) {
if ($item[1] == $join) {
return true;
}
}
}
return false;
}
public function hasLeftJoin($leftJoin, &$result)
{
return in_array($leftJoin, $result['leftJoin']);
if (in_array($leftJoin, $result['leftJoins'])) {
return true;
}
foreach ($result['leftJoins'] as $item) {
if (is_array($item) && count($item) > 1) {
if ($item[1] == $leftJoin) {
return true;
}
}
}
return false;
}
public function addJoin($join, &$result)
@@ -1163,15 +1233,21 @@ class Base
$d = array();
foreach ($fieldList as $field) {
$expression = $textFilter . '%';
if (
strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
&&
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
(
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
||
$this->getConfig()->get('textFilterUseContainsForVarchar')
)
) {
$d[$field . '*'] = '%' . $textFilter . '%';
$expression = '%' . $textFilter . '%';
} else {
$d[$field . '*'] = $textFilter . '%';
$expression = $textFilter . '%';
}
$d[$field . '*'] = $expression;
}
$result['whereClause'][] = array(
'OR' => $d

View File

@@ -65,6 +65,13 @@ abstract class Base implements Injectable
$this->dependencies[] = $name;
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
public function getDependencyList()
{
return $this->dependencies;

View File

@@ -0,0 +1,36 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Templates\Controllers;
class Event extends \Espo\Core\Controllers\Record
{
}

View File

@@ -0,0 +1,36 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Templates\Entities;
class Event extends \Espo\Core\ORM\Entity
{
}

View File

@@ -0,0 +1,39 @@
[
{
"label": "Overview",
"rows": [
[
{
"name": "name"
},
false
],
[
{
"name": "status"
},
false
],
[
{
"name": "dateStart"
},
{
"name": "dateEnd"
}
],
[
{
"name": "duration"
},
false
],
[
{
"name": "description",
"fullWidth": true
}
]
]
}
]

View File

@@ -0,0 +1,43 @@
[
{
"label": "",
"rows": [
[
{
"name": "name",
"fullWidth": true
}
],
[
{
"name": "status",
"fullWidth": true
}
],
[
{
"name": "dateStart",
"fullWidth": true
}
],
[
{
"name": "duration",
"fullWidth": true
}
],
[
{
"name": "dateEnd",
"fullWidth": true
}
],
[
{
"name": "description",
"fullWidth": true
}
]
]
}
]

View File

@@ -1,4 +1,4 @@
{
"controller": "Controllers.Record",
"controller": "controllers/record",
"boolFilterList": ["onlyMy"]
}

View File

@@ -29,10 +29,11 @@
"assignedUser": {
"type": "link",
"required": true,
"view": "views/fields/user"
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {

View File

@@ -1,6 +1,6 @@
{
"controller": "Controllers.RecordTree",
"collection": "Collections.Tree",
"controller": "controllers/record-tree",
"collection": "collections/tree",
"menu": {
"listTree": {
"buttons": [

View File

@@ -0,0 +1,4 @@
{
"controller": "controllers/record",
"boolFilterList": ["onlyMy"]
}

View File

@@ -0,0 +1,116 @@
{
"fields": {
"name": {
"type": "varchar",
"required": true,
"trim": true
},
"status": {
"type": "enum",
"options": ["Planned", "Held", "Not Held"],
"default": "Planned",
"view": "views/fields/enum-styled",
"style": {
"Held": "success"
},
"audited": true
},
"dateStart": {
"type": "datetime",
"required": true,
"default": "javascript: return this.dateTime.getNow(15);",
"audited": true
},
"dateEnd": {
"type": "datetime",
"required": true,
"after": "dateStart"
},
"duration": {
"type": "duration",
"start": "dateStart",
"end": "dateEnd",
"options": [300, 600, 900, 1800, 2700, 3600, 7200],
"default": 300,
"notStorable": true
},
"parent": {
"type": "linkParent",
"entityList": ["Account", "Lead"]
},
"description": {
"type": "text"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"assignedUser": {
"type": "link",
"required": false,
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
"parent": {
"type": "belongsToParent"
},
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"assignedUser": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "EntityTeam",
"layoutRelationshipsDisabled": true
}
},
"collection": {
"sortBy": "dateStart",
"asc": false
},
"indexes": {
"dateStartStatus": {
"columns": ["dateStart", "status"]
},
"dateStart": {
"columns": ["dateStart", "deleted"]
},
"status": {
"columns": ["status", "deleted"]
},
"assignedUser": {
"columns": ["assignedUserId", "deleted"]
},
"assignedUserStatus": {
"columns": ["assignedUserId", "status"]
}
}
}

View File

@@ -0,0 +1,11 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": true,
"calendar": true,
"notifications": true
}

View File

@@ -1,4 +1,4 @@
{
"controller": "Controllers.Record",
"controller": "controllers/record",
"boolFilterList": ["onlyMy"]
}

View File

@@ -21,10 +21,6 @@
"description": {
"type": "text"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"emailAddress": {
"type": "email"
},
@@ -53,6 +49,10 @@
"addressPostalCode": {
"type": "varchar"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
@@ -70,10 +70,11 @@
"assignedUser": {
"type": "link",
"required": false,
"view": "views/fields/user"
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {

View File

@@ -0,0 +1,36 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Templates\Repositories;
class Event extends \Espo\Core\ORM\Repositories\RDB
{
}

View File

@@ -0,0 +1,36 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Templates\Services;
class Event extends \Espo\Services\Record
{
}

View File

@@ -32,6 +32,39 @@ namespace Espo\Core\Templates\Services;
class Person extends \Espo\Services\Record
{
protected function getDuplicateWhereClause(Entity $entity, $data = array())
{
$data = array(
'OR' => array(
array(
'firstName' => $entity->get('firstName'),
'lastName' => $entity->get('lastName'),
)
)
);
if (
($entity->get('emailAddress') || $entity->get('emailAddressData'))
&&
($entity->isNew() || $entity->isFieldChanged('emailAddress') || $entity->isFieldChanged('emailAddressData'))
) {
if ($entity->get('emailAddress')) {
$list = [$entity->get('emailAddress')];
}
if ($entity->get('emailAddressData')) {
foreach ($entity->get('emailAddressData') as $row) {
if (!in_array($row->emailAddress, $list)) {
$list[] = $row->emailAddress;
}
}
}
foreach ($list as $emailAddress) {
$data['OR'][] = array(
'emailAddress' => $emailAddress
);
}
}
return $data;
}
}

View File

@@ -32,7 +32,7 @@ use Espo\Core\Utils\Util;
use Espo\Core\Utils\System;
use Espo\Core\Utils\Json;
use Espo\Core\Exceptions\Error;
use vierbergenlars\SemVer;
use Composer\Semver\Semver;
abstract class Base
{
@@ -221,20 +221,12 @@ abstract class Base
$versionList = (array) $versionList;
}
try {
$semver = new SemVer\version($currentVersion);
} catch (\Exception $e) {
$GLOBALS['log']->error('Cannot recognize currentVersion ['.$currentVersion.'], error: '.$e->getMessage().'.');
return false;
}
foreach ($versionList as $version) {
$isInRange = false;
try {
$isInRange = $semver->satisfies(new SemVer\expression($version));
$isInRange = Semver::satisfies($currentVersion, $version);
} catch (\Exception $e) {
$GLOBALS['log']->error('Version identification error: '.$e->getMessage().'.');
$GLOBALS['log']->error('SemVer: Version identification error: '.$e->getMessage().'.');
}
if ($isInRange) {

View File

@@ -163,6 +163,8 @@ class Auth
return false;
}
$user->set('portalId', $this->getPortal()->id);
} else {
$user->loadLinkMultipleField('teams');
}
$this->getEntityManager()->setUser($user);

View File

@@ -28,49 +28,68 @@
************************************************************************/
namespace Espo\Core\Utils\Authentication;
use Espo\Core\Exceptions\Error,
Espo\Core\Utils\Config,
Espo\Core\ORM\EntityManager,
Espo\Core\Utils\Auth;
use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\Config;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Auth;
class LDAP extends Base
{
private $utils;
private $zendLdap;
private $ldapClient;
/**
* Espo => LDAP name
* User field name => option name (LDAP attribute)
*
* @var array
*/
private $fields = array(
'userName' => 'cn',
'firstName' => 'givenname',
'lastName' => 'sn',
'title' => 'title',
'emailAddress' => 'mail',
'phoneNumber' => 'telephonenumber',
protected $ldapFieldMap = array(
'userName' => 'userNameAttribute',
'firstName' => 'userFirstNameAttribute',
'lastName' => 'userLastNameAttribute',
'title' => 'userTitleAttribute',
'emailAddress' => 'userEmailAddressAttribute',
'phoneNumber' => 'userPhoneNumberAttribute',
);
/**
* User field name => option name
*
* @var array
*/
protected $userFieldMap = array(
'teamsIds' => 'userTeamsIds',
'defaultTeamId' => 'userDefaultTeamId',
);
public function __construct(Config $config, EntityManager $entityManager, Auth $auth)
{
parent::__construct($config, $entityManager, $auth);
$this->zendLdap = new LDAP\LDAP();
$this->utils = new LDAP\Utils($config);
}
protected function getZendLdap()
{
return $this->zendLdap;
}
protected function getUtils()
{
return $this->utils;
}
protected function getLdapClient()
{
if (!isset($this->ldapClient)) {
$options = $this->getUtils()->getLdapClientOptions();
try {
$this->ldapClient = new LDAP\Client($options);
} catch (\Exception $e) {
$GLOBALS['log']->error('LDAP error: ' . $e->getMessage());
}
}
return $this->ldapClient;
}
/**
* LDAP login
@@ -78,6 +97,7 @@ class LDAP extends Base
* @param string $username
* @param string $password
* @param \Espo\Entities\AuthToken $authToken
*
* @return \Espo\Entities\User | null
*/
public function login($username, $password, \Espo\Entities\AuthToken $authToken = null)
@@ -86,28 +106,36 @@ class LDAP extends Base
return $this->loginByToken($username, $authToken);
}
$options = $this->getUtils()->getZendOptions();
$ldap = $this->getZendLdap();
$ldap = $ldap->setOptions($options);
$ldapClient = $this->getLdapClient();
//login LDAP system user (ldapUsername, ldapPassword)
try {
$ldap->bind($username, $password);
$ldapClient->bind();
} catch (\Exception $e) {
$options = $this->getUtils()->getLdapClientOptions();
$GLOBALS['log']->error('LDAP: Could not connect to LDAP server ['.$options['host'].'], details: ' . $e->getMessage());
$dn = $ldap->getDn($username);
$loginFilter = $this->getUtils()->getOption('userLoginFilter');
$userData = $ldap->searchByLoginFilter($loginFilter, $dn, 3);
} catch (\Zend\Ldap\Exception\LdapException $zle) {
$admin = $this->adminLogin($username, $password);
if (!isset($admin)) {
$GLOBALS['log']->info('LDAP Authentication: ' . $zle->getMessage());
$adminUser = $this->adminLogin($username, $password);
if (!isset($adminUser)) {
return null;
}
$GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.');
}
$GLOBALS['log']->info('LDAP Authentication: Administrator login by username ['.$username.']');
if (!isset($adminUser)) {
$userDn = $this->findLdapUserDnByUsername($username);
$GLOBALS['log']->debug('Found DN for ['.$username.']: ['.$userDn.'].');
if (!isset($userDn)) {
$GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: user is not found.');
return;
}
try {
$ldapClient->bind($userDn, $password);
} catch (\Exception $e) {
$GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: ' . $e->getMessage());
return null;
}
}
$user = $this->getEntityManager()->getRepository('User')->findOne(array(
@@ -118,7 +146,7 @@ class LDAP extends Base
$isCreateUser = $this->getUtils()->getOption('createEspoUser');
if (!isset($user) && $isCreateUser) {
$this->getAuth()->useNoAuth(); /** Required to fix Acl "isFetched()" error */
$userData = $ldapClient->getEntry($userDn);
$user = $this->createUser($userData);
}
@@ -130,6 +158,7 @@ class LDAP extends Base
*
* @param string $username
* @param \Espo\Entities\AuthToken $authToken
*
* @return \Espo\Entities\User | null
*/
protected function loginByToken($username, \Espo\Entities\AuthToken $authToken = null)
@@ -182,26 +211,106 @@ class LDAP extends Base
* Create Espo user with data gets from LDAP server
*
* @param array $userData LDAP entity data
*
* @return \Espo\Entities\User
*/
protected function createUser(array $userData)
{
$GLOBALS['log']->info('Creating new user ...');
$data = array();
foreach ($this->fields as $espo => $ldap) {
// show full array of the LDAP user
$GLOBALS['log']->debug('LDAP: user data: ' .print_r($userData, true));
//set values from ldap server
$ldapFields = $this->loadFields('ldap');
foreach ($ldapFields as $espo => $ldap) {
$ldap = strtolower($ldap);
if (isset($userData[$ldap][0])) {
$GLOBALS['log']->debug('LDAP: Create a user wtih ['.$espo.'] = ['.$userData[$ldap][0].'].');
$data[$espo] = $userData[$ldap][0];
}
}
//set user fields
$userFields = $this->loadFields('user');
foreach ($userFields as $fieldName => $fieldValue) {
$data[$fieldName] = $fieldValue;
}
$user = $this->getEntityManager()->getEntity('User');
$user->set($data);
$this->getEntityManager()->saveEntity($user);
return $user;
return $this->getEntityManager()->getEntity('User', $user->id);
}
/**
* Find LDAP user DN by his username
*
* @param string $username
*
* @return string | null
*/
protected function findLdapUserDnByUsername($username)
{
$ldapClient = $this->getLdapClient();
$options = $this->getUtils()->getOptions();
$loginFilterString = '';
if (!empty($options['userLoginFilter'])) {
$loginFilterString = $this->convertToFilterFormat($options['userLoginFilter']);
}
}
$searchString = '(&(objectClass='.$options['userObjectClass'].')('.$options['userNameAttribute'].'='.$username.')'.$loginFilterString.')';
$result = $ldapClient->search($searchString, null, LDAP\Client::SEARCH_SCOPE_SUB);
$GLOBALS['log']->debug('LDAP: user search string: "' . $searchString . '"');
foreach ($result as $item) {
return $item["dn"];
}
}
/**
* Check and convert filter item into LDAP format
*
* @param string $filter E.g. "memberof=CN=externalTesters,OU=groups,DC=espo,DC=local"
*
* @return string
*/
protected function convertToFilterFormat($filter)
{
$filter = trim($filter);
if (substr($filter, 0, 1) != '(') {
$filter = '(' . $filter;
}
if (substr($filter, -1) != ')') {
$filter = $filter . ')';
}
return $filter;
}
/**
* Load fields for a user
*
* @param string $type
*
* @return array
*/
protected function loadFields($type)
{
$options = $this->getUtils()->getOptions();
$typeMap = $type . 'FieldMap';
$fields = array();
foreach ($this->$typeMap as $fieldName => $fieldValue) {
if (isset($options[$fieldValue])) {
$fields[$fieldName] = $options[$fieldValue];
}
}
return $fields;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Authentication\LDAP;
class Client extends \Zend\Ldap\Ldap
{
}

View File

@@ -1,129 +0,0 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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\Authentication\LDAP;
class LDAP extends \Zend\Ldap\Ldap
{
protected $usernameAttribute = 'cn';
/**
* Get DN depends on options, ex. "cn=test,ou=People,dc=maxcrc,dc=com"
*
* @return string DN format
*/
public function getDn($acctname)
{
return $this->getAccountDn($acctname, \Zend\Ldap\Ldap::ACCTNAME_FORM_DN);
}
/**
* Fix a bug, ex. CN=Alice Baker,CN=Users,DC=example,DC=com
*
* @param string $acctname
* @return string - Account DN
*/
protected function getAccountDn($acctname)
{
$baseDn = $this->getBaseDn();
if ($this->getBindRequiresDn() && isset($baseDn)) {
try {
return parent::getAccountDn($acctname);
} catch (\Zend\Ldap\Exception\LdapException $zle) {
if ($zle->getCode() != \Zend\Ldap\Exception\LdapException::LDAP_NO_SUCH_OBJECT) {
throw $zle;
}
}
$acctname = $this->usernameAttribute . '=' . \Zend\Ldap\Filter\AbstractFilter::escapeValue($acctname) . ',' . $baseDn;
}
return parent::getAccountDn($acctname);
}
/**
* Search a user using userLoginFilter
*
* @param string $filter
* @param string $basedn
* @param int $scope
* @param array $attributes
* @return array
*/
public function searchByLoginFilter($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array())
{
$filter = $this->getLoginFilter($filter);
$result = $this->search($filter, $basedn, $scope, $attributes);
if ($result->count() > 0) {
return $result->getFirst();
}
throw new \Zend\Ldap\Exception\LdapException($this, 'searching: ' . $filter);
}
/**
* Get login filter in LDAP format
*
* @param string $filter
* @return string
*/
protected function getLoginFilter($filter)
{
$baseFilter = '(objectClass=*)';
if (!empty($filter)) {
$baseFilter = '(&' . $baseFilter . $this->convertToFilterFormat($filter). ')';
}
return $baseFilter;
}
/**
* Check and convert filter item in LDAP format
*
* @param string $filter [description]
* @return string
*/
protected function convertToFilterFormat($filter)
{
$filter = trim($filter);
if (substr($filter, 0, 1) != '(') {
$filter = '(' . $filter;
}
if (substr($filter, -1) != ')') {
$filter = $filter . ')';
}
return $filter;
}
}

View File

@@ -57,7 +57,16 @@ class Utils
'tryUsernameSplit' => 'ldapTryUsernameSplit',
'networkTimeout' => 'ldapNetworkTimeout',
'createEspoUser' => 'ldapCreateEspoUser',
'userNameAttribute' => 'ldapUserNameAttribute',
'userTitleAttribute' => 'ldapUserTitleAttribute',
'userFirstNameAttribute' => 'ldapUserFirstNameAttribute',
'userLastNameAttribute' => 'ldapUserLastNameAttribute',
'userEmailAddressAttribute' => 'ldapUserEmailAddressAttribute',
'userPhoneNumberAttribute' => 'ldapUserPhoneNumberAttribute',
'userLoginFilter' => 'ldapUserLoginFilter',
'userTeamsIds' => 'ldapUserTeamsIds',
'userDefaultTeamId' => 'ldapUserDefaultTeamId',
'userObjectClass' => 'ldapUserObjectClass',
);
/**
@@ -66,8 +75,17 @@ class Utils
* @var array
*/
protected $permittedEspoOptions = array(
'createEspoUser' => false,
'userLoginFilter' => null,
'createEspoUser',
'userNameAttribute',
'userObjectClass',
'userTitleAttribute',
'userFirstNameAttribute',
'userLastNameAttribute',
'userEmailAddressAttribute',
'userPhoneNumberAttribute',
'userLoginFilter',
'userTeamsIds',
'userDefaultTeamId',
);
/**
@@ -83,9 +101,11 @@ class Utils
);
public function __construct(Config $config)
public function __construct(Config $config = null)
{
$this->config = $config;
if (isset($config)) {
$this->config = $config;
}
}
protected function getConfig()
@@ -113,14 +133,25 @@ class Utils
}
}
/** peculiar fields */
$this->options = $this->normalizeOptions($options);
return $this->options;
}
/**
* Normalize options to LDAP client format
*
* @param array $options
*
* @return array
*/
public function normalizeOptions(array $options)
{
$options['useSsl'] = (bool) ($options['useSsl'] == 'SSL');
$options['useStartTls'] = (bool) ($options['useStartTls'] == 'TLS');
$options['accountCanonicalForm'] = $this->accountCanonicalFormMap[ $options['accountCanonicalForm'] ];
$this->options = $options;
return $this->options;
return $options;
}
/**
@@ -148,12 +179,10 @@ class Utils
*
* @return array
*/
public function getZendOptions()
public function getLdapClientOptions()
{
$options = $this->getOptions();
$espoOptions = array_keys($this->permittedEspoOptions);
$zendOptions = array_diff_key($options, array_flip($espoOptions));
$zendOptions = array_diff_key($options, array_flip($this->permittedEspoOptions));
return $zendOptions;
}

View File

@@ -96,8 +96,9 @@ class ClientManager
foreach ($vars as $key => $value) {
$html = str_replace('{{'.$key.'}}', $value, $html);
}
$html = str_replace('{{applicationName}}', $this->getConfig()->get('applicationName', 'EspoCRM'), $html);
$html = str_replace('{{cacheTimestamp}}', $this->getCacheTimestamp(), $html);
$html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false' , $html);
$html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false', $html);
$html = str_replace('{{stylesheet}}', $this->getThemeManager()->getStylesheet(), $html);
$html = str_replace('{{runScript}}', $runScript , $html);
$html = str_replace('{{basePath}}', $this->basePath , $html);

View File

@@ -316,6 +316,11 @@ class Config
return true;
}
public function getSiteUrl()
{
return rtrim($this->get('siteUrl'), '/');
}
}
?>

View File

@@ -69,20 +69,18 @@ class Job
*
* @return array
*/
public function getPendingJobs()
public function getPendingJobList()
{
/** Mark Failed old jobs and remove pending duplicates */
$this->markFailedJobs();
$this->markJobAttempts();
$this->removePendingJobDuplicates();
$jobConfigs = $this->getConfig()->get('cron');
$limit = !empty($jobConfigs['maxJobNumber']) ? intval($jobConfigs['maxJobNumber']) : 0;
$jobList = $this->getActiveJobs();
$jobList = $this->getJobList(CronManager::PENDING, $limit);
$runningScheduledJobs = $this->getActiveJobs('scheduled_job_id', CronManager::RUNNING, PDO::FETCH_COLUMN);
$runningScheduledJobIdList = $this->getRunningScheduledJobIdList();
$list = array();
$list = [];
foreach ($jobList as $row) {
if (!in_array($row['scheduled_job_id'], $runningScheduledJobs)) {
if (!in_array($row['scheduled_job_id'], $runningScheduledJobIdList)) {
$list[] = $row;
}
}
@@ -90,32 +88,63 @@ class Job
return $list;
}
/**
* Get active Jobs, which execution date in jobPeriod time
*
* @param string $displayColumns
* @param string $status
*
* @return array
*/
public function getActiveJobs($displayColumns = '*', $status = CronManager::PENDING, $fetchMode = PDO::FETCH_ASSOC)
public function getRunningScheduledJobIdList()
{
$jobConfigs = $this->getConfig()->get('cron');
$currentTime = time();
$limit = empty($jobConfigs['maxJobNumber']) ? '' : 'LIMIT '.$jobConfigs['maxJobNumber'];
$query = "SELECT " . $displayColumns . " FROM job WHERE
`status` = '" . $status . "'
AND execute_time <= '".date('Y-m-d H:i:s', $currentTime)."'
AND deleted = 0
ORDER BY execute_time ASC ".$limit;
$list = [];
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT scheduled_job_id FROM job
WHERE
`status` = 'Running' AND
scheduled_job_id IS NOT NULL AND
target_id IS NULL AND
deleted = 0
ORDER BY execute_time
";
$sth = $pdo->prepare($query);
$sth->execute();
$rows = $sth->fetchAll($fetchMode);
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($rowList as $row) {
$list[] = $row['scheduled_job_id'];
}
return $list;
}
/**
* Get job list, which execution date in jobPeriod time
*
* @param string $status
* @param int $limit
*
* @return array
*/
public function getJobList($status = CronManager::PENDING, $limit = null)
{
$currentTime = time();
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT * FROM job
WHERE
`status` = " . $pdo->quote($status) . "
AND execute_time <= '".date('Y-m-d H:i:s', $currentTime)."'
AND deleted = 0
ORDER BY execute_time ASC
";
if ($limit) {
$query .= " LIMIT " . $limit ;
}
$sth = $pdo->prepare($query);
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
return $rows;
}
@@ -133,13 +162,17 @@ class Job
$dateObj = new \DateTime($time);
$timeWithoutSeconds = $dateObj->format('Y-m-d H:i:');
$query = "SELECT * FROM job WHERE
scheduled_job_id = '".$scheduledJobId."'
AND execute_time LIKE '".$timeWithoutSeconds."%'
AND deleted = 0
LIMIT 1";
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT * FROM job
WHERE
scheduled_job_id = ".$pdo->quote($scheduledJobId)."
AND execute_time LIKE ". $pdo->quote($timeWithoutSeconds . '%') . "
AND deleted = 0
LIMIT 1
";
$sth = $pdo->prepare($query);
$sth->execute();
@@ -153,7 +186,7 @@ class Job
*
* @return void
*/
protected function markFailedJobs()
public function markFailedJobs()
{
$jobConfigs = $this->getConfig()->get('cron');
@@ -162,9 +195,11 @@ class Job
$pdo = $this->getEntityManager()->getPDO();
$select = "SELECT id, scheduled_job_id, execute_time FROM `job` WHERE
(`status` = '" . CronManager::RUNNING ."')
AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."' ";
$select = "
SELECT id, scheduled_job_id, execute_time, target_id, target_type FROM `job`
WHERE
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."'
";
$sth = $pdo->prepare($select);
$sth->execute();
@@ -173,7 +208,11 @@ class Job
$jobData[$row['id']] = $row;
}
$update = "UPDATE job SET `status` = '". CronManager::FAILED ."' WHERE id IN ('".implode("', '", array_keys($jobData))."')";
$update = "
UPDATE job
SET `status` = '". CronManager::FAILED ."'
WHERE id IN ('".implode("', '", array_keys($jobData))."')
";
$sth = $pdo->prepare($update);
$sth->execute();
@@ -181,7 +220,7 @@ class Job
$cronScheduledJob = $this->getCronScheduledJob();
foreach ($jobData as $jobId => $job) {
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time']);
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
}
}
}
@@ -191,38 +230,50 @@ class Job
*
* @return void
*/
protected function removePendingJobDuplicates()
public function removePendingJobDuplicates()
{
$pdo = $this->getEntityManager()->getPDO();
$query = "SELECT scheduled_job_id FROM job
WHERE
scheduled_job_id IS NOT NULL
AND `status` = '".CronManager::PENDING."'
AND execute_time <= '".date('Y-m-d H:i:s')."'
AND deleted = 0
GROUP BY scheduled_job_id
HAVING count( * ) > 1
ORDER BY execute_time ASC";
$query = "
SELECT scheduled_job_id
FROM job
WHERE
scheduled_job_id IS NOT NULL AND
`status` = '".CronManager::PENDING."' AND
execute_time <= '".date('Y-m-d H:i:s')."' AND
target_id IS NULL AND
deleted = 0
GROUP BY scheduled_job_id
HAVING count( * ) > 1
ORDER BY MAX(execute_time) ASC
";
$sth = $pdo->prepare($query);
$sth->execute();
$duplicateJobs = $sth->fetchAll(PDO::FETCH_ASSOC);
$duplicateJobList = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($duplicateJobs as $row) {
foreach ($duplicateJobList as $row) {
if (!empty($row['scheduled_job_id'])) {
/* no possibility to use limit in update or subqueries */
$query = "SELECT id FROM `job` WHERE scheduled_job_id = '" . $row['scheduled_job_id'] . "'
AND `status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000 ";
$query = "
SELECT id FROM `job`
WHERE
scheduled_job_id = '" . $row['scheduled_job_id'] . "' AND
`status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000
";
$sth = $pdo->prepare($query);
$sth->execute();
$jobIds = $sth->fetchAll(PDO::FETCH_COLUMN);
$update = "UPDATE job SET deleted = 1 WHERE
id IN ('". implode("', '", $jobIds)."') ";
$update = "
UPDATE job
SET deleted = 1
WHERE
id IN ('". implode("', '", $jobIds)."')
";
$sth = $pdo->prepare($update);
$sth->execute();
@@ -235,13 +286,16 @@ class Job
*
* @return void
*/
protected function markJobAttempts()
public function updateFailedJobAttempts()
{
$query = "SELECT * FROM job WHERE
`status` = '" . CronManager::FAILED . "'
AND deleted = 0
AND execute_time <= '".date('Y-m-d H:i:s')."'
AND attempts > 0";
$query = "
SELECT * FROM job
WHERE
`status` = '" . CronManager::FAILED . "' AND
deleted = 0 AND
execute_time <= '".date('Y-m-d H:i:s')."' AND
attempts > 0
";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -255,12 +309,14 @@ class Job
$attempts = $row['attempts'] - 1;
$failedAttempts = $row['failed_attempts'] + 1;
$update = "UPDATE job SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
$update = "
UPDATE job
SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
";
$pdo->prepare($update)->execute();
}

View File

@@ -59,11 +59,14 @@ class ScheduledJob
*
* @return array
*/
public function getActiveJobs()
public function getActiveScheduledJobList()
{
$query = "SELECT * FROM scheduled_job WHERE
`status` = 'Active'
AND deleted = 0";
$query = "
SELECT * FROM scheduled_job
WHERE
`status` = 'Active' AND
deleted = 0
";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -87,7 +90,7 @@ class ScheduledJob
*
* @return string ID of created ScheduledJobLogRecord
*/
public function addLogRecord($scheduledJobId, $status, $runTime = null)
public function addLogRecord($scheduledJobId, $status, $runTime = null, $targetId = null, $targetType = null)
{
if (!isset($runTime)) {
$runTime = date('Y-m-d H:i:s');
@@ -96,6 +99,11 @@ class ScheduledJob
$entityManager = $this->getEntityManager();
$scheduledJob = $entityManager->getEntity('ScheduledJob', $scheduledJobId);
if (!$scheduledJob) {
return;
}
$scheduledJob->set('lastRun', $runTime);
$entityManager->saveEntity($scheduledJob);
@@ -105,6 +113,8 @@ class ScheduledJob
'name' => $scheduledJob->get('name'),
'status' => $status,
'executionTime' => $runTime,
'targetId' => $targetId,
'targetType' => $targetType
));
$scheduledJobLogId = $entityManager->saveEntity($scheduledJobLog);

View File

@@ -82,15 +82,11 @@ class Converter
*/
public function process()
{
$GLOBALS['log']->debug('Orm\Converter - Start: orm convertation');
$ormMeta = $this->getOrmConverter()->process();
//save database meta to a file espoMetadata.php
$result = $this->getMetadata()->setOrmMetadata($ormMeta);
$GLOBALS['log']->debug('Orm\Converter - End: orm convertation, result=['.$result.']');
return $result;
}
}

View File

@@ -66,7 +66,9 @@ class Currency extends Base
"<" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate < {value}",
">=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate >= {value}",
"<=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <= {value}",
"<>" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <> {value}"
"<>" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <> {value}",
"IS NULL" => Util::toUnderScore($entityName) . "." . $currencyColumnName . ' IS NULL',
"IS NOT NULL" => Util::toUnderScore($entityName) . "." . $currencyColumnName . ' IS NOT NULL',
),
'notStorable' => true,
'orderBy' => $converedFieldName . " {direction}"

View File

@@ -48,22 +48,36 @@ class Email extends Base
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
email_address.deleted = 0 AND email_address.name LIKE {value}
)",
'=' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_email_address
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 = {value}
)",
'<>' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_email_address
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 <> {value}
)"
'=' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name = {value}',
'distinct' => true
),
'<>' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name <> {value}',
'distinct' => true
),
'IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IN {value}',
'distinct' => true
),
'NOT IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name NOT IN {value}',
'distinct' => true
),
'IS NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NULL',
'distinct' => true
),
'IS NOT NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NOT NULL',
'distinct' => true
)
),
'orderBy' => 'emailAddresses.name {direction}',
),
@@ -73,7 +87,7 @@ class Email extends Base
),
),
'relations' => array(
$fieldName.'es' => array(
'emailAddresses' => array(
'type' => 'manyMany',
'entity' => 'EmailAddress',
'relationName' => 'entityEmailAddress',

View File

@@ -48,22 +48,36 @@ class Phone extends Base
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name LIKE {value}
)",
'=' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_phone_number
JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id
WHERE
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name = {value}
)",
'<>' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_phone_number
JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id
WHERE
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name <> {value}
)"
'=' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name = {value}',
'distinct' => true
),
'<>' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name <> {value}',
'distinct' => true
),
'IN' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IN {value}',
'distinct' => true
),
'NOT IN' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name NOT IN {value}',
'distinct' => true
),
'IS NULL' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IS NULL',
'distinct' => true
),
'IS NOT NULL' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IS NOT NULL',
'distinct' => true
)
),
'orderBy' => 'phoneNumbers.name {direction}',
),
@@ -73,7 +87,7 @@ class Phone extends Base
),
),
'relations' => array(
$fieldName.'s' => array(
'phoneNumbers' => array(
'type' => 'manyMany',
'entity' => 'PhoneNumber',
'relationName' => 'entityPhoneNumber',

View File

@@ -116,6 +116,11 @@ class DateTime
return null;
}
public function setTimezone($timezone)
{
$this->timezone = new \DateTimeZone($timezone);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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;
use \Espo\Core\ORM\EntityManager;
use \Espo\Entities\Email;
class EmailFilterManager
{
private $entityManager;
private $data = array();
protected $filtersMatcher = null;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
protected function getEntityManager()
{
return $this->entityManager;
}
protected function getFiltersMatcher()
{
if (!$this->filtersMatcher) {
$this->filtersMatcher = new \Espo\Core\Mail\FiltersMatcher();
}
return $this->filtersMatcher;
}
public function getMatchingFilter(Email $email, $userId)
{
if (!array_key_exists($userId, $this->data)) {
$emailFilterList = $this->getEntityManager()->getRepository('EmailFilter')->where(array(
'parentId' => $userId,
'parentType' => 'User'
))->order('LIST:action:Skip;Move to Folder')->find();
$this->data[$userId] = $emailFilterList;
}
foreach ($this->data[$userId] as $emailFilter) {
if ($this->getFiltersMatcher()->match($email, $emailFilter)) {
return $emailFilter;
}
}
}
}

View File

@@ -82,6 +82,8 @@ class EntityManager
throw new Error();
}
$name = trim($name);
$normalizedName = Util::normilizeClassName($name);
$contents = "<" . "?" . "php\n\n".
@@ -217,6 +219,15 @@ class EntityManager
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
if (isset($data['textFilterFields'])) {
$entityDefsData = array(
'collection' => array(
'textFilterFields' => $data['textFilterFields']
)
);
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
$this->getMetadata()->save();
$this->getLanguage()->save();
@@ -270,9 +281,9 @@ class EntityManager
$linkType = $params['linkType'];
$entity = $params['entity'];
$link = $params['link'];
$link = trim($params['link']);
$entityForeign = $params['entityForeign'];
$linkForeign = $params['linkForeign'];
$linkForeign = trim($params['linkForeign']);
$label = $params['label'];
$labelForeign = $params['labelForeign'];
@@ -320,9 +331,15 @@ class EntityManager
switch ($linkType) {
case 'oneToMany':
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.field.' . $linkForeign)) {
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign)) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Id')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Name')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(
@@ -361,9 +378,15 @@ class EntityManager
);
break;
case 'manyToOne':
if ($this->getMetadata()->get('entityDefs.' . $entity . '.field.' . $link)) {
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link)) {
throw new Conflict('Field ['.$entity.'::'.$link.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Id')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Name')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(

View File

@@ -31,6 +31,8 @@ namespace Espo\Core\Utils;
use \Espo\Core\Exceptions\Error,
\Espo\Core\Exceptions\Conflict;
use \Espo\Core\Container;
class FieldManager
{
private $metadata;
@@ -41,14 +43,17 @@ class FieldManager
protected $isChanged = null;
private $container;
protected $metadataType = 'entityDefs';
protected $customOptionName = 'isCustom';
public function __construct(Metadata $metadata, Language $language)
public function __construct(Metadata $metadata, Language $language, Container $container = null)
{
$this->metadata = $metadata;
$this->language = $language;
$this->container = $container;
$this->metadataHelper = new \Espo\Core\Utils\Metadata\Helper($this->metadata);
}
@@ -74,6 +79,10 @@ class FieldManager
$fieldDefs['label'] = $this->getLanguage()->translate($name, 'fields', $scope);
$type = $this->getMetadata()->get(['entityDefs', $scope, 'fields', $name, 'type']);
$this->processHook('onRead', $type, $scope, $name, $fieldDefs);
return $fieldDefs;
}
@@ -89,6 +98,7 @@ class FieldManager
public function update($name, $fieldDefs, $scope)
{
$name = trim($name);
/*Add option to metadata to identify the custom field*/
if (!$this->isCore($name, $scope)) {
$fieldDefs[$this->customOptionName] = true;
@@ -99,7 +109,11 @@ class FieldManager
$this->setLabel($name, $fieldDefs['label'], $scope);
}
if (isset($fieldDefs['type']) && ($fieldDefs['type'] == 'enum' || $fieldDefs['type'] == 'phone')) {
$type = isset($fieldDefs['type']) ? $fieldDefs['type'] : $type = $this->getMetadata()->get(['entityDefs', $scope, 'fields', $name, 'type']);
$this->processHook('beforeSave', $type, $scope, $name, $fieldDefs);
if ($this->getMetadata()->get(['fields', $type, 'translatedOptions'])) {
if (isset($fieldDefs['translatedOptions'])) {
$this->setTranslatedOptions($name, $fieldDefs['translatedOptions'], $scope);
}
@@ -107,6 +121,8 @@ class FieldManager
if (isset($fieldDefs['label']) || isset($fieldDefs['translatedOptions'])) {
$res &= $this->getLanguage()->save();
$this->processHook('afterSave', $type, $scope, $name, $fieldDefs);
}
if ($this->isDefsChanged($name, $fieldDefs, $scope)) {
@@ -122,6 +138,10 @@ class FieldManager
throw new Error('Cannot delete core field ['.$name.'] in '.$scope);
}
$type = $this->getMetadata()->get(['entityDefs', $scope, 'fields', $name, 'type']);
$this->processHook('beforeRemove', $type, $scope, $name);
$unsets = array(
'fields.'.$name,
'links.'.$name,
@@ -131,6 +151,8 @@ class FieldManager
$res = $this->getMetadata()->save();
$res &= $this->deleteLabel($name, $scope);
$this->processHook('afterRemove', $type, $scope, $name);
return (bool) $res;
}
@@ -336,4 +358,31 @@ class FieldManager
{
return array_merge($this->getActualAttributeList($scope, $name), $this->getNotActualAttributeList($scope, $name));
}
protected function processHook($methodName, $type, $scope, $name, &$defs = null)
{
$hook = $this->getHook($type);
if (!$hook) return;
if (!method_exists($hook, $methodName)) return;
$hook->$methodName($scope, $name, $defs);
}
protected function getHook($type)
{
$className = $this->getMetadata()->get(['fields', $type, 'hookClassName']);
if (!$className) return;
if (class_exists($className)) {
$hook = new $className();
foreach ($hook->getDependencyList() as $name) {
$hook->inject($name, $this->container->get($name));
}
return $hook;
}
$GLOBALS['log']->error("Field Manager hook class '{$className}' does not exist.");
return;
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Espo\Core\Utils\FieldManager\Hooks;
abstract class Base
{
protected $dependencyList = [
'entityManager',
'config',
'metadata',
];
protected $injections = array();
public function __construct()
{
$this->init();
}
protected function init()
{
}
public function getDependencyList()
{
return $this->dependencyList;
}
protected function addDependencyList(array $list)
{
foreach ($list as $item) {
$this->addDependency($item);
}
}
protected function addDependency($name)
{
$this->dependencies[] = $name;
}
protected function getInjection($name)
{
return $this->injections[$name];
}
public function inject($name, $object)
{
$this->injections[$name] = $object;
}
protected function getMetadata()
{
return $this->getInjection('metadata');
}
protected function getConfig()
{
return $this->getInjection('config');
}
protected function getEntityManager()
{
return $this->getInjection('entityManager');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Espo\Core\Utils\FieldManager\Hooks;
class NumberType extends Base
{
public function onRead($scope, $name, &$defs)
{
$number = $this->getEntityManager()->getRepository('NextNumber')->where(array(
'entityType' => $scope,
'fieldName' => $name
))->findOne();
$value = null;
if (!$number) {
$value = 1;
} else {
if (!$number->get('value')) {
$value = 1;
}
}
if (!$value && $number) {
$value = $number->get('value');
}
$defs['nextNumber'] = $value;
}
public function afterSave($scope, $name, $defs)
{
if (!isset($defs['nextNumber'])) return;
$number = $this->getEntityManager()->getRepository('NextNumber')->where(array(
'entityType' => $scope,
'fieldName' => $name
))->findOne();
if (!$number) {
$number = $this->getEntityManager()->getEntity('NextNumber');
$number->set('entityType', $scope);
$number->set('fieldName', $name);
}
$number->set('value', $defs['nextNumber']);
$this->getEntityManager()->saveEntity($number);
}
public function afterRemove($scope, $name)
{
$number = $this->getEntityManager()->getRepository('NextNumber')->where(array(
'entityType' => $scope,
'fieldName' => $name
))->findOne();
if (!$number) return;
$this->getEntityManager()->removeEntity($number);
}
}

View File

@@ -188,9 +188,7 @@ class Manager
if (file_exists($fullPath) && strtolower(substr($fullPath, -4)) == '.php') {
$phpContents = include($fullPath);
if (is_array($phpContents)) {
return $phpContents;
}
return $phpContents;
}
return false;
@@ -249,7 +247,7 @@ class Manager
public function putContentsJson($path, $data)
{
if (!Utils\Json::isJSON($data)) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return $this->putContents($path, $data, LOCK_EX);
@@ -290,7 +288,7 @@ class Manager
$data = Utils\Util::merge($savedDataArray, $newDataArray);
if ($isReturnJson) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if ($isPhp) {
@@ -336,7 +334,7 @@ class Manager
public function unsetContents($path, $unsets, $isJSON = true)
{
$currentData = $this->getContents($path);
if ($currentData == false) {
if (!isset($currentData) || !$currentData) {
$GLOBALS['log']->notice('FileManager::unsetContents: File ['.$this->concatPaths($path).'] does not exist.');
return false;
}

View File

@@ -373,7 +373,7 @@ class Permission
protected function chmodReal($filename, $mode)
{
try {
$result = chmod($filename, $mode);
$result = @chmod($filename, $mode);
} catch (\Exception $e) {
$result = false;
}
@@ -383,7 +383,7 @@ class Permission
$this->chgrp($filename, $this->getDefaultGroup(true));
try {
$result = chmod($filename, $mode);
$result = @chmod($filename, $mode);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}
@@ -395,7 +395,7 @@ class Permission
protected function chownReal($path, $user)
{
try {
$result = chown($path, $user);
$result = @chown($path, $user);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}
@@ -406,7 +406,7 @@ class Permission
protected function chgrpReal($path, $group)
{
try {
$result = chgrp($path, $group);
$result = @chgrp($path, $group);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}

View File

@@ -185,6 +185,11 @@ class Language
$options = $this->get($scope. '.options.' . $field);
if (is_array($options) && array_key_exists($value, $options)) {
return $options[$value];
} else if ($scope !== 'Global') {
$options = $this->get('Global.options.' . $field);
if (is_array($options) && array_key_exists($value, $options)) {
return $options[$value];
}
}
return $value;
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core\Utils;
use Espo\Core\Exceptions\Error;
class Metadata
@@ -187,7 +188,7 @@ class Metadata
/**
* Get Metadata
*
* @param string $key
* @param mixed string|array $key
* @param mixed $default
*
* @return array

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils;
class Number
class NumberUtil
{
protected $decimalMark;

View File

@@ -394,33 +394,35 @@ class Util
$unsets = (array) $unsets;
}
foreach($unsets as $rootKey => $unsetItem){
foreach ($unsets as $rootKey => $unsetItem) {
$unsetItem = is_array($unsetItem) ? $unsetItem : (array) $unsetItem;
foreach($unsetItem as $unsetSett){
if (!empty($unsetSett)){
$keyItems = explode('.', $unsetSett);
$currVal = isset($content[$rootKey]) ? "\$content['{$rootKey}']" : "\$content";
foreach ($unsetItem as $unsetString) {
if (is_string($rootKey)) {
$unsetString = $rootKey . '.' . $unsetString;
}
$lastKey = array_pop($keyItems);
foreach($keyItems as $keyItem){
$currVal .= "['{$keyItem}']";
}
$keyСhain = explode('.', $unsetString);
$keyChainCount = count($keyСhain) - 1;
$unsetElem = $currVal . "['{$lastKey}']";
$elem = & $content;
for ($i = 0; $i <= $keyChainCount; $i++) {
$evalString = "
if (isset({$unsetElem}) || ( is_array({$currVal}) && array_key_exists('{$lastKey}', {$currVal}) )) {
unset({$unsetElem});
} ";
eval($evalString);
if (is_array($elem) && array_key_exists($keyСhain[$i], $elem)) {
if ($i == $keyChainCount) {
unset($elem[$keyСhain[$i]]);
if ($unsetParentEmptyArray && is_array($elem) && empty($elem)) {
unset($keyСhain[$i]);
$content = static::unsetInArray($content, implode('.', $keyСhain), false);
}
} else if (is_array($elem[$keyСhain[$i]])) {
$elem = & $elem[$keyСhain[$i]];
}
if ($unsetParentEmptyArray) {
$evalString = "
if (is_array({$currVal}) && empty({$currVal})) {
unset({$currVal});
} ";
eval($evalString);
}
}
}
@@ -449,7 +451,7 @@ class Util
* Return values of defined $key.
*
* @param array $array
* @param string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed array|string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed $default
* @return mixed
*/
@@ -459,7 +461,11 @@ class Util
return $array;
}
$keys = explode('.', $key);
if (is_array($key)) {
$keys = $key;
} else {
$keys = explode('.', $key);
}
$lastItem = $array;
foreach($keys as $keyName) {

View File

@@ -32,6 +32,7 @@ return array (
'driver' => 'pdo_mysql',
'host' => 'localhost',
'port' => '',
'charset' => 'utf8',
'dbname' => '',
'user' => '',
'password' => '',
@@ -67,6 +68,8 @@ return array (
'de_DE',
'es_ES',
'fr_FR',
'id_ID',
'it_IT',
'nl_NL',
'tr_TR',
'ro_RO',
@@ -93,12 +96,13 @@ return array (
'Opportunity',
),
"tabList" => ["Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign", "KnowledgeBaseArticle"],
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case"],
'calendarDefaultEntity' => 'Meeting',
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case", "Email"],
'exportDisabled' => false,
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => ['Lead', 'Opportunity', 'Task', 'Case'],
'assignmentNotificationsEntityList' => ['Meeting', 'Call', 'Task', 'Email'],
"portalStreamEmailNotifications" => true,
'streamEmailNotificationsEntityList' => ['Case'],
'emailMessageMaxSize' => 10,
'notificationsCheckInterval' => 10,
'disabledCountQueryEntityList' => ['Email'],
@@ -113,6 +117,7 @@ return array (
'authTokenLifetime' => 0,
'authTokenMaxIdleTime' => 120,
'userNameRegularExpression' => '[^a-z0-9\-@_\.\s]',
'addressFormat' => 1,
'displayListViewRecordCount' => true,
'dashboardLayout' => [
(object) [
@@ -145,6 +150,7 @@ return array (
]
]
],
'isInstalled' => false
"calendarEntityList" => ["Meeting", "Call", "Task"],
'isInstalled' => false,
);

View File

@@ -123,6 +123,15 @@ return array ( 'defaultPermissions' =>
'ldapTryUsernameSplit',
'ldapOptReferrals',
'ldapCreateEspoUser',
'ldapAccountDomainName',
'ldapAccountDomainNameShort',
'ldapUserNameAttribute',
'ldapUserFirstNameAttribute',
'ldapUserLastNameAttribute',
'ldapUserTitleAttribute',
'ldapUserEmailAddressAttribute',
'ldapUserPhoneNumberAttribute',
'ldapUserObjectClass',
'maxEmailAccountCount',
'massEmailMaxPerHourCount',
'personalEmailMaxPortionSize',
@@ -131,5 +140,12 @@ return array ( 'defaultPermissions' =>
'authTokenMaxIdleTime'
),
'isInstalled' => false,
'ldapUserNameAttribute' => 'sAMAccountName',
'ldapUserFirstNameAttribute' => 'givenName',
'ldapUserLastNameAttribute' => 'sn',
'ldapUserTitleAttribute' => 'title',
'ldapUserEmailAddressAttribute' => 'mail',
'ldapUserPhoneNumberAttribute' => 'telephoneNumber',
'ldapUserObjectClass' => 'person',
);

View File

@@ -51,6 +51,11 @@ class Email extends \Espo\Core\ORM\Entity
}
}
public function isManuallyArchived()
{
return $this->get('status') === 'Archived' && $this->get('createdById') !== 'system';
}
public function addAttachment(\Espo\Entities\Attachment $attachment)
{
if (!empty($this->id)) {

View File

@@ -0,0 +1,36 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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 EmailFolder extends \Espo\Core\ORM\Entity
{
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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 NextNumber extends \Espo\Core\ORM\Entity
{
}

View File

@@ -66,8 +66,6 @@ class Attachment extends \Espo\Core\EntryPoints\Base
header('Pragma: public');
header('Content-Length: ' . filesize($fileName));
ob_clean();
flush();
readfile($fileName);
exit;
}

View File

@@ -40,7 +40,9 @@ class Avatar extends Image
public static $notStrictAuth = true;
private $colorList = [
protected $systemColor = [212,114,155];
protected $colorList = [
[111,168,214],
[237,197,85],
[212,114,155],
@@ -73,7 +75,6 @@ class Avatar extends Image
$userId = $_GET['id'];
$user = $this->getEntityManager()->getEntity('User', $userId);
if (!$user) {
throw new NotFound();
@@ -106,9 +107,14 @@ class Avatar extends Image
header('Cache-Control: max-age=360000, must-revalidate');
header('Content-Type: image/png');
ob_clean();
flush();
echo $identicon->getImageData($userId, $width, $this->getColor($userId));
$hash = $userId;
$color = $this->getColor($userId);
if ($hash === 'system') {
$color = $this->systemColor;
}
$imgContent = $identicon->getImageData($hash, $width, $color);
echo $imgContent;
exit;
}
}

View File

@@ -73,6 +73,9 @@ class Download extends \Espo\Core\EntryPoints\Base
throw new NotFound();
}
$outputFileName = $attachment->get('name');
$outputFileName = str_replace("\"", "\\\"", $outputFileName);
$type = $attachment->get('type');
$disposition = 'attachment';
@@ -84,13 +87,12 @@ class Download extends \Espo\Core\EntryPoints\Base
if ($type) {
header('Content-Type: ' . $type);
}
header("Content-Disposition: " . $disposition . ";filename=\"" . $attachment->get('name') . "\"");
header("Content-Disposition: " . $disposition . ";filename=\"" . $outputFileName . "\"");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($fileName));
ob_clean();
flush();
readfile($fileName);
exit;
}

View File

@@ -143,8 +143,6 @@ class Image extends \Espo\Core\EntryPoints\Base
if ($fileSize) {
header('Content-Length: ' . $fileSize);
}
ob_clean();
flush();
readfile($filePath);
exit;
}

View File

@@ -44,7 +44,10 @@ class Portal extends \Espo\Core\EntryPoints\Base
} else if (!empty($data['id'])) {
$id = $data['id'];
} else {
$id = $this->getConfig()->get('defaultPortalId');
$id = explode('/', $_SERVER['REQUEST_URI'])[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
if (!$id) {
$id = $this->getConfig()->get('defaultPortalId');
}
if (!$id) {
throw new NotFound();
}

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