Compare commits

...

340 Commits
3.9.2 ... 4.0.2

Author SHA1 Message Date
yuri
e2deaf57dd fix portal account contact select acl 2016-03-03 13:02:10 +02:00
yuri
0530d9deb8 fix case compose email 2016-03-03 12:30:58 +02:00
yuri
b57ccf0c6a fix case assigned status 2016-03-03 12:27:01 +02:00
yuri
9ea9cf693d v 2016-03-03 12:19:44 +02:00
yuri
3460931fba fix portal windows issue 2016-03-03 12:19:05 +02:00
yuri
b743d113cc fix acl 2016-03-03 12:12:13 +02:00
yuri
f810371e70 fix tabbing 2016-03-03 12:12:07 +02:00
yuri
bc525f3047 fetchOnModelAfterRelate 2016-03-02 13:21:19 +02:00
yuri
6809181adf mass email: dont store test email 2016-03-02 13:02:44 +02:00
yuri
2c71a28421 fix use 2016-03-02 11:49:47 +02:00
yuri
82162e4fe6 fix select manager 2016-03-01 17:25:11 +02:00
yuri
03454bc309 fix portal log 2016-03-01 16:40:23 +02:00
yuri
d975501f29 fix warnings 2016-03-01 16:32:37 +02:00
yuri
761356adda fix mention 2016-03-01 12:23:37 +02:00
yuri
824835a28c email template: array fields 2016-03-01 12:01:27 +02:00
yuri
252ce15973 fix email template 2016-03-01 11:55:37 +02:00
yuri
7f25dba917 v 2016-03-01 10:57:31 +02:00
yuri
b5d4b8aa5a fix warning 2016-03-01 10:20:07 +02:00
yuri
39a136295a repository default options 2016-02-29 17:45:39 +02:00
yuri
6d674c007e php 7 compatibility 2016-02-29 17:01:18 +02:00
yuri
31f380a03a russian lang 2016-02-29 12:03:38 +02:00
yuri
7e23960196 v 2016-02-26 17:11:36 +02:00
yuri
d3b9f2479b fix export 2016-02-26 17:11:32 +02:00
yuri
c211842b52 fix metadata 2016-02-26 15:17:33 +02:00
yuri
3e11c0cfc1 fix loader 2016-02-26 12:23:53 +02:00
yuri
6747440b00 document folder customizable 2016-02-26 12:09:30 +02:00
yuri
dc1c4b1e78 fix lang 2016-02-26 10:48:01 +02:00
yuri
26f2fba3ac cleanyp 2016-02-25 16:17:55 +02:00
yuri
eb481c689b fix warnings 2016-02-25 16:16:40 +02:00
yuri
88c3984d35 date: ever filter 2016-02-25 13:07:50 +02:00
yuri
4eae088973 fix flotr2: remove underscore 2016-02-25 12:19:11 +02:00
yuri
60ae0fb365 fix 2016-02-25 10:28:21 +02:00
yuri
ab36f41fc3 duration notStorable 2016-02-24 17:51:13 +02:00
yuri
98de243e37 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-02-24 15:31:45 +02:00
yuri
0c6ccabbeb fix checkEntityForDuplicate 2016-02-24 15:31:26 +02:00
yuri
a66e6111cb fix detail view buttons fixed possition 2016-02-24 12:11:00 +02:00
yuri
caf5c8806a chart outlineColor 2016-02-24 11:43:48 +02:00
yuri
0154dfd6a1 view naming fix 2016-02-24 11:36:43 +02:00
yuri
e545e68877 version 2016-02-24 10:43:43 +02:00
yuri
fa24c2b7fe fix array field 2016-02-23 16:43:02 +02:00
Taras Machyshyn
df2b7d9659 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-02-23 15:58:24 +02:00
Taras Machyshyn
cc395232ff Fixed ukrainian translation 2016-02-23 15:58:10 +02:00
yuri
47afb83536 fix datetime 2016-02-23 15:48:13 +02:00
yuri
02d1b50b59 view naming 2016-02-23 15:42:05 +02:00
yuri
bd86eb08fa fix account list layout 2016-02-23 15:10:47 +02:00
yuri
b3c1a45b72 task: add parent to filters layout 2016-02-23 12:41:34 +02:00
yuri
15678e59e2 fix search frontend 2016-02-23 12:41:00 +02:00
yuri
6fe379bab3 link multiple and attachment multiple listLayoutDisabled 2016-02-23 11:54:48 +02:00
yuri
39ecb2fd37 cleanup 2016-02-23 11:51:36 +02:00
yuri
988c0059ff import language file 2016-02-23 11:34:15 +02:00
yuri
61ff31b3b7 trim 2016-02-23 11:17:05 +02:00
yuri
7c1a098b89 change account layouts 2016-02-22 16:47:52 +02:00
yuri
f843593843 add country to lead small view 2016-02-22 16:38:22 +02:00
yuri
c34eb915bc fix email draft 2016-02-22 15:54:37 +02:00
yuri
9508432294 opportunity set probability if empty 2016-02-22 15:00:40 +02:00
yuri
0959962269 version 2016-02-22 14:53:05 +02:00
yuri
e39a91bde6 fix view naming 2016-02-22 12:42:09 +02:00
yuri
51f5dbe25c address list view 2016-02-22 11:47:45 +02:00
yuri
6fd5c6cbe7 fix view naming 2016-02-22 11:18:07 +02:00
yuri
6622b09eaa fix issue that search input not stored 2016-02-22 11:16:47 +02:00
yuri
9bb9f066cd fix dashboard 2016-02-19 18:15:28 +02:00
yuri
03110b47df change account list layout 2016-02-19 17:41:15 +02:00
yuri
034e532e05 change contact list layout 2016-02-19 17:35:07 +02:00
yuri
c306af8b4c readable date formats according to selecred date format 2016-02-19 12:21:23 +02:00
yuri
bd4a784676 move readable date formats to date-time.js 2016-02-19 12:10:55 +02:00
yuri
4ca0a9dc5f fix german salutations 2016-02-19 12:03:15 +02:00
yuri
c6816b01b5 bool field filter: checked by default 2016-02-19 11:51:27 +02:00
yuri
bc7583ccb4 improve role table ui 2016-02-19 11:32:16 +02:00
yuri
318e43aca3 json formating 2016-02-19 10:38:05 +02:00
yuri
ea992f727b admin page: move user interface upper 2016-02-18 18:19:03 +02:00
yuri
5bdb16f98c fix home icon position 2016-02-18 18:09:27 +02:00
yuri
faa06993e6 cacheTimestamp fix for no cache 2016-02-18 18:06:50 +02:00
yuri
f6e0d017b2 change home icon 2016-02-18 18:03:16 +02:00
yuri
8ea56a74b0 code style link 2016-02-18 17:23:18 +02:00
yuri
d4c9666f85 contributing file 2016-02-18 17:19:43 +02:00
yuri
5f376304ce portal entryPoint changes 2016-02-18 11:41:56 +02:00
yuri
dbb29f25ab portal fixes 2016-02-17 17:31:10 +02:00
yuri
da059b2589 portal changes 2016-02-17 16:54:53 +02:00
yuri
fee64fd5ac fix lang 2016-02-17 12:48:30 +02:00
yuri
85de9f7a6f entryPoint data 2016-02-17 12:47:16 +02:00
yuri
5beb7641af portal folder 2016-02-17 12:35:19 +02:00
yuri
249852c3d7 baseBath 2016-02-16 17:27:45 +02:00
yuri
223d07579b display total count by default 2016-02-16 12:01:20 +02:00
yuri
74fb359740 pdf fontface in config 2016-02-16 10:48:40 +02:00
yuri
f7ffadc76f fix template vars sorting 2016-02-16 10:40:19 +02:00
yuri
fc03141dee calendar: fix issue with date w/o time 2016-02-16 10:25:38 +02:00
yuri
8d29014811 added cs_CZ lang 2016-02-15 16:18:52 +02:00
yuri
7dcbbcb98d version 2016-02-15 12:36:33 +02:00
yuri
60b9200247 fix vertical theme pagination issue 2016-02-15 12:19:02 +02:00
yuri
fa4d1d70d5 naming fix 2016-02-15 12:01:05 +02:00
yuri
de6cc1f9bb currency rounding 2016-02-15 11:52:20 +02:00
yuri
5fb6abe0e1 keep history in converted laed 2016-02-15 11:41:38 +02:00
yuri
94073b8aad account filters layout change 2016-02-15 11:16:40 +02:00
yuri
0e83e21aa0 use wildcard in address search 2016-02-15 11:14:56 +02:00
yuri
63bbf72942 fix company logo 403 error 2016-02-15 11:12:54 +02:00
yuri
dbaa41b161 refresh list view after create / edit 2016-02-15 10:53:25 +02:00
yuri
ed673dbe0d fix portal isDefault 2016-02-15 10:39:18 +02:00
yuri
d54ccb0c9e fix config 2016-02-12 18:40:23 +02:00
yuri
b29cbec3a1 kb fix 2016-02-12 12:33:19 +02:00
yuri
edc967c118 campaign: dont count test in statistics 2016-02-12 11:40:45 +02:00
yuri
e80681da22 change password url 2016-02-12 10:44:05 +02:00
yuri
03ec9c9378 attachment related field 2016-02-11 14:26:11 +02:00
yuri
6c54306cd6 fix email template 2016-02-11 12:48:33 +02:00
yuri
db49af84f4 kb portals 2016-02-11 12:29:12 +02:00
yuri
79a29531e9 fix import 2016-02-10 14:45:12 +02:00
yuri
c3de7f022e stream dashlet view list 2016-02-10 12:55:27 +02:00
yuri
59a3111596 fix acl 2016-02-10 12:49:06 +02:00
yuri
cf8dfadbdf dont display global search if no acl access 2016-02-10 11:58:07 +02:00
yuri
e500b2c906 user filters 2016-02-10 11:25:02 +02:00
yuri
0d9417cc3e create portal user 2016-02-10 11:21:50 +02:00
yuri
eb57d70182 fix lang 2016-02-10 10:27:37 +02:00
yuri
215b572d70 color fix 2016-02-10 10:01:46 +02:00
yuri
bc72f7f3e6 record dachlet check access 2016-02-09 16:18:09 +02:00
yuri
b449473f10 fix typo 2016-02-09 15:45:44 +02:00
yuri
6f23362bfd record dashlet url 2016-02-09 15:32:54 +02:00
yuri
074d2cc119 fix role 2016-02-09 12:33:20 +02:00
yuri
725cd224c1 fix portal html and 2016 2016-02-09 11:55:21 +02:00
yuri
2baee2398b fix portal html 2016-02-09 11:44:17 +02:00
yuri
1a8f8875fd fix show hide field 2016-02-09 10:46:40 +02:00
yuri
9cecea8317 theme in diff 2016-02-09 10:45:53 +02:00
yuri
05ac6ea0b8 fix cache 2016-02-08 18:22:56 +02:00
yuri
cce8186ccf fix dashlet 2016-02-08 17:39:19 +02:00
yuri
b143d1a30e show hide field for middle record 2016-02-08 17:09:50 +02:00
yuri
4c5f2e6b40 fixes 2016-02-08 16:54:58 +02:00
yuri
6bc2c7a576 theme manager fix 2016-02-08 16:03:09 +02:00
yuri
2412c7521b theme css use cacheTimestamp 2016-02-08 15:59:07 +02:00
yuri
c964897c59 fix list view show more 2016-02-08 13:00:47 +02:00
yuri
6abdc001e5 attachment: forbid parent 2016-02-08 11:56:47 +02:00
yuri
83943d1daa fix auto follow notification 2016-02-08 11:06:52 +02:00
yuri
eedd54d10a fix email modal 2016-02-05 16:40:03 +02:00
yuri
c8a3736816 attachment: small changes 2016-02-05 16:31:31 +02:00
yuri
e727196424 email address search check email create access 2016-02-05 16:06:07 +02:00
yuri
d0f715863c cleanup attachments w/o parent 2016-02-05 15:57:29 +02:00
yuri
ae94113370 attachment set size if empty 2016-02-05 12:43:16 +02:00
yuri
3fbe4970b2 clientDefs compose modal view 2016-02-05 12:31:54 +02:00
yuri
54e6e3bf39 fix mail sender 2016-02-05 12:16:17 +02:00
yuri
f2a7d3ae86 fix array 2016-02-05 11:06:31 +02:00
yuri
3799dd739b Attachment Multiple: sourceList to field manager 2016-02-05 10:55:16 +02:00
yuri
3161419682 fix pdf 2016-02-04 17:41:50 +02:00
yuri
bdc277e557 fix pdf 2016-02-04 17:39:38 +02:00
yuri
a515cd29a0 fix pdf 2016-02-04 17:38:26 +02:00
yuri
770a2b83f0 Pdf service 2016-02-04 17:35:48 +02:00
yuri
f9df8a757d attachment getFilePath method 2016-02-04 15:57:53 +02:00
yuri
6d17017c2d wysiwyg: auto height 2016-02-04 13:08:18 +02:00
yuri
9fea9d46fb fix attachment entry point 2016-02-04 12:44:37 +02:00
yuri
3925d83b68 when select display only not empty categories 2016-02-04 12:30:43 +02:00
yuri
ca23f1d58e add document 2016-02-04 12:15:28 +02:00
yuri
4492293464 new view naming 2016-02-03 15:30:33 +02:00
yuri
cd0a32846c added theme 2016-02-03 11:52:16 +02:00
yuri
f5d0a80626 impoty: empty assigned user 2016-02-03 10:43:42 +02:00
yuri
1b0fdad357 chache user layout 2016-02-02 17:03:28 +02:00
yuri
9ea5e3f4d0 email-to-case: no distribution 2016-02-02 15:58:40 +02:00
yuri
79416d7ac5 fix note error 2016-02-02 15:54:35 +02:00
yuri
52c6ecbcb6 new view name standard 2016-02-02 15:37:09 +02:00
yuri
f6732e72d2 notification: related id 2016-02-02 12:48:31 +02:00
yuri
bbf2128c8a fix notification all read 2016-02-02 12:19:01 +02:00
yuri
d5ba8058e4 notification list view link 2016-02-02 11:28:32 +02:00
yuri
926e243aa3 Notification remove 2016-02-01 17:36:53 +02:00
yuri
ed7d24bc8f notification fronend refactor 2016-02-01 17:09:45 +02:00
yuri
4f4069c3d7 case distribution small change 2016-02-01 15:49:03 +02:00
yuri
9426a09a40 fix stream post view 2016-02-01 11:51:53 +02:00
yuri
bfd9b40a5b fix email create contact 2016-02-01 11:04:39 +02:00
yuri
e94da5c189 select manager: addAndWhere addOrWhere 2016-01-29 16:56:51 +02:00
yuri
36847497fc fix case assignment if email is duplicate 2016-01-29 16:30:09 +02:00
yuri
0bb52a11ea fix email sent filter 2016-01-29 15:08:34 +02:00
yuri
9f7043a85d isFromTeams filter 2016-01-29 14:54:46 +02:00
yuri
eafb8bd2cb fix portal auth 2016-01-29 11:36:17 +02:00
yuri
63eeea7b26 lang 2016-01-29 11:29:07 +02:00
yuri
eac2372e00 edit dd for stream panel 2016-01-29 11:26:04 +02:00
yuri
d9e972684c change portal user name autofill 2016-01-29 11:12:39 +02:00
yuri
aaa9d1a7b4 add items to default filter layouts 2016-01-29 10:46:57 +02:00
yuri
df9a778ba0 portal dev 2016-01-28 17:55:28 +02:00
yuri
68612cf9d1 fux uk_UA 2016-01-28 16:26:07 +02:00
yuri
26b72ea73d Added Mass Email link 2016-01-28 16:19:59 +02:00
yuri
e8c570f5df fix emailReplyToAllByDefault 2016-01-28 16:10:29 +02:00
yuri
abbf601325 Preferences: replyEmailToAllByDefault 2016-01-28 15:57:23 +02:00
yuri
1cd73e2f50 fix lang 2016-01-28 15:33:11 +02:00
yuri
4b49bf280b portalUserLimit 2016-01-28 15:23:44 +02:00
yuri
48302fae95 code improvements 2016-01-28 13:00:39 +02:00
yuri
c525698427 fix reply cc 2016-01-27 16:29:47 +02:00
yuri
64738979e3 fix campaign revenue sum 2016-01-27 16:17:06 +02:00
yuri
bed7434cd0 Settings: added readableDateFormatDisabled 2016-01-27 15:42:19 +02:00
yuri
95f23863d4 attachment: sourceId 2016-01-27 11:56:22 +02:00
yuri
1406527755 text field parameters added 2016-01-27 11:08:16 +02:00
yuri
78395407e3 load jsLibs before login 2016-01-27 10:53:28 +02:00
yuri
26e5c802b6 portal dev 2016-01-26 16:26:10 +02:00
yuri
2f5f64e53c added d/m/y date format 2016-01-26 11:17:22 +02:00
yuri
a1c95fe0e9 kb changes 2016-01-26 11:17:09 +02:00
yuri
eb17c54dd6 opportunity assigned user not required 2016-01-25 15:58:51 +02:00
yuri
f3207a271a fix acl delete own record 2016-01-25 11:25:55 +02:00
yuri
ea76104b80 role: change order 2016-01-25 11:25:39 +02:00
yuri
3d5be39d46 fix quick create acl 2016-01-25 11:25:19 +02:00
yuri
1678bb070e dashlets changes 2016-01-22 16:52:08 +02:00
yuri
470a49d6cd fix record list dashlet 2016-01-22 16:04:02 +02:00
yuri
bb11a26816 role: clear cache on team change 2016-01-22 16:01:17 +02:00
yuri
06e6b19c40 siteUrl and text search filters 2016-01-22 15:43:37 +02:00
yuri
f085bf5e58 dashlet layouts dev 2016-01-22 15:01:30 +02:00
yuri
2437388bf0 dashboard improvements 2016-01-21 17:47:45 +02:00
yuri
fb70c9dce6 calendar size fix 2016-01-21 10:35:28 +02:00
yuri
30384a713e calendar dashlet size 2016-01-20 18:12:17 +02:00
yuri
cc26345db6 Merge branch 'master' of https://github.com/espocrm/espocrm 2016-01-20 18:03:45 +02:00
yuri
f29534531d dashboard improvements 2016-01-20 18:03:37 +02:00
Yuri Kuznetsov
00a2eae74e Merge pull request #82 from alasdaircr/PATCH_warning
Typo in variable name
2016-01-20 15:19:51 +02:00
Alasdair Campbell
283cac586d PATCH: warning 2016-01-20 11:03:20 +00:00
yuri
ca33cab247 fix currency change trigger 2016-01-20 12:51:09 +02:00
yuri
52fb9f8b0a increase max lenngth of attachment field 2016-01-18 16:54:49 +02:00
yuri
10fee4d974 after relate event 2016-01-18 16:48:00 +02:00
yuri
6edf6ae9f2 refactor stream query 2016-01-18 16:23:54 +02:00
yuri
58e0ee5721 portal dev 2016-01-18 15:35:23 +02:00
yuri
6354c72334 knowledge base dev 2016-01-15 16:36:20 +02:00
yuri
f93e6c03c9 knowledge base dev 2016-01-15 15:23:49 +02:00
yuri
ef5fec4282 portal dev 2016-01-15 14:41:58 +02:00
yuri
6dc13f229e is not read 2016-01-15 13:27:49 +02:00
yuri
f148bb0089 email: is read and is important filters 2016-01-15 13:24:06 +02:00
yuri
9b9a472dd5 portal dev 2016-01-15 12:52:23 +02:00
yuri
f965acf384 kb fixes 2016-01-14 17:12:36 +02:00
yuri
f7e3fb3e15 knowledge base 2016-01-14 16:41:27 +02:00
yuri
a8396df545 document folder acl 2016-01-14 12:02:29 +02:00
yuri
3cd91ba15e cleanup 2016-01-13 17:39:05 +02:00
yuri
2433f0d626 document changes 2016-01-13 17:30:24 +02:00
yuri
c0b5661ef4 fix list tree 2016-01-13 17:30:09 +02:00
yuri
ff81306e90 fix list and list tree views 2016-01-13 16:40:27 +02:00
yuri
d51e38bb6e portal dev 2016-01-13 13:08:35 +02:00
yuri
5c1ae72bc6 fix text fields undefined 2016-01-13 11:16:46 +02:00
yuri
b1ada57d93 portal dev 2016-01-12 15:51:30 +02:00
yuri
e7afe68868 dev 2016-01-11 16:02:03 +02:00
yuri
f6f58c679d fixes 2016-01-08 15:18:48 +02:00
yuri
4b4f8d2cc4 dev and fix 2016-01-08 14:50:46 +02:00
yuri
7dc230d731 portal development 2016-01-08 14:39:59 +02:00
yuri
db7e33fa61 development 2016-01-05 18:04:15 +02:00
yuri
e4d6b13d27 fixes 2016-01-05 13:00:30 +02:00
yuri
3177945146 portal auth 2016-01-05 11:49:14 +02:00
yuri
ef9c5da2d6 portal development 2016-01-04 16:14:18 +02:00
yuri
18423c0ba1 acl portal 2015-12-29 15:16:49 +02:00
yuri
f629cb3af6 role appearance change 2015-12-29 12:09:58 +02:00
yuri
d081d6b2d9 portal dev 2015-12-28 17:37:17 +02:00
yuri
ab3dfe4bf6 portal dev 2015-12-28 16:58:16 +02:00
yuri
a472e6d348 Portal Roles 2015-12-28 14:43:10 +02:00
yuri
fa014bb232 modal fixes 2015-12-25 18:31:20 +02:00
yuri
bdee4068c0 fixes 2015-12-25 18:01:02 +02:00
yuri
025e7134fc acl changes and portal entity 2015-12-25 17:01:50 +02:00
yuri
a388638a91 fix field readOnly 2015-12-25 15:52:30 +02:00
yuri
e7b8283fdc modals fixes 2015-12-25 12:30:56 +02:00
yuri
d7172f8ebe acl changes and fixes 2015-12-25 12:04:41 +02:00
yuri
bc0ea9ab3a readOnly attributes 2015-12-25 10:59:28 +02:00
yuri
838f8ba3b3 fix contact layout 2015-12-24 18:03:24 +02:00
yuri
4da13a55cf hasOne link and portal user fields 2015-12-24 18:01:59 +02:00
yuri
8489bca8c0 portal fields 2015-12-24 14:59:01 +02:00
yuri
d620b36dd1 Merge branch 'hotfix/3.9.3' 2015-12-23 12:37:19 +02:00
yuri
89b5daebee fix ua lang 2015-12-23 12:37:05 +02:00
yuri
fdea3231e9 acl and search 2015-12-23 12:09:42 +02:00
yuri
882b74a31f fix acl and relogin issue 2015-12-23 10:43:49 +02:00
yuri
181a680296 acl and export 2015-12-23 10:06:35 +02:00
yuri
d7d93e6a79 role panels 2015-12-22 16:59:52 +02:00
yuri
d6ee607b9d fix email save 2015-12-22 15:57:01 +02:00
yuri
75f26e3ecc navigate in quick view 2 2015-12-22 15:43:35 +02:00
yuri
e959bcb369 trim 2015-12-22 11:28:56 +02:00
yuri
c9eea8796f next prev title 2015-12-21 17:28:38 +02:00
yuri
8d44e0b1ad next prev in preview 2015-12-21 17:26:37 +02:00
yuri
947f47ef25 role add field translte 2015-12-21 16:03:32 +02:00
yuri
27e5df5367 field level acl frontend 2 2015-12-21 15:15:06 +02:00
yuri
b7b64d7b32 duplicate changes 2015-12-21 11:25:23 +02:00
yuri
4f9a6a0dd0 acl table change 2015-12-18 18:00:49 +02:00
yuri
bb5543fc81 role field security detail view 2015-12-18 17:45:42 +02:00
yuri
8b1ffaac3f disable template 2015-12-18 11:46:06 +02:00
yuri
0961ceb203 field level client side 1 2015-12-17 16:44:32 +02:00
yuri
96ca44f91b fix acl table 2015-12-17 15:14:23 +02:00
yuri
869bf46070 attributes instead of fields; server side field level security 2015-12-17 14:58:19 +02:00
yuri
af039971a6 fix acl 2015-12-16 17:44:39 +02:00
yuri
5b614691fd acl table refactor 2015-12-16 17:36:11 +02:00
yuri
39295d2d6f merge 2015-12-16 11:27:24 +02:00
yuri
51952a9283 fix issue that email reply stylesheet was broken 2015-12-16 11:26:16 +02:00
yuri
e88ffc1270 wyywyg full screen button 2015-12-15 18:23:08 +02:00
yuri
c7fccac10c summernote upgrade 2015-12-15 18:18:23 +02:00
yuri
cdb4b4e7bd summernote upgrade 2015-12-15 18:18:02 +02:00
yuri
3fa6ac5042 Merge branch 'master' of https://github.com/espocrm/espocrm 2015-12-15 15:59:04 +02:00
Yuri Kuznetsov
47e52b6670 Merge pull request #73 from ayman-alkom/master
Translate Turkish dates
2015-12-15 15:58:45 +02:00
yuri
461eb480f1 move populate assigned user and assigned teams to view 2015-12-15 14:49:55 +02:00
yuri
b73cbef3d4 Merge branch 'hotfix/3.9.3' 2015-12-15 11:03:16 +02:00
yuri
c6abb0a531 fix stream list 2015-12-15 11:03:03 +02:00
yuri
7227122fd8 Merge branch 'hotfix/3.9.3' 2015-12-15 11:01:18 +02:00
yuri
cf3228466d fix list 2015-12-15 11:01:06 +02:00
yuri
4452db65ed Merge branch 'hotfix/3.9.3' 2015-12-15 10:30:33 +02:00
yuri
635f0d4891 fix remove from list 2015-12-15 10:30:23 +02:00
yuri
3da60c1ada rdb before after methods added 2015-12-14 17:39:43 +02:00
yuri
7187156390 rdb relate methods 2015-12-14 17:34:20 +02:00
yuri
b764fd8da2 fix email select manager 2015-12-14 15:44:04 +02:00
yuri
da6d590cc1 select manager change 2015-12-14 15:43:33 +02:00
Ayman Alkom
3b265056c2 Translate Turkish dates 2015-12-14 15:11:39 +02:00
yuri
7fe08f1669 acl fixes 2015-12-14 11:58:14 +02:00
yuri
dc0a616ec8 select manager changes 2015-12-11 14:43:02 +02:00
yuri
7dc43e5ccc entity class fixes 2015-12-11 12:50:23 +02:00
yuri
9a78cf2389 acl changes 2015-12-11 12:17:15 +02:00
yuri
b0e050ceac email assignedUsers 2015-12-10 18:05:37 +02:00
yuri
90f06fc532 setReadOnly locked 2015-12-10 16:45:13 +02:00
yuri
6d3d922290 hide show field changhe 2015-12-10 16:04:02 +02:00
yuri
3c2bc8871e fix 2015-12-10 16:01:43 +02:00
yuri
16399bf71d acl email 2015-12-10 15:48:11 +02:00
yuri
abf963099c acl load and promises 2015-12-10 12:01:01 +02:00
yuri
b7ab6953cd fixes in record view 2015-12-09 18:53:13 +02:00
yuri
1628821cdd changes in record 2015-12-09 18:46:05 +02:00
yuri
fbf98e9754 frontend client refactor 2015-12-09 17:21:05 +02:00
yuri
b4ec610fc2 record fixes 2015-12-09 17:20:53 +02:00
yuri
e2a63729b6 record view refactor 2015-12-09 12:58:45 +02:00
yuri
b72924c126 panel view disabled check 2015-12-08 17:15:04 +02:00
yuri
115bcc626a Merge branch 'stable' 2015-12-08 17:05:48 +02:00
yuri
3fabdc1d44 Merge branch 'hotfix/3.9.2' 2015-12-08 16:23:20 +02:00
yuri
7fdb40b44e more view refactoring 2015-12-08 16:21:35 +02:00
yuri
929eeddce7 grand record view refactor 2015-12-08 12:59:19 +02:00
yuri
ea5d27a87f detail view refactor 2015-12-07 17:08:17 +02:00
yuri
0da898a242 show hide field method changes 2015-12-04 16:16:10 +02:00
yuri
4a14cb0e8c showPanel hidePanel methods 2015-12-04 15:55:41 +02:00
yuri
509c7f3989 stream acl 2015-12-04 15:22:18 +02:00
yuri
3212f59cf2 fix css 2015-12-04 14:12:59 +02:00
yuri
ad25e13cf0 acl view change 2015-12-04 12:42:36 +02:00
yuri
fadd4ffe42 refactor acl table 2015-12-04 12:02:38 +02:00
yuri
7a3e36c092 notifications suprt parent 2015-12-04 11:10:34 +02:00
yuri
730e8143e7 scope disabled check 2015-12-03 15:37:59 +02:00
yuri
a401c4cd4c set is fetched 2015-12-03 15:34:59 +02:00
yuri
51bf1343c8 notifications and acl 2015-12-03 15:32:35 +02:00
yuri
343986bf83 email notificator acl check 2015-12-03 14:46:46 +02:00
yuri
d0b3ab57de Merge branch 'hotfix/3.9.2' 2015-12-03 14:26:04 +02:00
yuri
6ca6f45b58 stream and acl 2015-12-03 13:54:07 +02:00
yuri
d3b50c077b ability to disable scope 2015-12-03 12:42:25 +02:00
yuri
2c4ba8c1b5 lang 2015-12-03 11:08:29 +02:00
yuri
a8e00dda0b fix main view $el emptyied 2015-12-02 13:22:27 +02:00
yuri
7b88c008de fix naming 2015-12-02 13:09:20 +02:00
yuri
2c251133af naming fix 2015-12-02 13:07:48 +02:00
yuri
c079b256f0 fix naming 2015-12-02 12:40:27 +02:00
yuri
6a9abab7ea refactor settings views 2015-12-02 12:23:10 +02:00
yuri
f59c217053 fields/base small change 2015-12-02 11:18:51 +02:00
yuri
57008f834a Merge branch 'hotfix/3.9.2' 2015-12-02 10:47:36 +02:00
yuri
7b90e74b97 view refactoring 2015-12-02 10:47:28 +02:00
yuri
7cfae284ae remove main.html 2015-11-30 15:25:18 +02:00
yuri
d946eed86a html folder 2015-11-30 15:22:52 +02:00
yuri
030e4ac7ab Merge branch 'hotfix/3.9.2' 2015-11-30 14:23:22 +02:00
yuri
f98d5a4ee9 fix typo 2015-11-30 10:26:08 +02:00
yuri
c653d731cc fix readme 2015-11-27 17:14:07 +02:00
yuri
f4582ac3a6 change readme 2015-11-27 17:11:14 +02:00
yuri
5bb7842585 client manager 2015-11-27 16:58:05 +02:00
1351 changed files with 32144 additions and 8791 deletions

11
.gitignore vendored
View File

@@ -6,13 +6,14 @@
/data/config.php
/build
/node_modules
/client
/test.php
/main.html
/frontend/client/css/espo.css
/frontend/client/css/espo-vertical.css
/frontend/client/css/sakura.css
/frontend/client/css/sakura-vertical.css
/client/css/espo.css
/client/css/espo-vertical.css
/client/css/sakura.css
/client/css/sakura-vertical.css
/client/css/violet.css
/client/css/violet-vertical.css
/tests/testData/cache/*
composer.phar
vendor/

3
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,3 @@
Before we can merge your pull request you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
[Code Style Guidelines](https://github.com/espocrm/espocrm/wiki/Code-Style-Guidelines).

View File

@@ -23,6 +23,7 @@ module.exports = function (grunt) {
var jsFilesToMinify = [
'client/lib/jquery-2.1.4.min.js',
'client/lib/underscore-min.js',
'client/lib/es6-promise.min.js',
'client/lib/backbone-min.js',
'client/lib/handlebars.js',
'client/lib/base64.js',
@@ -64,39 +65,55 @@ module.exports = function (grunt) {
yuicompress: true,
},
files: {
'frontend/client/css/espo.css': 'frontend/less/espo/main.less',
},
},
sakura: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/sakura.css': 'frontend/less/sakura/main.less',
},
'client/css/espo.css': 'frontend/less/espo/main.less',
}
},
espoVertical: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/espo-vertical.css': 'frontend/less/espo-vertical/main.less',
'client/css/espo-vertical.css': 'frontend/less/espo-vertical/main.less',
}
},
sakura: {
options: {
yuicompress: true,
},
files: {
'client/css/sakura.css': 'frontend/less/sakura/main.less',
}
},
sakuraVertical: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/sakura-vertical.css': 'frontend/less/sakura-vertical/main.less',
'client/css/sakura-vertical.css': 'frontend/less/sakura-vertical/main.less',
}
},
violet: {
options: {
yuicompress: true,
},
files: {
'client/css/violet.css': 'frontend/less/violet/main.less',
}
},
violetVertical: {
options: {
yuicompress: true,
},
files: {
'client/css/violet-vertical.css': 'frontend/less/violet-vertical/main.less',
}
}
},
cssmin: {
minify: {
files: {
'build/tmp/client/css/espo.css': [
'frontend/client/css/espo.css',
'client/css/espo.css',
]
}
},
@@ -107,13 +124,13 @@ module.exports = function (grunt) {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
},
'build/tmp/client/espo.min.js': jsFilesToMinify.map(function (item) {
return 'frontend/' + item;
return '' + item;
})
},
copy: {
frontendFolders: {
expand: true,
cwd: 'frontend/client',
cwd: 'client',
src: [
'src/**',
'res/**',
@@ -128,13 +145,13 @@ module.exports = function (grunt) {
dest: 'build/tmp/client',
},
frontendHtml: {
src: 'frontend/html/reset.html',
src: 'frontend/reset.html',
dest: 'build/tmp/reset.html'
},
frontendLib: {
expand: true,
dot: true,
cwd: 'frontend/client/lib',
cwd: 'client/lib',
src: '**',
dest: 'build/tmp/client/lib/',
},
@@ -147,7 +164,9 @@ module.exports = function (grunt) {
'custom/**',
'data/.data',
'install/**',
'portal/**',
'vendor/**',
'html/**',
'bootstrap.php',
'cron.php',
'rebuild.php',
@@ -212,8 +231,12 @@ module.exports = function (grunt) {
},
files: [
{
src: 'frontend/html/main.html',
dest: 'build/tmp/main.html'
src: 'build/tmp/html/main.html',
dest: 'build/tmp/html/main.html'
},
{
src: 'build/tmp/html/portal.html',
dest: 'build/tmp/html/portal.html'
}
]
},

View File

@@ -26,9 +26,9 @@ Create an issue [here](https://github.com/espocrm/espocrm/issues) or post on our
Never update composer dependencies if you are going to contribute code back.
Now you can build.
Now you can build. Build will create compiled css files.
If your repository is accessible via a web server then you can run EspoCRM by url `http://PROJECT_URL/frontend`. To compose a proper config.php and populate database you can run install by opening `http(s)://{YOUR_CRM_URL}/install` location in a browser. Also you need to run build before to have compiled css.
To compose a proper config.php and populate database you can run install by opening `http(s)://{YOUR_CRM_URL}/install` location in a browser. Then open `data/config.php` file and add `isDeveloperMode => true`.
### How to build

12
api/v1/portal-access/.htaccess Executable file
View File

@@ -0,0 +1,12 @@
RewriteEngine On
# Some hosts may require you to use the `RewriteBase` directive.
# If you need to use the `RewriteBase` directive, it should be the
# absolute physical path to the directory that contains this htaccess file.
#
# RewriteBase /
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

View File

@@ -0,0 +1,39 @@
<?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.
************************************************************************/
require_once('../../../bootstrap.php');
if (!empty($_GET['portalId'])) {
$portalId = $_GET['portalId'];
} else {
$portalId = explode('/', $_SERVER['REQUEST_URI'])[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
}
$app = new \Espo\Core\Portal\Application($portalId);
$app->run();

15
api/v1/portal-access/web.config Executable file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="rule 1G" stopProcessing="true">
<match url="^" />
<action type="Rewrite" url="index.php" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,93 @@
<?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\Acl;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Attachment extends \Espo\Core\Acl\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
{
if ($user->isAdmin()) {
return true;
}
if ($entity->get('parentType') === 'Settings') {
return true;
}
$parent = null;
$hasParent = false;
if ($entity->get('parentId') && $entity->get('parentType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
} else if ($entity->get('relatedId') && $entity->get('relatedType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
}
if ($hasParent) {
if ($parent) {
if ($parent->getEntityType() === 'Note') {
if ($parent->get('parentId') && $parent->get('parentType')) {
$parentOfParent = $this->getEntityManager()->getEntity($parent->get('parentType'), $parent->get('parentId'));
if ($parentOfParent && $this->getAclManager()->checkEntity($user, $parentOfParent)) {
return true;
}
} else {
return true;
}
} else {
if ($this->getAclManager()->checkEntity($user, $parent)) {
return true;
}
}
}
} else {
return true;
}
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
}
return false;
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;
}
return false;
}
}

View File

@@ -44,8 +44,8 @@ class Email extends \Espo\Core\Acl\Base
if ($data === false) {
return false;
}
if (is_array($data)) {
if (empty($data['read']) || $data['read'] == 'no') {
if (is_object($data)) {
if ($data->read === false || $data->read === 'no') {
return false;
}
}
@@ -62,18 +62,19 @@ class Email extends \Espo\Core\Acl\Base
public function checkIsOwner(User $user, Entity $entity)
{
if ($entity->has('assignedUserId')) {
if ($user->id === $entity->get('assignedUserId')) {
return true;
}
if ($user->id === $entity->get('assignedUserId')) {
return true;
}
if ($user->id === $entity->get('createdById')) {
return true;
}
if ($entity->hasLinkMultipleId('assignedUsers', $user->id)) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
<?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\Acl;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Notification extends \Espo\Core\Acl\Base
{
public function checkIsOwner(User $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
<?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\Acl;
use \Espo\ORM\Entity;
class User extends \Espo\Core\Acl\Base
{
public function checkIsOwner(\Espo\Entities\User $user, Entity $entity)
{
return $user->id === $entity->id;
}
}

View File

@@ -0,0 +1,93 @@
<?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\AclPortal;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Attachment extends \Espo\Core\AclPortal\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
{
if ($user->isAdmin()) {
return true;
}
if ($entity->get('parentType') === 'Settings') {
return true;
}
$parent = null;
$hasParent = false;
if ($entity->get('parentId') && $entity->get('parentType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
} else if ($entity->get('relatedId') && $entity->get('relatedType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
}
if ($hasParent) {
if ($parent) {
if ($parent->getEntityType() === 'Note') {
if ($parent->get('parentId') && $parent->get('parentType')) {
$parentOfParent = $this->getEntityManager()->getEntity($parent->get('parentType'), $parent->get('parentId'));
if ($parentOfParent && $this->getAclManager()->checkEntity($user, $parentOfParent)) {
return true;
}
} else {
return true;
}
} else {
if ($this->getAclManager()->checkEntity($user, $parent)) {
return true;
}
}
}
} else {
return true;
}
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
}
return false;
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,71 @@
<?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\AclPortal;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Email extends \Espo\Core\AclPortal\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
}
if ($data === false) {
return false;
}
if (is_object($data)) {
if ($data->read === false || $data->read === 'no') {
return false;
}
}
if (!$entity->has('usersIds')) {
$entity->loadLinkMultipleField('users');
}
$userIdList = $entity->get('usersIds');
if (is_array($userIdList) && in_array($user->id, $userIdList)) {
return true;
}
return false;
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
<?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\AclPortal;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Notification extends \Espo\Core\AclPortal\Base
{
public function checkIsOwner(User $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
<?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\AclPortal;
use \Espo\ORM\Entity;
class User extends \Espo\Core\AclPortal\Base
{
public function checkIsOwner(\Espo\Entities\User $user, Entity $entity)
{
return $user->id === $entity->id;
}
}

View File

@@ -35,16 +35,20 @@ class App extends \Espo\Core\Controllers\Base
{
public function actionUser()
{
$preferences = $this->getPreferences()->toArray();
$preferences = $this->getPreferences()->getValues();
unset($preferences['smtpPassword']);
$user = $this->getUser();
if (!$user->has('teamsIds')) {
$user->loadLinkMultipleField('teams');
}
if ($user->get('isPortalUser')) {
$user->loadAccountField();
$user->loadLinkMultipleField('accounts');
}
return array(
'user' => $user->toArray(),
'user' => $user->getValues(),
'acl' => $this->getAcl()->getMap(),
'preferences' => $preferences,
'token' => $this->getUser()->get('token')

View File

@@ -34,13 +34,16 @@ use \Espo\Core\Exceptions\BadRequest;
class Attachment extends \Espo\Core\Controllers\Record
{
public function actionUpload($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (!$this->getAcl()->checkScope('Attachment', 'create')) {
throw new Forbidden();
}
list($prefix, $contents) = explode(',', $data);
$contents = base64_decode($contents);

View File

@@ -35,9 +35,12 @@ use \Espo\Core\Exceptions\Error;
class Email extends \Espo\Core\Controllers\Record
{
public function actionGetCopiedAttachments($params, $data, $request)
public function postActionGetCopiedAttachments($params, $data, $request)
{
$id = $request->get('id');
if (empty($data['id'])) {
throw new BadRequest();
}
$id = $data['id'];
return $this->getRecordService()->getCopiedAttachments($id);
}

View File

@@ -25,14 +25,22 @@
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
************************************************************************/
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Forbidden;
class EmailAddress extends \Espo\Core\Controllers\Record
{
public function actionSearchInAddressBook($params, $data, $request)
{
if (!$this->getAcl()->checkScope('Email')) {
throw new Forbidden();
}
if (!$this->getAcl()->checkScope('Email', 'create')) {
throw new Forbidden();
}
$q = $request->get('q');
$limit = intval($request->get('limit'));
if (empty($limit) || $limit > 30) {

View File

@@ -69,6 +69,9 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (!empty($data['stream'])) {
$params['stream'] = $data['stream'];
}
if (!empty($data['disabled'])) {
$params['disabled'] = $data['disabled'];
}
if (!empty($data['sortBy'])) {
$params['sortBy'] = $data['sortBy'];
}

View File

@@ -37,6 +37,13 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
{
public static $defaultAction = 'list';
protected function checkControllerAccess()
{
if (!$this->getAcl()->checkScope('ExternalAccount')) {
throw new Forbidden();
}
}
public function actionList($params, $data, $request)
{
$integrations = $this->getEntityManager()->getRepository('Integration')->find();

View File

@@ -31,7 +31,6 @@ namespace Espo\Controllers;
class I18n extends \Espo\Core\Controllers\Base
{
public function actionRead($params, $data)
{
return $this->getContainer()->get('language')->getAll();

View File

@@ -25,7 +25,7 @@
*
* 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;

View File

@@ -31,7 +31,7 @@ namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
class Notification extends \Espo\Core\Controllers\Base
class Notification extends \Espo\Core\Controllers\Record
{
public static $defaultAction = 'list';
@@ -44,10 +44,12 @@ class Notification extends \Espo\Core\Controllers\Base
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
$after = $request->get('after');
$params = array(
'offset' => $offset,
'maxSize' => $maxSize,
'after' => $after
);
$result = $this->getService('Notification')->getList($userId, $params);
@@ -69,5 +71,30 @@ class Notification extends \Espo\Core\Controllers\Base
$userId = $this->getUser()->id;
return $this->getService('Notification')->markAllRead($userId);
}
public function actionExport($params, $data, $request)
{
throw new Error();
}
public function actionMassUpdate($params, $data, $request)
{
throw new Error();
}
public function actionCreateLink($params, $data, $request)
{
throw new Error();
}
public function actionRemoveLink($params, $data, $request)
{
throw new Error();
}
public function actionMerge($params, $data, $request)
{
throw new Error();
}
}

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\Controllers;
use \Espo\Core\Exceptions\Forbidden;
class Portal extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()
{
$portalPermission = $this->getAcl()->get('portalPermission');
if (!$portalPermission || $portalPermission === 'no') {
throw new Forbidden();
}
}
}

View File

@@ -0,0 +1,34 @@
<?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;
class PortalRole extends \Espo\Core\Controllers\Record
{
}

View File

@@ -88,6 +88,14 @@ class Preferences extends \Espo\Core\Controllers\Base
throw new BadRequest();
}
if ($this->getAcl()->getLevel('Preferences', 'read') === 'no') {
throw new Forbidden();
}
foreach ($this->getAcl()->getScopeForbiddenAttributeList('Preferences', 'edit') as $attribute) {
unset($data[$attribute]);
}
if (array_key_exists('smtpPassword', $data)) {
$data['smtpPassword'] = $this->getCrypt()->encrypt($data['smtpPassword']);
}
@@ -127,6 +135,10 @@ class Preferences extends \Espo\Core\Controllers\Base
$entity->clear('smtpPassword');
foreach ($this->getAcl()->getScopeForbiddenAttributeList('Preferences', 'read') as $attribute) {
$entity->clear($attribute);
}
return $entity->toArray();
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
@@ -45,6 +46,9 @@ class Settings extends \Espo\Core\Controllers\Base
unset($data[$field]);
}
}
$data['jsLibs'] = $this->getMetadata()->get('app.jsLibs');
return $data;
}

View File

@@ -55,22 +55,16 @@ class User extends \Espo\Core\Controllers\Record
return $this->getAclManager()->getMap($user);
}
public function actionChangeOwnPassword($params, $data, $request)
public function postActionChangeOwnPassword($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (!array_key_exists('password', $data) || !array_key_exists('currentPassword', $data)) {
throw new BadRequest();
}
return $this->getService('User')->changePassword($this->getUser()->id, $data['password'], true, $data['currentPassword']);
}
public function actionChangePasswordByRequest($params, $data, $request)
public function postActionChangePasswordByRequest($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (empty($data['requestId']) || empty($data['password'])) {
throw new BadRequest();
}
@@ -89,23 +83,27 @@ class User extends \Espo\Core\Controllers\Record
$this->getEntityManager()->removeEntity($p);
return $this->getService('User')->changePassword($userId, $data['password']);
if ($this->getService('User')->changePassword($userId, $data['password'])) {
return array(
'url' => $p->get('url')
);
}
}
public function actionPasswordChangeRequest($params, $data, $request)
public function postActionPasswordChangeRequest($params, $data, $request)
{
if (!$request->isPost()) {
throw new Forbidden();
}
if (empty($data['userName']) || empty($data['emailAddress'])) {
throw new BadRequest();
}
$userName = $data['userName'];
$emailAddress = $data['emailAddress'];
$url = null;
if (!empty($data['url'])) {
$url = $data['url'];
}
return $this->getService('User')->passwordChangeRequest($userName, $emailAddress);
return $this->getService('User')->passwordChangeRequest($userName, $emailAddress, $url);
}
}

View File

@@ -79,19 +79,44 @@ class Acl
return $this->getAclManager()->checkReadOnlyOwn($this->getUser(), $scope);
}
public function check($subject, $action = null, $isOwner = null, $inTeam = null)
public function check($subject, $action = null)
{
return $this->getAclManager()->check($this->getUser(), $subject, $action, $isOwner, $inTeam) ;
return $this->getAclManager()->check($this->getUser(), $subject, $action);
}
public function checkScope($scope, $action = null, $isOwner = null, $inTeam = null, $entity = null)
public function checkScope($scope, $action = null)
{
return $this->getAclManager()->checkScope($this->getUser(), $scope, $action, $isOwner, $inTeam, $entity) ;
return $this->getAclManager()->checkScope($this->getUser(), $scope, $action);
}
public function checkEntity(Entity $entity, $action = 'read')
{
return $this->getAclManager()->checkEntity($this->getUser(), $entity, $action);
}
public function checkUser($permission, User $entity)
{
return $this->getAclManager()->checkUser($this->getUser(), $permission, $entity);
}
public function checkIsOwner(Entity $entity)
{
return $this->getAclManager()->checkIsOwner($this->getUser(), $entity);
}
public function checkInTeam(Entity $entity)
{
return $this->getAclManager()->checkInTeam($this->getUser(), $entity);
}
public function getScopeForbiddenAttributeList($scope, $action = 'read', $thresholdLevel = 'no')
{
return $this->getAclManager()->getScopeForbiddenAttributeList($this->getUser(), $scope, $action, $thresholdLevel);
}
public function getScopeForbiddenFieldList($scope, $action = 'read', $thresholdLevel = 'no')
{
return $this->getAclManager()->getScopeForbiddenFieldList($this->getUser(), $scope, $action, $thresholdLevel);
}
}

View File

@@ -42,6 +42,8 @@ class Base implements Injectable
'aclManager'
);
protected $scope;
protected $injections = array();
public function inject($name, $object)
@@ -49,9 +51,10 @@ class Base implements Injectable
$this->injections[$name] = $object;
}
public function __construct()
public function __construct($scope)
{
$this->init();
$this->scope = $scope;
}
protected function init()
@@ -90,27 +93,34 @@ class Base implements Injectable
public function checkReadOnlyTeam(User $user, $data)
{
if (empty($data) || !is_array($data) || !isset($data['read'])) {
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data['read'] === 'team';
return $data->read === 'team';
}
public function checkReadOnlyOwn(User $user, $data)
{
if (empty($data) || !is_array($data) || !isset($data['read'])) {
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data['read'] === 'own';
return $data->read === 'own';
}
public function checkEntity(User $user, Entity $entity, $data, $action)
{
return $this->checkScope($user, $data, $entity->getEntityType(), $action, null, null, $entity);
if ($user->isAdmin()) {
return true;
}
return $this->checkScope($user, $data, $action, $entity);
}
public function checkScope(User $user, $data, $scope, $action = null, $isOwner = null, $inTeam = null, Entity $entity = null)
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = array())
{
if ($user->isAdmin()) {
return true;
}
if (is_null($data)) {
return false;
}
@@ -120,83 +130,103 @@ class Base implements Injectable
if ($data === true) {
return true;
}
if (is_string($data)) {
return true;
}
if (!is_null($action)) {
if (array_key_exists($action, $data)) {
$value = $data[$action];
$isOwner = null;
if (isset($entityAccessData['isOwner'])) {
$isOwner = $entityAccessData['isOwner'];
}
$inTeam = null;
if (isset($entityAccessData['inTeam'])) {
$inTeam = $entityAccessData['inTeam'];
}
if ($value === 'all' || $value === true) {
return true;
}
if (is_null($action)) {
return true;
}
if (!$value || $value === 'no') {
return false;
}
if (!isset($data->$action)) {
return true;
}
if (is_null($isOwner)) {
if ($entity) {
$isOwner = $this->checkIsOwner($user, $entity);
} else {
return true;
}
}
$value = $data->$action;
if ($isOwner) {
if ($value === 'own' || $value === 'team') {
return true;
}
}
if (is_null($inTeam) && $entity) {
$inTeam = $this->checkInTeam($user, $entity);
}
if ($value === 'all' || $value === 'yes' || $value === true) {
return true;
}
if ($inTeam) {
if ($value === 'team') {
return true;
}
}
return false;
if (!$value || $value === 'no') {
return false;
}
if (is_null($isOwner)) {
if ($entity) {
$isOwner = $this->checkIsOwner($user, $entity);
} else {
return true;
}
}
return true;
if ($isOwner) {
if ($value === 'own' || $value === 'team') {
return true;
}
}
if (is_null($inTeam) && $entity) {
$inTeam = $this->checkInTeam($user, $entity);
}
if ($inTeam) {
if ($value === 'team') {
return true;
}
}
return false;
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($entity->has('assignedUserId')) {
if ($user->id === $entity->get('assignedUserId')) {
return true;
if ($entity->hasAttribute('assignedUserId')) {
if ($entity->has('assignedUserId')) {
if ($user->id === $entity->get('assignedUserId')) {
return true;
}
}
} else {
} else if ($entity->hasAttribute('createdById')) {
if ($entity->has('createdById')) {
if ($user->id === $entity->get('createdById')) {
return true;
}
}
}
if ($entity->hasAttribute('assignedUsersIds') && $entity->hasRelation('assignedUsers')) {
if ($entity->hasLinkMultipleId('assignedUsers', $user->id)) {
return true;
}
}
return false;
}
public function checkInTeam(User $user, Entity $entity)
{
$userTeamIds = $user->get('teamsIds');
$userTeamIdList = $user->getLinkMultipleIdList('teams');
if (!$entity->hasRelation('teams') || !$entity->hasField('teamsIds')) {
if (!$entity->hasRelation('teams') || !$entity->hasAttribute('teamsIds')) {
return false;
}
if (!$entity->has('teamsIds')) {
$entity->loadLinkMultipleField('teams');
}
$entityTeamIdList = $entity->getLinkMultipleIdList('teams');
$teamIds = $entity->get('teamsIds');
if (empty($teamIds)) {
if (empty($entityTeamIdList)) {
return false;
}
foreach ($userTeamIds as $id) {
if (in_array($id, $teamIds)) {
foreach ($userTeamIdList as $id) {
if (in_array($id, $entityTeamIdList)) {
return true;
}
}
@@ -205,27 +235,32 @@ class Base implements Injectable
public function checkEntityDelete(User $user, Entity $entity, $data)
{
$result = $this->checkEntity($user, $entity, $data, 'delete');
if (!$result) {
if (is_array($data)) {
if ($data['edit'] != 'no') {
if ($entity->has('createdById') && $entity->get('createdById') == $user->id) {
if (!$entity->has('assignedUserId')) {
if ($user->isAdmin()) {
return true;
}
if ($this->checkEntity($user, $entity, $data, 'delete')) {
return true;
}
if (is_object($data)) {
if ($data->edit !== 'no' || $data->create !== 'no') {
if ($entity->has('createdById') && $entity->get('createdById') == $user->id) {
if (!$entity->has('assignedUserId')) {
return true;
} else {
if (!$entity->get('assignedUserId')) {
return true;
}
if ($entity->get('assignedUserId') == $entity->get('createdById')) {
return true;
} else {
if (!$entity->get('assignedUserId')) {
return true;
}
if ($entity->get('assignedUserId') == $entity->get('createdById')) {
return true;
}
}
}
}
}
}
return $result;
return false;
}
}

View File

@@ -32,31 +32,69 @@ namespace Espo\Core\Acl;
use \Espo\Core\Exceptions\Error;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use \Espo\Core\Utils\Config;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\FieldManager;
use \Espo\Core\Utils\File\Manager as FileManager;
class Table
{
private $data = array(
'table' => array()
protected $type = 'acl';
protected $defaultAclType = 'recordAllTeamOwnNo';
private $data = null;
protected $cacheFilePath;
protected $actionList = ['read', 'stream', 'edit', 'delete', 'create'];
protected $booleanActionList = ['create'];
protected $levelList = ['yes', 'all', 'team', 'own', 'no'];
protected $fieldActionList = ['read', 'edit'];
protected $fieldLevelList = ['yes', 'no'];
protected $valuePermissionList = ['assignmentPermission', 'userPermission', 'portalPermission'];
protected $valuePrtmissionHighestLevels = array(
'assignmentPermission' => 'all',
'userPermission' => 'all',
'portalPermission' => 'yes'
);
private $cacheFile;
private $fileManager;
private $actionList = ['read', 'edit', 'delete'];
private $metadata;
private $levelList = ['all', 'team', 'own', 'no'];
private $fieldManager;
protected $fileManager;
protected $forbiddenAttributesCache = array();
protected $metadata;
protected $forbiddenFieldsCache = array();
public function __construct(\Espo\Entities\User $user, $config = null, $fileManager = null, $metadata = null)
public function __construct(User $user, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManager $fieldManager = null)
{
$this->data = (object) [
'table' => (object) [],
'fieldTable' => (object) [],
'fieldTableQuickAccess' => (object) [],
];
$this->user = $user;
$this->metadata = $metadata;
if ($fieldManager) {
$this->fieldManager = $fieldManager;
}
if (!$this->user->isFetched()) {
throw new Error();
throw new Error('User must be fetched before ACL check.');
}
$this->user->loadLinkMultipleField('teams');
@@ -64,22 +102,46 @@ class Table
if ($fileManager) {
$this->fileManager = $fileManager;
}
$this->valuePermissionList = $this->metadata->get('app.' . $this->type . '.defs.valuePermissionList', $this->valuePermissionList);
$this->cacheFile = 'data/cache/application/acl/' . $user->id . '.php';
$this->initCacheFilePath();
if ($config && $config->get('useCache') && file_exists($this->cacheFile)) {
$cached = include $this->cacheFile;
if ($config && $config->get('useCache') && file_exists($this->cacheFilePath)) {
$cached = include $this->cacheFilePath;
$this->data = $cached;
$this->initSolid();
} else {
$this->load();
$this->initSolid();
if ($config && $fileManager && $config->get('useCache')) {
$this->buildCache();
}
}
}
protected function initCacheFilePath()
{
$this->cacheFilePath = 'data/cache/application/acl/' . $this->user->id . '.php';
}
protected function getUser()
{
return $this->user;
}
protected function getMetadata()
{
return $this->metadata;
}
protected function getFieldManager()
{
return $this->fieldManager;
}
protected function getConfig()
{
return $this->config;
}
public function getMap()
{
return $this->data;
@@ -87,8 +149,8 @@ class Table
public function getScopeData($scope)
{
if (array_key_exists($scope, $this->data['table'])) {
$data = $this->data['table'][$scope];
if (isset($this->data->table->$scope)) {
$data = $this->data->table->$scope;
if (is_string($data)) {
$data = $this->getScopeData($data);
return $data;
@@ -104,17 +166,17 @@ class Table
return null;
}
if (array_key_exists($permission, $this->data)) {
return $this->data[$permission];
if (isset($this->data->$permission)) {
return $this->data->$permission;
}
return null;
}
public function getLevel($scope, $action)
{
if (array_key_exists($scope, $this->data['table'])) {
if (array_key_exists($action, $this->data['table'][$scope])) {
return $this->data['table'][$scope][$action];
if (isset($this->data->table->$scope)) {
if (isset($this->data->table->$scope->$action)) {
return $this->data->table->$scope->$action;
}
}
return false;
@@ -122,48 +184,343 @@ class Table
private function load()
{
$aclTables = [];
$assignmentPermissionList = [];
$userPermissionList = [];
$userRoles = $this->user->get('roles');
foreach ($userRoles as $role) {
$aclTables[] = $role->get('data');
$assignmentPermissionList[] = $role->get('assignmentPermission');
$userPermissionList[] = $role->get('userPermission');
$valuePermissionLists = (object)[];
foreach ($this->valuePermissionList as $permission) {
$valuePermissionLists->$permission = [];
}
$teams = $this->user->get('teams');
foreach ($teams as $team) {
$teamRoles = $team->get('roles');
foreach ($teamRoles as $role) {
$aclTables[] = $role->get('data');
$assignmentPermissionList[] = $role->get('assignmentPermission');
$userPermissionList[] = $role->get('userPermission');
$aclTableList = [];
$fieldTableList = [];
if (!$this->user->isAdmin()) {
$roleList = $this->getRoleList();
foreach ($roleList as $role) {
$aclTableList[] = $role->get('data');
$fieldTableList[] = $role->get('fieldData');
foreach ($this->valuePermissionList as $permission) {
$valuePermissionLists->{$permission}[] = $role->get($permission);
}
}
$aclTable = $this->mergeTableList($aclTableList);
$fieldTable = $this->mergeFieldTableList($fieldTableList);
$this->applyDefault($aclTable, $fieldTable);
$this->applyDisabled($aclTable, $fieldTable);
$this->applyMandatory($aclTable, $fieldTable);
} else {
$aclTable = (object) [];
foreach ($this->getScopeList() as $scope) {
if ($this->metadata->get("scopes.{$scope}.{$this->type}") === 'boolean') {
$aclTable->$scope = true;
} else {
if ($this->metadata->get("scopes.{$scope}.entity")) {
$aclTable->$scope = (object) [];
foreach ($this->actionList as $action) {
$aclTable->$scope->$action = 'all';
if (in_array($action, $this->booleanActionList)) {
$aclTable->$scope->$action = 'yes';
}
}
}
}
}
$fieldTable = (object) [];
}
foreach ($aclTable as $scope => $data) {
if (is_string($data)) {
if (isset($aclTable->$data)) {
$aclTable->$scope = $aclTable->$data;
}
}
}
$this->data['table'] = $this->merge($aclTables);
$this->data->table = $aclTable;
$this->data->fieldTable = $fieldTable;
$this->data['assignmentPermission'] = $this->mergeValues($assignmentPermissionList, $this->metadata->get('app.acl.valueDefaults.assignmentPermission', 'all'));
$this->data['userPermission'] = $this->mergeValues($userPermissionList, $this->metadata->get('app.acl.valueDefaults.userPermission', 'no'));
$this->fillFieldTableQuickAccess();
if (!$this->user->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)) {
$this->data->$permission = $this->metadata->get('app.'.$this->type.'.mandatory.' . $permission);
}
}
} else {
foreach ($this->valuePermissionList as $permission) {
if (isset($this->valuePrtmissionHighestLevels[$permission])) {
$this->data->$permission = $this->valuePrtmissionHighestLevels[$permission];
continue;
}
$this->data->$permission = 'all';
}
}
}
private function initSolid()
protected function getRoleList()
{
if (!$this->metadata) {
$roleList = [];
$userRoleList = $this->getUser()->get('roles');
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$teamList = $this->getUser()->get('teams');
foreach ($teamList as $team) {
$teamRoleList = $team->get('roles');
foreach ($teamRoleList as $role) {
$roleList[] = $role;
}
}
return $roleList;
}
public function getScopeForbiddenAttributeList($scope, $action = 'read', $thresholdLevel = 'no')
{
$key = $scope . '_'. $action . '_' . $thresholdLevel;
if (isset($this->forbiddenAttributesCache[$key])) {
return $this->forbiddenAttributesCache[$key];
}
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
if (!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) || !isset($fieldTableQuickAccess->$scope->attributes->$action)) {
$this->forbiddenAttributesCache[$key] = [];
return [];
}
$levelList = [];
foreach ($this->fieldLevelList as $level) {
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
$levelList[] = $level;
}
}
$attributeList = [];
foreach ($levelList as $level) {
if (!isset($fieldTableQuickAccess->$scope->attributes->$action->$level)) continue;
foreach ($fieldTableQuickAccess->$scope->attributes->$action->$level as $attribute) {
if (in_array($attribute, $attributeList)) continue;
$attributeList[] = $attribute;
}
}
$this->forbiddenAttributesCache[$key] = $attributeList;
return $attributeList;
}
public function getScopeForbiddenFieldList($scope, $action = 'read', $thresholdLevel = 'no')
{
$key = $scope . '_'. $action . '_' . $thresholdLevel;
if (isset($this->forbiddenFieldsCache[$key])) {
return $this->forbiddenFieldsCache[$key];
}
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
if (!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) || !isset($fieldTableQuickAccess->$scope->fields->$action)) {
$this->forbiddenFieldsCache[$key] = [];
return [];
}
$levelList = [];
foreach ($this->fieldLevelList as $level) {
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
$levelList[] = $level;
}
}
$fieldList = [];
foreach ($levelList as $level) {
if (!isset($fieldTableQuickAccess->$scope->fields->$action->$level)) continue;
foreach ($fieldTableQuickAccess->$scope->fields->$action->$level as $field) {
if (in_array($field, $fieldList)) continue;
$fieldList[] = $field;
}
}
$this->forbiddenFieldsCache[$key] = $fieldList;
return $fieldList;
}
protected function fillFieldTableQuickAccess()
{
$fieldTable = $this->data->fieldTable;
$fieldTableQuickAccess = (object) [];
foreach (get_object_vars($fieldTable) as $scope => $scopeData) {
$fieldTableQuickAccess->$scope = (object) [
'attributes' => (object) [],
'fields' => (object) []
];
foreach ($this->fieldActionList as $action) {
$fieldTableQuickAccess->$scope->attributes->$action = (object) [];
$fieldTableQuickAccess->$scope->fields->$action = (object) [];
foreach ($this->fieldLevelList as $level) {
$fieldTableQuickAccess->$scope->attributes->$action->$level = [];
$fieldTableQuickAccess->$scope->fields->$action->$level = [];
}
}
foreach (get_object_vars($scopeData) as $field => $fieldData) {
$attributeList = $this->getFieldManager()->getAttributeList($scope, $field);
foreach ($this->fieldActionList as $action) {
if (!isset($fieldData->$action)) continue;
foreach ($this->fieldLevelList as $level) {
if ($fieldData->$action === $level) {
$fieldTableQuickAccess->$scope->fields->$action->{$level}[] = $field;
foreach ($attributeList as $attribute) {
$fieldTableQuickAccess->$scope->attributes->$action->{$level}[] = $attribute;
}
}
}
}
}
}
$this->data->fieldTableQuickAccess = $fieldTableQuickAccess;
}
protected function applyDefault(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
return;
}
$data = $this->metadata->get('app.acl.solid', array());
$data = $this->metadata->get('app.'.$this->type.'.default.scopeLevel', array());
foreach ($data as $entityType => $item) {
$this->data['table'][$entityType] = $item;
foreach ($data as $scope => $item) {
if (isset($table->$scope)) continue;
$value = $item;
if (is_array($item)) {
$value = (object) $item;
}
$table->$scope = $value;
}
$defaultFieldData = $this->metadata->get('app.'.$this->type.'.default.fieldLevel', array());
foreach ($this->getScopeList() as $scope) {
if (isset($table->$scope) && $table->$scope === false) continue;
if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue;
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.default.scopeFieldLevel.' . $scope, array());
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
if (!in_array($field, $fieldList)) continue;
if (!isset($fieldTable->$scope)) {
$fieldTable->$scope = (object) [];
}
if (isset($fieldTable->$scope->$field)) continue;
$fieldTable->$scope->$field = (object) [];
foreach ($this->fieldActionList as $action) {
$level = 'no';
if ($f === true) {
$level = 'yes';
} else {
if (is_array($f) && isset($f[$action])) {
$level = $f[$action];
}
}
$fieldTable->$scope->$field->$action = $level;
}
}
}
foreach ($this->getScopeWithAclList() as $scope) {
if (!isset($table->$scope)) {
$aclType = $this->metadata->get('scopes.' . $scope . '.' . $this->type);
if ($aclType === true) {
$aclType = $this->defaultAclType;
}
if (!empty($aclType)) {
$defaultValue = $this->metadata->get('app.'.$this->type.'.scopeLevelTypesDefaults.' . $aclType, $this->metadata->get('app.'.$this->type.'.scopeLevelTypesDefaults.record'));
if (is_array($defaultValue)) {
$defaultValue = (object) $defaultValue;
}
$table->$scope = $defaultValue;
}
}
}
}
private function mergeValues(array $list, $defaultValue)
protected function applyMandatory(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
return;
}
$data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', array());
foreach ($data as $scope => $item) {
$value = $item;
if (is_array($item)) {
$value = (object) $item;
}
$table->$scope = $value;
}
$mandatoryFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', array());
foreach ($this->getScopeList() as $scope) {
if (isset($table->$scope) && $table->$scope === false) continue;
if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue;
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
$mandatoryScopeFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.scopeFieldLevel.' . $scope, array());
foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $f) {
if (!in_array($field, $fieldList)) continue;
if (!isset($fieldTable->$scope)) {
$fieldTable->$scope = (object) [];
}
$fieldTable->$scope->$field = (object) [];
foreach ($this->fieldActionList as $action) {
$level = 'no';
if ($f === true) {
$level = 'yes';
} else {
if (is_array($f) && isset($f[$action])) {
$level = $f[$action];
}
}
$fieldTable->$scope->$field->$action = $level;
}
}
}
}
protected function applyDisabled(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
return;
}
foreach ($this->getScopeList() as $scope) {
if ($this->getMetadata()->get('scopes.' . $scope . '.disabled')) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
private function mergeValueList(array $list, $defaultValue)
{
$result = null;
foreach ($list as $level) {
@@ -183,51 +540,74 @@ class Table
return $result;
}
private function getScopeList()
protected function getScopeWithAclList()
{
$scopeList = [];
$scopes = $this->metadata->get('scopes');
foreach ($scopes as $scope => $d) {
if (!empty($d['acl'])) {
$scopeList[] = $scope;
}
if (empty($d['acl'])) continue;
$scopeList[] = $scope;
}
return $scopeList;
}
private function merge($tables)
protected function getScopeList()
{
$data = array();
$scopeList = $this->getScopeList();
$scopeList = [];
$scopes = $this->metadata->get('scopes');
foreach ($scopes as $scope => $d) {
$scopeList[] = $scope;
}
return $scopeList;
}
foreach ($tables as $table) {
private function mergeTableList(array $tableList)
{
$data = (object) [];
$scopeList = $this->getScopeWithAclList();
foreach ($tableList as $table) {
foreach ($scopeList as $scope) {
if (!isset($table->$scope)) {
continue;
}
if (!isset($table->$scope)) continue;
$row = $table->$scope;
if ($row == false) {
if (!isset($data[$scope])) {
$data[$scope] = false;
if (!isset($data->$scope)) {
$data->$scope = false;
}
} else if ($row === true) {
$data[$scope] = true;
$data->$scope = true;
} else {
if (!isset($data[$scope])) {
$data[$scope] = array();
if (!isset($data->$scope)) {
$data->$scope = (object) [];
}
if ($data[$scope] == false) {
$data[$scope] = array();
if ($data->$scope === false) {
$data->$scope = (object) [];
}
if (is_array($row) || $row instanceof \stdClass) {
foreach ($row as $action => $level) {
if (!isset($data[$scope][$action])) {
$data[$scope][$action] = $level;
if (!is_object($row)) continue;
foreach ($this->actionList as $i => $action) {
if (isset($row->$action)) {
$level = $row->$action;
if (!isset($data->$scope->$action)) {
$data->$scope->$action = $level;
} else {
if (array_search($data[$scope][$action], $this->levelList) > array_search($level, $this->levelList)) {
$data[$scope][$action] = $level;
if (array_search($data->$scope->$action, $this->levelList) > array_search($level, $this->levelList)) {
$data->$scope->$action = $level;
}
}
} else {
if ($i > 0) {
// TODO remove it
$previousAction = $this->actionList[$i - 1];
if (in_array($action, $this->booleanActionList)) {
$data->$scope->$action = 'yes';
} else {
if (isset($data->$scope->$previousAction)) {
$data->$scope->$action = $data->$scope->$previousAction;
}
}
}
}
@@ -236,24 +616,75 @@ class Table
}
}
foreach ($scopeList as $scope) {
if (!array_key_exists($scope, $data)) {
$aclType = $this->metadata->get('scopes.' . $scope . '.acl');
if ($aclType === true) {
$aclType = 'recordAllTeamOwnNo';
return $data;
}
private function mergeFieldTableList(array $tableList)
{
$data = (object) [];
$scopeList = $this->getScopeWithAclList();
foreach ($tableList as $table) {
foreach ($scopeList as $scope) {
if (!isset($table->$scope)) continue;
if (!isset($data->$scope)) {
$data->$scope = (object) [];
}
if (!empty($aclType)) {
$data[$scope] = $this->metadata->get('app.acl.defaults.' . $aclType, true);
}
}
if (!is_object($table->$scope)) continue;
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
foreach (get_object_vars($table->$scope) as $field => $row) {
if (!is_object($row)) continue;
if (!in_array($field, $fieldList)) continue;
if (!isset($data->$scope->$field)) {
$data->$scope->$field = (object) [];
}
foreach ($this->fieldActionList as $i => $action) {
if (!isset($row->$action)) continue;
$level = $row->$action;
if (!isset($data->$scope->$field->$action)) {
$data->$scope->$field->$action = $level;
} else {
if (array_search($data->$scope->$field->$action, $this->fieldLevelList) > array_search($level, $this->fieldLevelList)) {
$data->$scope->$field->$action = $level;
}
}
}
}
}
}
return $data;
}
private function buildCache()
{
$contents = '<' . '?'. 'php return ' . var_export($this->data, true) . ';';
$this->fileManager->putContents($this->cacheFile, $contents);
$contents = '<' . '?'. 'php return ' . $this->varExport($this->data) . ';';
$this->fileManager->putContents($this->cacheFilePath, $contents);
}
private function varExport($variable)
{
if ($variable instanceof \StdClass) {
$result = '(object) ' . $this->varExport(get_object_vars($variable), true);
} else if (is_array($variable)) {
$array = array();
foreach ($variable as $key => $value) {
$array[] = var_export($key, true).' => ' . $this->varExport($value, true);
}
$result = '['.implode(', ', $array).']';
} else {
$result = var_export($variable, true);
}
return $result;
}
}

View File

@@ -45,6 +45,8 @@ class AclManager
private $tableHashMap = array();
protected $tableClassName = '\\Espo\\Core\\Acl\\Table';
public function __construct(Container $container)
{
$this->container = $container;
@@ -56,6 +58,11 @@ class AclManager
return $this->container;
}
protected function getMetadata()
{
return $this->metadata;
}
public function getImplementation($scope)
{
if (empty($this->implementationHashMap[$scope])) {
@@ -75,10 +82,10 @@ class AclManager
}
if (class_exists($className)) {
$acl = new $className();
$acl = new $className($scope);
$dependencies = $acl->getDependencyList();
foreach ($dependencies as $name) {
$acl->inject($name, $this->container->get($name));
$acl->inject($name, $this->getContainer()->get($name));
}
$this->implementationHashMap[$scope] = $acl;
} else {
@@ -97,8 +104,9 @@ class AclManager
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManager');
$this->tableHashMap[$key] = new \Espo\Core\Acl\Table($user, $config, $fileManager, $metadata);
$this->tableHashMap[$key] = new $this->tableClassName($user, $config, $fileManager, $metadata, $fieldManager);
}
return $this->tableHashMap[$key];
@@ -119,9 +127,6 @@ class AclManager
public function get(User $user, $permission)
{
if ($user->isAdmin()) {
return true;
}
return $this->getTable($user)->get($permission);
}
@@ -143,46 +148,48 @@ class AclManager
return $this->getImplementation($scope)->checkReadOnlyOwn($user, $data);
}
public function check(User $user, $subject, $action = null, $isOwner = null, $inTeam = null)
public function check(User $user, $subject, $action = null)
{
if ($user->isAdmin()) {
return true;
}
if (is_string($subject)) {
return $this->checkScope($user, $subject, $action, $isOwner, $inTeam);
return $this->checkScope($user, $subject, $action);
} else {
$entity = $subject;
if ($entity instanceof Entity) {
$entityType = $entity->getEntityType();
$impl = $this->getImplementation($entityType);
$methodName = 'checkEntity' . ucfirst($action);
if (method_exists($impl, $methodName)) {
$data = $this->getTable($user)->getScopeData($entityType);
return $impl->$methodName($user, $entity, $data);
}
return $this->checkEntity($user, $entity, $action);
}
}
}
public function checkEntity(User $user, Entity $entity, $action)
public function checkEntity(User $user, Entity $entity, $action = 'read')
{
if ($user->isAdmin()) {
return true;
$scope = $entity->getEntityType();
$data = $this->getTable($user)->getScopeData($scope);
$impl = $this->getImplementation($scope);
$methodName = 'checkEntity' . ucfirst($action);
if (method_exists($impl, $methodName)) {
return $impl->$methodName($user, $entity, $data);
}
$data = $this->getTable($user)->getScopeData($entity->getEntityType());
return $this->getImplementation($entity->getEntityType())->checkEntity($user, $entity, $data, $action);
return $impl->checkEntity($user, $entity, $data, $action);
}
public function checkScope(User $user, $scope, $action = null, $isOwner = null, $inTeam = null, $entity = null)
public function checkIsOwner(User $user, Entity $entity)
{
return $this->getImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity, $action)
{
return $this->getImplementation($entity->getEntityType())->checkInTeam($user, $entity);
}
public function checkScope(User $user, $scope, $action = null)
{
if ($user->isAdmin()) {
return true;
}
$data = $this->getTable($user)->getScopeData($scope);
return $this->getImplementation($scope)->checkScope($user, $data, $scope, $action, $isOwner, $inTeam, $entity);
return $this->getImplementation($scope)->checkScope($user, $data, $action);
}
public function checkUser(User $user, $permission, User $entity)
@@ -213,5 +220,17 @@ class AclManager
}
return true;
}
public function getScopeForbiddenAttributeList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($user->isAdmin()) return [];
return $this->getTable($user)->getScopeForbiddenAttributeList($scope, $action, $thresholdLevel);
}
public function getScopeForbiddenFieldList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($user->isAdmin()) return [];
return $this->getTable($user)->getScopeForbiddenFieldList($scope, $action, $thresholdLevel);
}
}

View File

@@ -0,0 +1,212 @@
<?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\AclPortal;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Base extends \Espo\Core\Acl\Base
{
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = array())
{
if ($user->isAdmin()) {
return true;
}
if (is_null($data)) {
return false;
}
if ($data === false) {
return false;
}
if ($data === true) {
return true;
}
if (is_string($data)) {
return true;
}
$isOwner = null;
if (isset($entityAccessData['isOwner'])) {
$isOwner = $entityAccessData['isOwner'];
}
$inAccount = null;
if (isset($entityAccessData['inAccount'])) {
$inAccount = $entityAccessData['inAccount'];
}
$isOwnContact = null;
if (isset($entityAccessData['isOwnContact'])) {
$isOwnContact = $entityAccessData['isOwnContact'];
}
if (is_null($action)) {
return true;
}
if (!isset($data->$action)) {
return true;
}
$value = $data->$action;
if ($value === 'all' || $value === 'yes' || $value === true) {
return true;
}
if (!$value || $value === 'no') {
return false;
}
if (is_null($isOwner)) {
if ($entity) {
$isOwner = $this->checkIsOwner($user, $entity);
} else {
return true;
}
}
if ($isOwner) {
if ($value === 'own' || $value === 'account' || $value === 'contact') {
return true;
}
}
if ($value === 'account') {
if (is_null($inAccount) && $entity) {
$inAccount = $this->checkInAccount($user, $entity);
}
if ($inAccount) {
return true;
}
}
if ($value === 'contact') {
if (is_null($isOwnContact) && $entity) {
$isOwnContact = $this->checkIsOwnContact($user, $entity);
}
if ($isOwnContact) {
return true;
}
}
return false;
}
public function checkReadOnlyAccount(User $user, $data)
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === 'account';
}
public function checkReadOnlyContact(User $user, $data)
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === 'contact';
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($entity->hasAttribute('createdById')) {
if ($entity->has('createdById')) {
if ($user->id === $entity->get('createdById')) {
return true;
}
}
}
return false;
}
public function checkInAccount(User $user, Entity $entity)
{
$accountIdList = $user->getLinkMultipleIdList('accounts');
if (count($accountIdList)) {
if ($entity->hasAttribute('accountId')) {
if (in_array($entity->get('accountId'), $accountIdList)) {
return true;
}
}
if ($entity->hasRelation('accounts')) {
$repository = $this->getEntityManager()->getRepository($entity->getEntityType());
foreach ($accountIdList as $accountId) {
if ($repository->isRelated($entity, 'accounts', $accountId)) {
return true;
}
}
}
if ($entity->hasAttribute('parentId') && $entity->hasRelation('parent')) {
if ($entity->get('parentType') === 'Account') {
if (in_array($entity->get('parentId'), $accountIdList)) {
return true;
}
}
}
}
return false;
}
public function checkIsOwnContact(User $user, Entity $entity)
{
$contactId = $user->get('contactId');
if ($contactId) {
if ($entity->hasAttribute('contactId')) {
if ($entity->get('contactId') === $contactId) {
return true;
}
}
if ($entity->hasRelation('contacts')) {
$repository = $this->getEntityManager()->getRepository($entity->getEntityType());
if ($repository->isRelated($entity, 'contacts', $contactId)) {
return true;
}
}
if ($entity->hasAttribute('parentId') && $entity->hasRelation('parent')) {
if ($entity->get('parentType') === 'Contact') {
if ($entity->get('parentId') === $contactId) {
return true;
}
}
}
}
return false;
}
}

View File

@@ -0,0 +1,125 @@
<?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\AclPortal;
use \Espo\Core\Exceptions\Error;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use \Espo\Entities\Portal;
use \Espo\Core\Utils\Config;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\FieldManager;
use \Espo\Core\Utils\File\Manager as FileManager;
class Table extends \Espo\Core\Acl\Table
{
protected $type = 'aclPortal';
protected $portal;
protected $defaultAclType = 'recordAllOwnNo';
protected $levelList = ['yes', 'all', 'account', 'contact', 'own', 'no'];
protected $valuePermissionList = [];
public function __construct(User $user, Portal $portal, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManager $fieldManager = null)
{
if (empty($portal)) {
throw new Error("No portal was passed to AclPortal\\Table constructor.");
}
$this->portal = $portal;
parent::__construct($user, $config, $fileManager, $metadata, $fieldManager);
}
protected function getPortal()
{
return $this->portal;
}
protected function initCacheFilePath()
{
$this->cacheFilePath = 'data/cache/application/acl-portal/'.$this->getPortal()->id.'/' . $this->getUser()->id . '.php';
}
protected function getRoleList()
{
$roleList = [];
$userRoleList = $this->getUser()->get('portalRoles');
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$portalRoleList = $this->getPortal()->get('portalRoles');
foreach ($portalRoleList as $role) {
$roleList[] = $role;
}
return $roleList;
}
protected function getScopeWithAclList()
{
$scopeList = [];
$scopes = $this->getMetadata()->get('scopes');
foreach ($scopes as $scope => $d) {
if (empty($d['acl'])) continue;
if (empty($d['aclPortal'])) continue;
$scopeList[] = $scope;
}
return $scopeList;
}
protected function applyDefault(&$table, &$fieldTable)
{
parent::applyDefault($table, $fieldTable);
foreach ($this->getScopeList() as $scope) {
if (!isset($table->$scope)) {
$table->$scope = false;
}
}
}
protected function applyDisabled(&$table, &$fieldTable)
{
foreach ($this->getScopeList() as $scope) {
$d = $this->getMetadata()->get('scopes.' . $scope);
if ($d['disabled'] || $d['portalDisabled']) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
}

View File

@@ -33,26 +33,28 @@ class Application
{
private $metadata;
private $container;
protected $container;
private $slim;
private $auth;
/**
* Constructor
*/
public function __construct()
{
$this->container = new Container();
date_default_timezone_set('UTC');
$GLOBALS['log'] = $this->container->get('log');
$this->initContainer();
$GLOBALS['log'] = $this->getContainer()->get('log');
$this->initAutoloads();
}
protected function initContainer()
{
$this->container = new Container();
}
public function getSlim()
{
if (empty($this->slim)) {
@@ -69,12 +71,9 @@ class Application
return $this->metadata;
}
protected function getAuth()
protected function createAuth()
{
if (empty($this->auth)) {
$this->auth = new \Espo\Core\Utils\Auth($this->container);
}
return $this->auth;
return new \Espo\Core\Utils\Auth($this->container);
}
public function getContainer()
@@ -91,19 +90,10 @@ class Application
public function runClient()
{
$config = $this->getContainer()->get('config');
$themeManager = $this->getContainer()->get('themeManager');
$html = file_get_contents('main.html');
$html = str_replace('{{cacheTimestamp}}', $config->get('cacheTimestamp', 0), $html);
$html = str_replace('{{useCache}}', $config->get('useCache') ? 'true' : 'false' , $html);
$html = str_replace('{{stylesheet}}', $themeManager->getStylesheet(), $html);
$html = str_replace('{{runScript}}', 'app.start();' , $html);
echo $html;
exit;
$this->getContainer()->get('clientManager')->display();
}
public function runEntryPoint($entryPoint)
public function runEntryPoint($entryPoint, $data = array(), $final = false)
{
if (empty($entryPoint)) {
throw new \Error();
@@ -112,18 +102,27 @@ class Application
$slim = $this->getSlim();
$container = $this->getContainer();
$slim->get('/', function() {});
$slim->post('/', function() {});
$slim->any('.*', function() {});
$entryPointManager = new \Espo\Core\EntryPointManager($container);
try {
$auth = $this->getAuth();
$apiAuth = new \Espo\Core\Utils\Api\Auth($auth, $entryPointManager->checkAuthRequired($entryPoint), true);
$authRequired = $entryPointManager->checkAuthRequired($entryPoint);
$authNotStrict = $entryPointManager->checkNotStrictAuth($entryPoint);
if ($authRequired && !$authNotStrict) {
if (!$final && $portalId = $this->detectedPortalId()) {
$app = new \Espo\Core\Portal\Application($portalId);
$app->setBasePath($this->getBasePath());
$app->runEntryPoint($entryPoint, $data, true);
exit;
}
}
$auth = new \Espo\Core\Utils\Auth($this->container, $authNotStrict);
$apiAuth = new \Espo\Core\Utils\Api\Auth($auth, $authRequired, true);
$slim->add($apiAuth);
$slim->hook('slim.before.dispatch', function () use ($entryPoint, $entryPointManager, $container) {
$entryPointManager->run($entryPoint);
$slim->hook('slim.before.dispatch', function () use ($entryPoint, $entryPointManager, $container, $data) {
$entryPointManager->run($entryPoint, $data);
});
$slim->run();
@@ -134,7 +133,7 @@ class Application
public function runCron()
{
$auth = $this->getAuth();
$auth = $this->createAuth();
$auth->useNoAuth(true);
$cronManager = new \Espo\Core\CronManager($this->container);
@@ -164,20 +163,25 @@ class Application
return false;
}
protected function createApiAuth($auth)
{
return new \Espo\Core\Utils\Api\Auth($auth);
}
protected function routeHooks()
{
$container = $this->getContainer();
$slim = $this->getSlim();
try {
$auth = $this->getAuth();
$auth = $this->createAuth();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode());
}
$apiAuth = new \Espo\Core\Utils\Api\Auth($auth);
$this->getSlim()->add($apiAuth);
$apiAuth = $this->createApiAuth($auth);
$this->getSlim()->add($apiAuth);
$this->getSlim()->hook('slim.before.dispatch', function () use ($slim, $container) {
$route = $slim->router()->getCurrentRoute();
@@ -237,13 +241,19 @@ class Application
});
}
protected function initRoutes()
protected function getRouteList()
{
$routes = new \Espo\Core\Utils\Route($this->getContainer()->get('config'), $this->getMetadata(), $this->getContainer()->get('fileManager'));
$crudList = array_keys( $this->getContainer()->get('config')->get('crud') );
foreach ($routes->getAll() as $route) {
return $routes->getAll();
}
protected function initRoutes()
{
$crudList = array_keys($this->getContainer()->get('config')->get('crud'));
foreach ($this->getRouteList() as $route) {
$method = strtolower($route['method']);
if (!in_array($method, $crudList)) {
$GLOBALS['log']->error('Route: Method ['.$method.'] does not exist. Please check your route ['.$route['route'].']');
@@ -288,5 +298,30 @@ class Application
$classLoader->register(true);
}
public function setBasePath($basePath)
{
$this->getContainer()->get('clientManager')->setBasePath($basePath);
}
public function getBasePath()
{
return $this->getContainer()->get('clientManager')->getBasePath();
}
public function detectedPortalId()
{
if (!empty($_GET['portalId'])) {
return $_GET['portalId'];
}
if (!empty($_COOKIE['auth-token'])) {
$token = $this->getContainer()->get('entityManager')->getRepository('AuthToken')->where(array('token'=>$_COOKIE['auth-token']))->findOne();
if ($token && $token->get('portalId')) {
return $token->get('portalId');
}
}
return null;
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core;
class Container
{
@@ -39,7 +40,6 @@ class Container
*/
public function __construct()
{
}
public function get($name)
@@ -47,7 +47,15 @@ class Container
if (empty($this->data[$name])) {
$this->load($name);
}
return $this->data[$name];
if (isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
protected function set($name, $obj)
{
$this->data[$name] = $obj;
}
private function load($name)
@@ -115,53 +123,54 @@ class Container
return $this;
}
private function loadSlim()
protected function loadSlim()
{
return new \Espo\Core\Utils\Api\Slim();
}
private function loadFileManager()
protected function loadFileManager()
{
return new \Espo\Core\Utils\File\Manager(
$this->get('config')
);
}
private function loadPreferences()
protected function loadPreferences()
{
return $this->get('entityManager')->getEntity('Preferences', $this->get('user')->id);
}
private function loadConfig()
protected function loadConfig()
{
return new \Espo\Core\Utils\Config(
new \Espo\Core\Utils\File\Manager()
);
}
private function loadHookManager()
protected function loadHookManager()
{
return new \Espo\Core\HookManager(
$this
);
}
private function loadOutput()
protected function loadOutput()
{
return new \Espo\Core\Utils\Api\Output(
$this->get('slim')
);
}
private function loadMailSender()
protected function loadMailSender()
{
$className = $this->getServiceClassName('mailSernder', '\\Espo\\Core\\Mail\\Sender');
return new $className(
$this->get('config')
$this->get('config'),
$this->get('entityManager')
);
}
private function loadDateTime()
protected function loadDateTime()
{
return new \Espo\Core\Utils\DateTime(
$this->get('config')->get('dateFormat'),
@@ -170,7 +179,7 @@ class Container
);
}
private function loadNumber()
protected function loadNumber()
{
return new \Espo\Core\Utils\Number(
$this->get('config')->get('decimalMark'),
@@ -178,14 +187,14 @@ class Container
);
}
private function loadServiceFactory()
protected function loadServiceFactory()
{
return new \Espo\Core\ServiceFactory(
$this
);
}
private function loadSelectManagerFactory()
protected function loadSelectManagerFactory()
{
return new \Espo\Core\SelectManagerFactory(
$this->get('entityManager'),
@@ -195,7 +204,7 @@ class Container
);
}
private function loadMetadata()
protected function loadMetadata()
{
return new \Espo\Core\Utils\Metadata(
$this->get('config'),
@@ -203,15 +212,16 @@ class Container
);
}
private function loadLayout()
protected function loadLayout()
{
return new \Espo\Core\Utils\Layout(
$this->get('fileManager'),
$this->get('metadata')
$this->get('metadata'),
$this->get('user')
);
}
private function loadAclManager()
protected function loadAclManager()
{
$className = $this->getServiceClassName('acl', '\\Espo\\Core\\AclManager');
return new $className(
@@ -219,7 +229,7 @@ class Container
);
}
private function loadAcl()
protected function loadAcl()
{
$className = $this->getServiceClassName('acl', '\\Espo\\Core\\Acl');
return new $className(
@@ -228,7 +238,7 @@ class Container
);
}
private function loadSchema()
protected function loadSchema()
{
return new \Espo\Core\Utils\Database\Schema\Schema(
$this->get('config'),
@@ -239,7 +249,7 @@ class Container
);
}
private function loadClassParser()
protected function loadClassParser()
{
return new \Espo\Core\Utils\File\ClassParser(
$this->get('fileManager'),
@@ -248,7 +258,7 @@ class Container
);
}
private function loadLanguage()
protected function loadLanguage()
{
return new \Espo\Core\Utils\Language(
$this->get('fileManager'),
@@ -258,28 +268,28 @@ class Container
);
}
private function loadCrypt()
protected function loadCrypt()
{
return new \Espo\Core\Utils\Crypt(
$this->get('config')
);
}
private function loadScheduledJob()
protected function loadScheduledJob()
{
return new \Espo\Core\Utils\ScheduledJob(
$this
);
}
private function loadDataManager()
protected function loadDataManager()
{
return new \Espo\Core\DataManager(
$this
);
}
private function loadFieldManager()
protected function loadFieldManager()
{
return new \Espo\Core\Utils\FieldManager(
$this->get('metadata'),
@@ -287,7 +297,7 @@ class Container
);
}
private function loadThemeManager()
protected function loadThemeManager()
{
return new \Espo\Core\Utils\ThemeManager(
$this->get('config'),
@@ -295,9 +305,17 @@ class Container
);
}
public function setUser($user)
protected function loadClientManager()
{
$this->data['user'] = $user;
return new \Espo\Core\Utils\ClientManager(
$this->get('config'),
$this->get('themeManager')
);
}
public function setUser(\Espo\Entities\User $user)
{
$this->set('user', $user);
}
}

View File

@@ -119,7 +119,7 @@ class ControllerManager
$controller->$afterMethodName($params, $data, $request);
}
if (is_array($result) || is_bool($result)) {
if (is_array($result) || is_bool($result) || $result instanceof \StdClass) {
return \Espo\Core\Utils\Json::encode($result);
}

View File

@@ -87,7 +87,7 @@ class Record extends Base
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'edit')) {
if (!$this->getAcl()->check($this->name, 'create')) {
throw new Forbidden();
}
@@ -321,18 +321,18 @@ class Record extends Base
$where = json_decode(json_encode($data['where']), true);
return $this->getRecordService()->linkEntityMass($id, $link, $where);
} else {
$foreignIds = array();
$foreignIdList = array();
if (isset($data['id'])) {
$foreignIds[] = $data['id'];
$foreignIdList[] = $data['id'];
}
if (isset($data['ids']) && is_array($data['ids'])) {
foreach ($data['ids'] as $foreignId) {
$foreignIds[] = $foreignId;
$foreignIdList[] = $foreignId;
}
}
$result = false;
foreach ($foreignIds as $foreignId) {
foreach ($foreignIdList as $foreignId) {
if ($this->getRecordService()->linkEntity($id, $link, $foreignId)) {
$result = true;
}
@@ -386,7 +386,7 @@ class Record extends Base
if (!$request->isPut()) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'read')) {
if (!$this->getAcl()->check($this->name, 'stream')) {
throw new Forbidden();
}
$id = $params['id'];

View File

@@ -37,7 +37,6 @@ use \Espo\Core\Utils\Util;
class RecordTree extends Record
{
public static $defaultAction = 'list';
protected $defaultRecordServiceName = 'RecordTree';
@@ -51,9 +50,11 @@ class RecordTree extends Record
$where = $request->get('where');
$parentId = $request->get('parentId');
$maxDepth = $request->get('maxDepth');
$onlyNotEmpty = $request->get('onlyNotEmpty');
$collection = $this->getRecordService()->getTree($parentId, array(
'where' => $where
'where' => $where,
'onlyNotEmpty' => $onlyNotEmpty
), 0, $maxDepth);
return array(
'list' => $collection->toArray(),

View File

@@ -81,7 +81,16 @@ class EntryPointManager
return $className::$authRequired;
}
public function run($name)
public function checkNotStrictAuth($name)
{
$className = $this->getClassName($name);
if (!$className) {
throw new NotFound();
}
return $className::$notStrictAuth;
}
public function run($name, $data = array())
{
$className = $this->getClassName($name);
if (!$className) {
@@ -89,7 +98,7 @@ class EntryPointManager
}
$entryPoint = new $className($this->container);
$entryPoint->run();
$entryPoint->run($data);
}
protected function getClassName($name)

View File

@@ -39,6 +39,8 @@ abstract class Base
public static $authRequired = true;
public static $notStrictAuth = false;
protected function getContainer()
{
return $this->container;
@@ -94,12 +96,15 @@ abstract class Base
return $this->getContainer()->get('language');
}
protected function getClientManager()
{
return $this->getContainer()->get('clientManager');
}
public function __construct(Container $container)
{
$this->container = $container;
}
abstract public function run();
}

View File

@@ -71,7 +71,7 @@ class Importer
return $this->filtersMatcher;
}
public function importMessage($message, $userId = null, $teamsIdList = [], $userIdList = [], $filterList = [])
public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [])
{
try {
$email = $this->getEntityManager()->getEntity('Email');
@@ -84,9 +84,10 @@ class Importer
$email->set('isHtml', false);
$email->set('name', $subject);
$email->set('status', 'Archived');
$email->set('attachmentsIds', array());
if ($userId) {
$email->set('assignedUserId', $userId);
$email->set('attachmentsIds', []);
if ($assignedUserId) {
$email->set('assignedUserId', $assignedUserId);
$email->set('assignedUsersIds', [$assignedUserId]);
}
$email->set('teamsIds', $teamsIdList);
@@ -115,7 +116,6 @@ class Importer
return false;
}
if (isset($message->messageId) && !empty($message->messageId)) {
$email->set('messageId', $message->messageId);
if (isset($message->deliveredTo)) {
@@ -127,21 +127,15 @@ class Importer
}
if ($duplicate = $this->findDuplicate($email)) {
$duplicate->loadLinkMultipleField('users');
$usersIds = $duplicate->get('usersIds');
if ($userId) {
if (!in_array($userId, $usersIds)) {
$usersIds[] = $userId;
}
if ($assignedUserId) {
$duplicate->addLinkMultipleId('users', $assignedUserId);
$duplicate->addLinkMultipleId('assignedUsers', $assignedUserId);
}
if (!empty($userIdList)) {
foreach ($userIdList as $additionalUserId) {
if (!in_array($additionalUserId, $usersIds)) {
$usersIds[] = $additionalUserId;
}
foreach ($userIdList as $uId) {
$duplicate->addLinkMultipleId('users', $uId);
}
}
$duplicate->set('usersIds', $usersIds);
$this->getEntityManager()->saveEntity($duplicate);
@@ -150,7 +144,7 @@ class Importer
$this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId);
}
}
return false;
return $duplicate;
}
if (isset($message->date)) {
@@ -446,7 +440,7 @@ class Importer
$this->getEntityManager()->saveEntity($attachment);
$path = 'data/upload/' . $attachment->id;
$path = $this->getEntityManager()->getRepository('Attachment')->getFilePath($attachment);
$this->getFileManager()->putContents($path, $content);
if ($disposition == 'attachment') {

View File

@@ -45,18 +45,31 @@ class Sender
{
protected $config;
protected $entityManager;
protected $transport;
protected $isGlobal = false;
protected $params = array();
public function __construct($config)
public function __construct($config, $entityManager)
{
$this->config = $config;
$this->entityManager = $entityManager;
$this->useGlobal();
}
protected function getConfig()
{
return $this->config;
}
protected function getEntityManager()
{
return $this->entityManager;
}
public function resetParams()
{
$this->params = array();
@@ -235,7 +248,7 @@ class Sender
if (!empty($attachmentCollection)) {
foreach ($attachmentCollection as $a) {
$fileName = 'data/upload/' . $a->id;
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
$attachment = new MimePart(file_get_contents($fileName));
$attachment->disposition = Mime::DISPOSITION_ATTACHMENT;
$attachment->encoding = Mime::ENCODING_BASE64;
@@ -249,7 +262,7 @@ class Sender
if (!empty($attachmentInlineCollection)) {
foreach ($attachmentInlineCollection as $a) {
$fileName = 'data/upload/' . $a->id;
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
$attachment = new MimePart(file_get_contents($fileName));
$attachment->disposition = Mime::DISPOSITION_INLINE;
$attachment->encoding = Mime::ENCODING_BASE64;

View File

@@ -34,43 +34,148 @@ class Entity extends \Espo\ORM\Entity
public function loadLinkMultipleField($field, $columns = null)
{
if ($this->hasRelation($field) && $this->hasField($field . 'Ids')) {
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Ids')) return;
$defs = array();
if (!empty($columns)) {
$defs['additionalColumns'] = $columns;
}
$defs = array();
if (!empty($columns)) {
$defs['additionalColumns'] = $columns;
}
$collection = $this->get($field, $defs);
$ids = array();
$names = new \stdClass();
$types = new \stdClass();
if (!empty($columns)) {
$columnsData = new \stdClass();
}
$collection = $this->get($field, $defs);
$ids = array();
$names = new \stdClass();
$types = new \stdClass();
if (!empty($columns)) {
$columnsData = new \stdClass();
}
if ($collection) {
foreach ($collection as $e) {
$id = $e->id;
$ids[] = $id;
$names->$id = $e->get('name');
$types->$id = $e->get('type');
if (!empty($columns)) {
$columnsData->$id = new \stdClass();
foreach ($columns as $column => $f) {
$columnsData->$id->$column = $e->get($f);
}
if ($collection) {
foreach ($collection as $e) {
$id = $e->id;
$ids[] = $id;
$names->$id = $e->get('name');
$types->$id = $e->get('type');
if (!empty($columns)) {
$columnsData->$id = new \stdClass();
foreach ($columns as $column => $f) {
$columnsData->$id->$column = $e->get($f);
}
}
}
}
$this->set($field . 'Ids', $ids);
$this->set($field . 'Names', $names);
$this->set($field . 'Types', $types);
if (!empty($columns)) {
$this->set($field . 'Columns', $columnsData);
$this->set($field . 'Ids', $ids);
$this->set($field . 'Names', $names);
$this->set($field . 'Types', $types);
if (!empty($columns)) {
$this->set($field . 'Columns', $columnsData);
}
}
public function loadLinkField($field)
{
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Id')) return;
if ($this->getRelationType($field) !== 'hasOne' && $this->getRelationType($field) !== 'belongsTo') return;
$entity = $this->get($field);
$entityId = null;
$entityName = null;
if ($entity) {
$entityId = $entity->id;
$entityName = $entity->get('name');
}
$this->set($field . 'Id', $entityId);
$this->set($field . 'Name', $entityName);
}
public function getLinkMultipleColumn($field, $column, $id)
{
$columnsField = $field . 'Columns';
if (!$this->has($columnsField)) {
return;
}
$columns = $this->get($columnsField);
if ($columns instanceof \StdClass) {
if (isset($columns->$id)) {
if (isset($columns->$id->$column)) {
return $columns->$id->$column;
}
}
}
}
public function setLinkMultipleIdList($field, array $idList)
{
$idsField = $field . 'Ids';
$this->set($idsField, $idList);
}
public function addLinkMultipleId($field, $id)
{
$idsField = $field . 'Ids';
if (!$this->hasField($idsField)) return;
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field);
} else {
$this->set($idsField, []);
}
}
if (!$this->has($idsField)) {
return;
}
$idList = $this->get($idsField);
if (!in_array($id, $idList)) {
$idList[] = $id;
$this->set($idsField, $idList);
}
}
public function getLinkMultipleIdList($field)
{
$idsField = $field . 'Ids';
if (!$this->hasAttribute($idsField)) return null;
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field);
}
}
$valueList = $this->get($idsField);
if (empty($valueList)) {
return [];
}
return $valueList;
}
public function hasLinkMultipleId($field, $id)
{
$idsField = $field . 'Ids';
if (!$this->hasAttribute($idsField)) return null;
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field);
}
}
if (!$this->has($idsField)) {
return;
}
$idList = $this->get($idsField);
if (in_array($id, $idList)) {
return true;
}
return false;
}
}

View File

@@ -47,6 +47,11 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
private $restoreData = null;
protected function addDependency($name)
{
$this->dependencies[] = $name;
}
public function inject($name, $object)
{
$this->injections[$name] = $object;
@@ -153,10 +158,10 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeRemove', $entity, $options);
$nowString = date('Y-m-d H:i:s', time());
if ($entity->hasField('modifiedAt')) {
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasField('modifiedById')) {
if ($entity->hasAttribute('modifiedById')) {
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
}
}
@@ -194,6 +199,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$this->handleEmailAddressSave($entity);
$this->handlePhoneNumberSave($entity);
$this->handleSpecifiedRelations($entity);
$this->handleFileFields($entity);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterSave', $entity, $options);
}
@@ -208,13 +214,13 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$entity->set('id', Util::generateId());
}
if ($entity->hasField('createdAt')) {
if ($entity->hasAttribute('createdAt')) {
$entity->set('createdAt', $nowString);
}
if ($entity->hasField('modifiedAt')) {
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasField('createdById')) {
if ($entity->hasAttribute('createdById')) {
$entity->set('createdById', $this->entityManager->getUser()->id);
}
@@ -227,10 +233,10 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$entity->clear('modifiedById');
} else {
if (empty($options['silent'])) {
if ($entity->hasField('modifiedAt')) {
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasField('modifiedById')) {
if ($entity->hasAttribute('modifiedById')) {
$entity->set('modifiedById', $this->entityManager->getUser()->id);
}
}
@@ -251,30 +257,52 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
return $result;
}
protected function handleFileFields(Entity $entity)
{
foreach ($entity->getRelations() as $name => $defs) {
if (!isset($defs['type']) || !isset($defs['entity'])) continue;
if (!($defs['type'] === $entity::BELONGS_TO && $defs['entity'] === 'Attachment')) continue;
$attribute = $name . 'Id';
if (!$entity->hasAttribute($attribute)) continue;
if (!$entity->get($attribute)) continue;
if (!$entity->isAttributeChanged($attribute)) continue;
$attachment = $this->getEntityManager()->getEntity('Attachment', $entity->get($attribute));
if (!$attachment) continue;
$attachment->set(array(
'relatedId' => $entity->id,
'relatedType' => $entity->getEntityType()
));
$this->getEntityManager()->saveEntity($attachment);
}
}
protected function handleEmailAddressSave(Entity $entity)
{
if ($entity->hasRelation('emailAddresses') && $entity->hasField('emailAddress')) {
if ($entity->hasRelation('emailAddresses') && $entity->hasAttribute('emailAddress')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('EmailAddress')->storeEntityEmailAddress($entity);
}
}
protected function handlePhoneNumberSave(Entity $entity)
{
if ($entity->hasRelation('phoneNumbers') && $entity->hasField('phoneNumber')) {
if ($entity->hasRelation('phoneNumbers') && $entity->hasAttribute('phoneNumber')) {
$emailAddressRepository = $this->getEntityManager()->getRepository('PhoneNumber')->storeEntityPhoneNumber($entity);
}
}
protected function handleSpecifiedRelations(Entity $entity)
{
$relationTypes = array($entity::HAS_MANY, $entity::MANY_MANY, $entity::HAS_CHILDREN);
$relationTypeList = [$entity::HAS_MANY, $entity::MANY_MANY, $entity::HAS_CHILDREN];
foreach ($entity->getRelations() as $name => $defs) {
if (in_array($defs['type'], $relationTypes)) {
if (in_array($defs['type'], $relationTypeList)) {
$fieldName = $name . 'Ids';
$columnsFieldsName = $name . 'Columns';
if ($entity->has($fieldName) || $entity->has($columnsFieldsName)) {
if ($entity->has($fieldName) || $entity->has($columnsFieldsName)) {
if ($this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.noSave")) {
continue;
}
@@ -359,6 +387,42 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
}
}
}
} else if ($defs['type'] === $entity::HAS_ONE) {
if (empty($defs['entity']) || empty($defs['foreignKey'])) continue;
if ($this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.noSave")) {
continue;
}
$foreignEntityType = $defs['entity'];
$foreignKey = $defs['foreignKey'];
$idFieldName = $name . 'Id';
$nameFieldName = $name . 'Name';
if (!$entity->has($idFieldName)) continue;
$where = array();
$where[$foreignKey] = $entity->id;
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
if ($previousForeignEntity) {
$entity->setFetched($idFieldName, $previousForeignEntity->id);
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
$previousForeignEntity->set($foreignKey, null);
$this->getEntityManager()->saveEntity($previousForeignEntity);
}
} else {
$entity->setFetched($idFieldName, null);
}
if ($entity->get($idFieldName)) {
$newForeignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $entity->get($idFieldName));
if ($newForeignEntity) {
$newForeignEntity->set($foreignKey, $entity->id);
$this->getEntityManager()->saveEntity($newForeignEntity);
} else {
$entity->set($idFieldName, null);
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
<?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\Portal;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
class Acl extends \Espo\Core\Acl
{
public function checkReadOnlyAccount($scope)
{
return $this->getAclManager()->checkReadOnlyAccount($this->getUser(), $scope);
}
public function checkReadOnlyContact($scope)
{
return $this->getAclManager()->checkReadOnlyContact($this->getUser(), $scope);
}
public function checkInAccount(Entity $entity)
{
return $this->getAclManager()->checkInAccount($this->getUser(), $entity);
}
public function checkIsOwnContact(Entity $entity)
{
return $this->getAclManager()->checkIsOwnContact($this->getUser(), $entity);
}
}

View File

@@ -0,0 +1,119 @@
<?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\Portal;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use \Espo\Core\Utils\Util;
class AclManager extends \Espo\Core\AclManager
{
protected $tableClassName = '\\Espo\\Core\\AclPortal\\Table';
public function getImplementation($scope)
{
if (empty($this->implementationHashMap[$scope])) {
$normalizedName = Util::normilizeClassName($scope);
$className = '\\Espo\\Custom\\AclPortal\\' . $normalizedName;
if (!class_exists($className)) {
$moduleName = $this->getMetadata()->getScopeModuleName($scope);
if ($moduleName) {
$className = '\\Espo\\Modules\\' . $moduleName . '\\AclPortal\\' . $normalizedName;
} else {
$className = '\\Espo\\AclPortal\\' . $normalizedName;
}
if (!class_exists($className)) {
$className = '\\Espo\\Core\\AclPortal\\Base';
}
}
if (class_exists($className)) {
$acl = new $className($scope);
$dependencies = $acl->getDependencyList();
foreach ($dependencies as $name) {
$acl->inject($name, $this->getContainer()->get($name));
}
$this->implementationHashMap[$scope] = $acl;
} else {
throw new Error();
}
}
return $this->implementationHashMap[$scope];
}
protected function getTable(User $user)
{
$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');
$this->tableHashMap[$key] = new $this->tableClassName($user, $portal, $config, $fileManager, $metadata, $fieldManager);
}
return $this->tableHashMap[$key];
}
public function checkReadOnlyAccount(User $user, $scope)
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return $this->getImplementation($scope)->checkReadOnlyAccount($user, $data);
}
public function checkReadOnlyContact(User $user, $scope)
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return $this->getImplementation($scope)->checkReadOnlyContact($user, $data);
}
public function checkInAccount(User $user, Entity $entity, $action)
{
return $this->getImplementation($entity->getEntityType())->checkInAccount($user, $entity);
}
public function checkIsOwnContact(User $user, Entity $entity, $action)
{
return $this->getImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
}
}

View File

@@ -0,0 +1,98 @@
<?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\Portal;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Forbidden;
class Application extends \Espo\Core\Application
{
public function __construct($portalId)
{
date_default_timezone_set('UTC');
$this->initContainer();
if (empty($portalId)) {
throw new Error("Portal id was not passed to ApplicationPortal.");
}
$GLOBALS['log'] = $this->getContainer()->get('log');
$portal = $this->getContainer()->get('entityManager')->getEntity('Portal', $portalId);
if (!$portal) {
throw new NotFound();
}
if (!$portal->get('isActive')) {
throw new Forbidden("Portal is not active.");
}
$this->portal = $portal;
$this->getContainer()->setPortal($portal);
$this->initAutoloads();
}
protected function getPortal()
{
return $this->portal;
}
protected function initContainer()
{
$this->container = new Container();
}
protected function getRouteList()
{
$routeList = parent::getRouteList();
foreach ($routeList as $i => $route) {
if (isset($route['route'])) {
if ($route['route']{0} !== '/') {
$route['route'] = '/' . $route['route'];
}
$route['route'] = '/:portalId' . $route['route'];
}
$routeList[$i] = $route;
}
return $routeList;
}
public function runClient()
{
$this->getContainer()->get('clientManager')->display(null, 'html/portal.html', array(
'portalId' => $this->getPortal()->id
));
}
}

View File

@@ -0,0 +1,126 @@
<?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\Portal;
class Container extends \Espo\Core\Container
{
protected function getServiceClassName($name, $default)
{
$metadata = $this->get('metadata');
$className = $metadata->get('app.serviceContainerPortal.classNames.' . $name, $default);
return $className;
}
protected function loadAclManager()
{
$className = $this->getServiceClassName('aclManager', '\\Espo\\Core\\Portal\\AclManager');
return new $className(
$this->get('container')
);
}
protected function loadAcl()
{
$className = $this->getServiceClassName('acl', '\\Espo\\Core\\Portal\\Acl');
return new $className(
$this->get('aclManager'),
$this->get('user')
);
}
protected function loadThemeManager()
{
return new \Espo\Core\Portal\Utils\ThemeManager(
$this->get('config'),
$this->get('metadata'),
$this->get('portal')
);
}
protected function loadLayout()
{
return new \Espo\Core\Portal\Utils\Layout(
$this->get('fileManager'),
$this->get('metadata'),
$this->get('user')
);
}
protected function loadLanguage()
{
$language = new \Espo\Core\Portal\Utils\Language(
$this->get('fileManager'),
$this->get('config'),
$this->get('metadata'),
$this->get('preferences')
);
$language->setPortal($this->get('portal'));
return $language;
}
public function setPortal(\Espo\Entities\Portal $portal)
{
$this->set('portal', $portal);
$data = array();
foreach ($this->get('portal')->getSettingsAttributeList() as $attribute) {
$data[$attribute] = $this->get('portal')->get($attribute);
}
if (empty($data['language'])) {
unset($data['language']);
}
if (empty($data['theme'])) {
unset($data['theme']);
}
if (empty($data['timeZone'])) {
unset($data['timeZone']);
}
if (empty($data['dateFormat'])) {
unset($data['dateFormat']);
}
if (empty($data['timeFormat'])) {
unset($data['timeFormat']);
}
if (isset($data['weekStart']) && $data['weekStart'] === -1) {
unset($data['weekStart']);
}
if (array_key_exists('weekStart', $data) && is_null($data['weekStart'])) {
unset($data['weekStart']);
}
if (empty($data['defaultCurrency'])) {
unset($data['defaultCurrency']);
}
foreach ($data as $attribute => $value) {
$this->get('config')->set($attribute, $value, true);
}
}
}

View File

@@ -0,0 +1,46 @@
<?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\Portal\Utils;
use \Espo\Entities\Portal;
class Language extends \Espo\Core\Utils\Language
{
public function setPortal($portal)
{
if ($portal->get('language') !== '' && $portal->get('language')) {
if (!$this->getPreferences()->get('language')) {
$this->setLanguage($portal->get('language'));
}
}
}
}

View File

@@ -0,0 +1,129 @@
<?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\Portal\Utils;
use \Espo\Core\Utils\Util;
use \Espo\Core\Utils\Json;
class Layout extends \Espo\Core\Utils\Layout
{
public function get($scope, $name)
{
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
if (isset($this->changedData[$scope][$name])) {
return Json::encode($this->changedData[$scope][$name]);
}
$fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), 'portal/' . $name . '.json');
if (!file_exists($fileFullPath)) {
$fileFullPath = Util::concatPath($this->getLayoutPath($scope), 'portal/' . $name . '.json');
}
if (!file_exists($fileFullPath)) {
$fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name . '.json');
}
if (!file_exists($fileFullPath)) {
$fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name . '.json');
}
if (!file_exists($fileFullPath)) {
$defaultPath = $this->params['defaultsPath'];
$fileFullPath = Util::concatPath(Util::concatPath($defaultPath, 'layouts'), $name . '.json' );
if (!file_exists($fileFullPath)) {
return false;
}
}
return $this->getFileManager()->getContents($fileFullPath);
}
public function set($data, $scope, $name)
{
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
if (empty($scope) || empty($name)) {
return false;
}
$this->changedData[$scope][$name] = $data;
}
public function resetToDefault($scope, $name)
{
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
$filePath = 'custom/Espo/Custom/Resources/layouts/' . $scope . '/' . $name . '.json';
if ($this->getFileManager()->isFile($filePath)) {
$this->getFileManager()->removeFile($filePath);
}
if (!empty($this->changedData[$scope]) && !empty($this->changedData[$scope][$name])) {
unset($this->changedData[$scope][$name]);
}
return $this->get($scope, $name);
}
/**
* Save changes
*
* @return bool
*/
public function save()
{
$result = true;
if (!empty($this->changedData)) {
foreach ($this->changedData as $scope => $rowData) {
foreach ($rowData as $layoutName => $layoutData) {
if (empty($scope) || empty($layoutName)) {
continue;
}
$layoutPath = $this->getLayoutPath($scope, true);
$data = Json::encode($layoutData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$result &= $this->getFileManager()->putContents(array($layoutPath, $layoutName.'.json'), $data);
}
}
}
if ($result == true) {
$this->clearChanges();
}
return (bool) $result;
}
}

View File

@@ -0,0 +1,56 @@
<?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\Portal\Utils;
use \Espo\Entities\Portal;
use \Espo\Core\Utils\Config;
use \Espo\Core\Utils\Metadata;
class ThemeManager extends \Espo\Core\Utils\ThemeManager
{
public function __construct(Config $config, Metadata $metadata, Portal $portal)
{
$this->config = $config;
$this->metadata = $metadata;
$this->portal = $portal;
}
public function getName()
{
$theme = $this->portal->get('theme');
if (!$theme) {
$theme = $this->defaultName;
}
return $theme;
}
}

View File

@@ -30,6 +30,7 @@
namespace Espo\Core\SelectManagers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Acl;
@@ -51,6 +52,8 @@ class Base
private $userTimeZone = null;
protected $additionalFilterTypeList = ['linkedWith', 'inCategory', 'isUserFromTeams'];
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
@@ -115,9 +118,9 @@ class Base
}
}
protected function getTextFilterFields()
protected function getTextFilterFieldList()
{
return $this->metadata->get("entityDefs.{$this->entityType}.collection.textFilterFields", array('name'));
return $this->metadata->get("entityDefs.{$this->entityType}.collection.textFilterFields", ['name']);
}
protected function getSeed()
@@ -157,127 +160,151 @@ class Base
}
}
$linkedWith = array();
$inCategory = array();
$ignoreList = ['linkedWith', 'inCategory', 'bool', 'primary'];
$ignoreTypeList = array_merge(['bool', 'primary'], $this->additionalFilterTypeList);
$additionalFilters = array();
foreach ($where as $item) {
if (!in_array($item['type'], $ignoreList)) {
$type = $item['type'];
if (!in_array($type, $ignoreTypeList)) {
$part = $this->getWherePart($item);
if (!empty($part)) {
$whereClause[] = $part;
}
} else {
if ($item['type'] == 'linkedWith' && !empty($item['value'])) {
$linkedWith[$item['field']] = $item['value'];
} else if ($item['type'] == 'inCategory' && !empty($item['value'])) {
$inCategory[$item['field']] = $item['value'];
if (in_array($type, $this->additionalFilterTypeList)) {
if (!empty($item['value'])) {
$methodName = 'apply' . ucfirst($type);
if (method_exists($this, $methodName)) {;
$this->$methodName($item['field'], $item['value'], $result);
}
}
}
}
}
$result['whereClause'] = array_merge($result['whereClause'], $whereClause);
if (!empty($linkedWith)) {
$this->handleLinkedWith($linkedWith, $result);
}
if (!empty($inCategory)) {
$this->handleInCategory($inCategory, $result);
}
}
protected function handleLinkedWith($linkedWith, &$result)
protected function applyLinkedWith($link, $idsValue, &$result)
{
$joins = [];
$part = array();
foreach ($linkedWith as $link => $idsValue) {
if (is_array($idsValue) && count($idsValue) == 1) {
$idsValue = $idsValue[0];
}
$relDefs = $this->getSeed()->getRelations();
if (is_array($idsValue) && count($idsValue) == 1) {
$idsValue = $idsValue[0];
}
if (!empty($relDefs[$link])) {
$defs = $relDefs[$link];
if ($defs['type'] == 'manyMany') {
$joins[] = $link;
if (!empty($defs['midKeys'])) {
$key = $defs['midKeys'][1];
$part[$link . 'Middle.' . $key] = $idsValue;
}
} else if ($defs['type'] == 'belongsTo') {
if (!empty($defs['key'])) {
$key = $defs['key'];
$part[$key] = $idsValue;
}
}
$seed = $this->getSeed();
if (!$seed->hasRelation($link)) return;
$relDefs = $this->getSeed()->getRelations();
$relationType = $seed->getRelationType($link);
$defs = $relDefs[$link];
if ($relationType == 'manyMany') {
$this->addJoin($link, $result);
$midKeys = $seed->getRelationParam($link, 'midKeys');
if (!empty($midKeys)) {
$key = $midKeys[1];
$part[$link . 'Middle.' . $key] = $idsValue;
}
} else if ($relationType== 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
if (!empty($key)) {
$part[$key] = $idsValue;
}
} else {
return;
}
if (!empty($part)) {
$result['whereClause'][] = $part;
}
$result['joins'] = array_merge($result['joins'], $joins);
$result['joins'] = array_unique($result['joins']);
$result['distinct'] = true;
$this->setDistinct(true, $result);
}
protected function handleInCategory($inCategory, &$result)
protected function applyIsUserFromTeams($link, $idsValue, &$result)
{
$joins = [];
if (is_array($idsValue) && count($idsValue) == 1) {
$idsValue = $idsValue[0];
}
$part = array();
$query = $this->getEntityManager()->getQuery();
$seed = $this->getSeed();
$relDefs = $seed->getRelations();
if (!$seed->hasRelation($link)) return;
$relationType = $seed->getRelationType($link);
if ($relationType == 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
$aliasName = 'usersTeams' . ucfirst($link);
$result['customJoin'] .= "
JOIN team_user AS {$aliasName}Middle ON {$aliasName}Middle.user_id = ".$query->toDb($seed->getEntityType()).".".$query->toDb($key)." AND {$aliasName}Middle.deleted = 0
JOIN team AS {$aliasName} ON {$aliasName}.deleted = 0 AND {$aliasName}Middle.team_id = {$aliasName}.id
";
$result['whereClause'][] = array(
$aliasName . 'Middle.teamId' => $idsValue
);
} else {
return;
}
$this->setDistinct(true, $result);
}
public function applyInCategory($link, $value, &$result)
{
$relDefs = $this->getSeed()->getRelations();
$query = $this->getEntityManager()->getQuery();
$tableName = $query->toDb($this->getSeed()->getEntityType());
foreach ($inCategory as $link => $val) {
if (!empty($relDefs[$link])) {
$defs = $relDefs[$link];
$relDefs = $this->getSeed()->getRelations();
$foreignEntity = $defs['entity'];
if (empty($foreignEntity)) {
return;
}
if (!empty($relDefs[$link])) {
$defs = $relDefs[$link];
$pathName = lcfirst($query->sanitize($foreignEntity . 'Path'));
$foreignEntity = $defs['entity'];
if (empty($foreignEntity)) {
continue;
if ($defs['type'] == 'manyMany') {
if (!empty($defs['midKeys'])) {
$result['distinct'] = true;
$result['joins'][] = $link;
$key = $defs['midKeys'][1];
$middleName = $link . 'Middle';
$result['customJoin'] .= "
JOIN " . $query->toDb($pathName) . " AS `{$pathName}` ON {$pathName}.descendor_id = ".$query->sanitize($middleName) . "." . $query->toDb($key) . "
";
$result['whereClause'][$pathName . '.ascendorId'] = $value;
}
$pathName = lcfirst($query->sanitize($foreignEntity . 'Path'));
if ($defs['type'] == 'manyMany') {
if (!empty($defs['midKeys'])) {
$result['distinct'] = true;
$result['joins'][] = $link;
$key = $defs['midKeys'][1];
$middleName = $link . 'Middle';
$result['customJoin'] .= "
JOIN " . $query->toDb($pathName) . " AS `{$pathName}` ON {$pathName}.descendor_id = ".$query->sanitize($middleName) . "." . $query->toDb($key) . "
";
$part[$pathName . '.ascendorId'] = $val;
}
} else if ($defs['type'] == 'belongsTo') {
if (!empty($defs['key'])) {
$key = $defs['key'];
$result['customJoin'] .= "
JOIN " . $query->toDb($pathName) . " AS `{$pathName}` ON {$pathName}.descendor_id = {$tableName}." . $query->toDb($key) . "
";
$part[$pathName . '.ascendorId'] = $val;
}
} else if ($defs['type'] == 'belongsTo') {
if (!empty($defs['key'])) {
$key = $defs['key'];
$result['customJoin'] .= "
JOIN " . $query->toDb($pathName) . " AS `{$pathName}` ON {$pathName}.descendor_id = {$tableName}." . $query->toDb($key) . "
";
$result['whereClause'][$pathName . '.ascendorId'] = $value;
}
}
}
if (!empty($part)) {
$result['whereClause'][] = $part;
}
}
protected function q($params, &$result)
@@ -299,6 +326,14 @@ class Base
$this->q(array('q' => $textFilter), $result);
}
public function getEmptySelectParams()
{
$result = array();
$this->prepareResult($result);
return $result;
}
protected function prepareResult(&$result)
{
if (empty($result)) {
@@ -324,47 +359,233 @@ class Base
}
}
protected function checkIsPortal()
{
return !!$this->getUser()->get('portalId');
}
protected function access(&$result)
{
if ($this->acl->checkReadOnlyOwn($this->entityType)) {
$this->accessOnlyOwn($result);
if (!$this->checkIsPortal()) {
if ($this->getAcl()->checkReadOnlyOwn($this->getEntityType())) {
$this->accessOnlyOwn($result);
} else {
if (!$this->getUser()->isAdmin()) {
if ($this->getAcl()->checkReadOnlyTeam($this->getEntityType())) {
$this->accessOnlyTeam($result);
}
}
}
} else {
if (!$this->user->isAdmin() && $this->acl->checkReadOnlyTeam($this->entityType)) {
$this->accessOnlyTeam($result);
if ($this->getAcl()->checkReadOnlyOwn($this->getEntityType())) {
$this->accessPortalOnlyOwn($result);
} else {
if ($this->getAcl()->checkReadOnlyAccount($this->getEntityType())) {
$this->accessPortalOnlyAccount($result);
} else {
if ($this->getAcl()->checkReadOnlyContact($this->getEntityType())) {
$this->accessPortalOnlyContact($result);
}
}
}
}
}
protected function accessOnlyOwn(&$result)
{
if ($this->getSeed()->hasField('assignedUserId')) {
if ($this->hasAssignedUsersField()) {
$this->setDistinct(true, $result);
$this->addLeftJoin('assignedUsers', $result);
$result['whereClause'][] = array(
'assignedUsers.id' => $this->getUser()->id
);
return;
}
if ($this->hasAssignedUserField()) {
$result['whereClause'][] = array(
'assignedUserId' => $this->getUser()->id
);
return;
}
if ($this->getSeed()->hasField('createdById')) {
if ($this->hasCreatedByField()) {
$result['whereClause'][] = array(
'createdById' => $this->getUser()->id
);
return;
}
}
protected function accessOnlyTeam(&$result)
{
if (!$this->getSeed()->hasField('teamsIds')) {
if (!$this->hasTeamsField()) {
return;
}
$this->setDistinct(true, $result);
$this->addLeftJoin('teams', $result);
$result['whereClause'][] = array(
'OR' => array(
'teams.id' => $this->user->get('teamsIds'),
'assignedUserId' => $this->getUser()->id
)
$this->addLeftJoin(['teams', 'teamsAccess'], $result);
if ($this->hasAssignedUsersField()) {
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
$result['whereClause'][] = array(
'OR' => array(
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams'),
'assignedUsersAccess.id' => $this->getUser()->id
)
);
return;
}
$d = array(
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams')
);
if ($this->hasAssignedUserField()) {
$d['assignedUserId'] = $this->getUser()->id;
} else if ($this->hasCreatedByField()) {
$d['createdById'] = $this->getUser()->id;
}
$result['whereClause'][] = array(
'OR' => $d
);
}
protected function accessPortalOnlyOwn(&$result)
{
if ($this->getSeed()->hasAttribute('createdById')) {
$result['whereClause'][] = array(
'createdById' => $this->getUser()->id
);
} else {
$result['whereClause'][] = array(
'id' => null
);
}
}
protected function accessPortalOnlyContact(&$result)
{
$d = array();
$contactId = $this->getUser()->get('contactId');
if ($contactId) {
if ($this->getSeed()->hasAttribute('contactId')) {
$d['contactId'] = $contactId;
}
if ($this->getSeed()->hasRelation('contacts')) {
$this->addLeftJoin(['contacts', 'contactsAccess'], $result);
$this->setDistinct(true, $result);
$d['contactsAccess.id'] = $contactId;
}
}
if ($this->getSeed()->hasAttribute('createdById')) {
$d['createdById'] = $this->getUser()->id;
}
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
$contactId = $this->getUser()->get('contactId');
if ($contactId) {
$d[] = array(
'parentType' => 'Contact',
'parentId' => $contactId
);
}
}
if (!empty($d)) {
$result['whereClause'][] = array(
'OR' => $d
);
} else {
$result['whereClause'][] = array(
'id' => null
);
}
}
protected function accessPortalOnlyAccount(&$result)
{
$d = array();
$accountIdList = $this->getUser()->getLinkMultipleIdList('accounts');
$contactId = $this->getUser()->get('contactId');
if (count($accountIdList)) {
if ($this->getSeed()->hasAttribute('accountId')) {
$d['accountId'] = $accountIdList;
}
if ($this->getSeed()->hasRelation('accounts')) {
$this->addLeftJoin(['accounts', 'accountsAccess'], $result);
$this->setDistinct(true, $result);
$d['accountsAccess.id'] = $accountIdList;
}
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
$d[] = array(
'parentType' => 'Account',
'parentId' => $accountIdList
);
if ($contactId) {
$d[] = array(
'parentType' => 'Contact',
'parentId' => $contactId
);
}
}
}
if ($contactId) {
if ($this->getSeed()->hasAttribute('contactId')) {
$d['contactId'] = $contactId;
}
if ($this->getSeed()->hasRelation('contacts')) {
$this->addLeftJoin(['contacts', 'contactsAccess'], $result);
$this->setDistinct(true, $result);
$d['contactsAccess.id'] = $contactId;
}
}
if ($this->getSeed()->hasAttribute('createdById')) {
$d['createdById'] = $this->getUser()->id;
}
if (!empty($d)) {
$result['whereClause'][] = array(
'OR' => $d
);
} else {
$result['whereClause'][] = array(
'id' => null
);
}
}
protected function hasAssignedUsersField()
{
if ($this->getSeed()->hasRelation('assignedUsers') && $this->getSeed()->hasAttribute('assignedUsersIds')) {
return true;
}
}
protected function hasAssignedUserField()
{
if ($this->getSeed()->hasAttribute('assignedUserId')) {
return true;
}
}
protected function hasCreatedByField()
{
if ($this->getSeed()->hasAttribute('createdById')) {
return true;
}
}
protected function hasTeamsField()
{
if ($this->getSeed()->hasRelation('teams') && $this->getSeed()->hasAttribute('teamsIds')) {
return true;
}
}
public function getAclParams()
@@ -374,7 +595,7 @@ class Base
return $result;
}
public function getSelectParams(array $params, $withAcl = false)
public function getSelectParams(array $params, $withAcl = false, $checkWherePermission = false)
{
$result = array();
$this->prepareResult($result);
@@ -405,6 +626,9 @@ class Base
}
if (!empty($params['where']) && is_array($params['where'])) {
if ($checkWherePermission) {
$this->checkWhere($params['where']);
}
$this->where($params['where'], $result);
}
@@ -418,9 +642,31 @@ class Base
$this->access($result);
}
$this->applyAdditional($result);
return $result;
}
protected function checkWhere($where)
{
foreach ($where as $w) {
if (isset($w['field'])) {
if (isset($w['type']) && $w['type'] === 'linkedWith') {
if (in_array($w['field'], $this->getAcl()->getScopeForbiddenFieldList($this->getEntityType()))) {
throw new Forbidden();
}
} else {
if (in_array($w['field'], $this->getAcl()->getScopeForbiddenAttributeList($this->getEntityType()))) {
throw new Forbidden();
}
}
}
if (!empty($w['value']) && is_array($w['value'])) {
$this->checkWhere($w['value']);
}
}
}
protected function getUserTimeZone()
{
if (empty($this->userTimeZone)) {
@@ -586,6 +832,18 @@ class Base
{
$part = array();
if (!empty($item['field']) && !empty($item['type'])) {
$methodName = 'getWherePart' . ucfirst($item['field']) . ucfirst($item['type']);
if (method_exists($this, $methodName)) {
$value = null;
if (!empty($item['value'])) {
$value = $item['value'];
}
return $this->$methodName($value);
}
}
if (!empty($item['dateTime'])) {
return $this->convertDateTimeWhere($item);
}
@@ -648,6 +906,7 @@ class Base
$part[$item['field'] . '='] = null;
break;
case 'isNotNull':
case 'ever':
$part[$item['field'] . '!='] = null;
break;
case 'isTrue':
@@ -798,15 +1057,50 @@ class Base
$this->textFilter($textFilter, $result);
}
public function applyAdditional(&$result)
{
}
public function hasJoin($join, &$result)
{
return in_array($join, $result['joins']);
}
public function hasLeftJoin($leftJoin, &$result)
{
return in_array($leftJoin, $result['leftJoin']);
}
public function addJoin($join, &$result)
{
if (empty($result['joins'])) {
$result['joins'] = [];
}
if (!in_array($join, $result['joins'])) {
$result['joins'][] = $join;
$alias = $join;
if (is_array($join)) {
if (count($join) > 1) {
$alias = $join[1];
} else {
$alias = $join[0];
}
}
foreach ($result['joins'] as $j) {
$a = $j;
if (is_array($j)) {
if (count($j) > 1) {
$a = $j[1];
} else {
$a = $j[0];
}
}
if ($a === $alias) {
return;
}
}
$result['joins'][] = $join;
}
public function addLeftJoin($leftJoin, &$result)
@@ -815,9 +1109,34 @@ class Base
$result['leftJoins'] = [];
}
if (!in_array($leftJoin, $result['leftJoins'])) {
$result['leftJoins'][] = $leftJoin;
$alias = $leftJoin;
if (is_array($leftJoin)) {
if (count($leftJoin) > 1) {
$alias = $leftJoin[1];
} else {
$alias = $leftJoin[0];
}
}
foreach ($result['leftJoins'] as $j) {
$a = $j;
if (is_array($j)) {
if (count($j) > 1) {
$a = $j[1];
} else {
$a = $j[0];
}
}
if ($a === $alias) {
return;
}
}
$result['leftJoins'][] = $leftJoin;
}
public function setJoinCondition($join, $condition, &$result)
{
$result['joinConditions'][$join] = $condition;
}
public function setDistinct($distinct, &$result)
@@ -825,10 +1144,22 @@ class Base
$result['distinct'] = (bool) $distinct;
}
public function addAndWhere($whereClause, &$result)
{
$result['whereClause'][] = $whereClause;
}
public function addOrWhere($whereClause, &$result)
{
$result['whereClause'][] = array(
'OR' => $whereClause
);
}
protected function textFilter($textFilter, &$result)
{
$fieldDefs = $this->getSeed()->getFields();
$fieldList = $this->getTextFilterFields();
$fieldDefs = $this->getSeed()->getAttributes();
$fieldList = $this->getTextFilterFieldList();
$d = array();
foreach ($fieldList as $field) {
@@ -872,9 +1203,21 @@ class Base
protected function boolFilterOnlyMy(&$result)
{
$result['whereClause'][] = array(
'assignedUserId' => $this->getUser()->id
);
if (!$this->checkIsPortal()) {
if ($this->hasAssignedUserField()) {
$result['whereClause'][] = array(
'assignedUserId' => $this->getUser()->id
);
} else {
$result['whereClause'][] = array(
'createdById' => $this->getUser()->id
);
}
} else {
$result['whereClause'][] = array(
'createdById' => $this->getUser()->id
);
}
}
protected function filterFollowed(&$result)

View File

@@ -2,7 +2,8 @@
"fields": {
"name": {
"type": "varchar",
"required": true
"required": true,
"trim": true
},
"description": {
"type": "text"
@@ -17,15 +18,18 @@
},
"createdBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"assignedUser": {
"type": "link",
"required": true
"required": true,
"view": "views/fields/user"
},
"teams": {
"type": "linkMultiple"

View File

@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": true,
"notifications": true

View File

@@ -2,7 +2,8 @@
"fields": {
"name": {
"type": "varchar",
"required": true
"required": true,
"trim": true
},
"order": {
"type": "int",
@@ -22,11 +23,13 @@
},
"createdBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"teams": {
"type": "linkMultiple"

View File

@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": false,
"notifications": false

View File

@@ -59,15 +59,18 @@
},
"createdBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true
"readOnly": true,
"view": "views/fields/user"
},
"assignedUser": {
"type": "link",
"required": true
"required": false,
"view": "views/fields/user"
},
"teams": {
"type": "linkMultiple"

View File

@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": true,
"notifications": true

View File

@@ -39,33 +39,19 @@ class Slim extends \Slim\Slim
*/
public function run()
{
//set_error_handler(array('\Slim\Slim', 'handleErrors')); //Espo: no needs to use this handler
//Apply final outer middleware layers
if ($this->config('debug')) {
//Apply pretty exceptions only in debug to avoid accidental information leakage in production
//$this->add(new \Slim\Middleware\PrettyExceptions()); //Espo: no needs to use this handler
}
//Invoke middleware and application stack
$this->middleware[0]->call();
//Fetch status, header, and body
list($status, $headers, $body) = $this->response->finalize();
// Serialize cookies (with optional encryption)
\Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings);
//Send headers
if (headers_sent() === false) {
//Send status
if (strpos(PHP_SAPI, 'cgi') === 0) {
header(sprintf('Status: %s', \Slim\Http\Response::getMessageForCode($status)));
} else {
header(sprintf('HTTP/%s %s', $this->config('http.version'), \Slim\Http\Response::getMessageForCode($status)));
}
//Send headers
foreach ($headers as $name => $value) {
$hValues = explode("\n", $value);
foreach ($hValues as $hVal) {
@@ -74,12 +60,9 @@ class Slim extends \Slim\Slim
}
}
//Send body, but only if it isn't a HEAD request
if (!$this->request->isHead()) {
echo $body;
}
//restore_error_handler(); //Espo: no needs to use this handler
}
public function printError($error, $status)
@@ -87,5 +70,4 @@ class Slim extends \Slim\Slim
echo static::generateTemplateMarkup($status, '<p>'.$error.'</p><a href="' . $this->request->getRootUri() . '/">Visit the Home Page</a>');
}
}

View File

@@ -32,50 +32,112 @@ namespace Espo\Core\Utils;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Entities\Portal;
class Auth
{
protected $container;
protected $authentication;
protected $config;
protected $allowAnyAccess;
protected $entityManager;
const ACCESS_CRM_ONLY = 0;
public function __construct(\Espo\Core\Container $container)
const ACCESS_PORTAL_ONLY = 1;
const ACCESS_ANY = 3;
private $portal;
public function __construct(\Espo\Core\Container $container, $allowAnyAccess = false)
{
$this->container = $container;
$this->entityManager = $this->container->get('entityManager');
$this->config = $this->container->get('config');
$this->allowAnyAccess = $allowAnyAccess;
$authenticationMethod = $this->config->get('authenticationMethod', 'Espo');
$authenticationMethod = $this->getConfig()->get('authenticationMethod', 'Espo');
$authenticationClassName = "\\Espo\\Core\\Utils\\Authentication\\" . $authenticationMethod;
$this->authentication = new $authenticationClassName($this->config, $this->entityManager, $this);
$this->authentication = new $authenticationClassName($this->getConfig(), $this->getEntityManager(), $this);
$this->request = $this->container->get('slim')->request();
$this->request = $container->get('slim')->request();
}
protected function getContainer()
{
return $this->container;
}
protected function setPortal(Portal $portal)
{
$this->portal = $portal;
}
protected function isPortal()
{
if ($this->portal) {
return true;
}
return !!$this->getContainer()->get('portal');
}
protected function getPortal()
{
if ($this->portal) {
return $this->portal;
}
return $this->getContainer()->get('portal');
}
protected function getConfig()
{
return $this->getContainer()->get('config');
}
protected function getEntityManager()
{
return $this->getContainer()->get('entityManager');
}
public function useNoAuth($isAdmin = false)
{
$entityManager = $this->container->get('entityManager');
$entityManager = $this->getContainer()->get('entityManager');
$user = $entityManager->getRepository('User')->get('system');
if (!$user) {
throw new Error('System user is not found');
throw new Error("System user is not found");
}
$user->set('isAdmin', $isAdmin);
$entityManager->setUser($user);
$this->container->setUser($user);
$this->getContainer()->setUser($user);
}
public function login($username, $password)
{
$entityManager = $this->entityManager;
$authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $password))->findOne();
$authToken = $entityManager->getRepository('AuthToken')->where(array('token' => $password))->findOne();
if ($authToken) {
if (!$this->allowAnyAccess) {
if ($this->isPortal() && $authToken->get('portalId') !== $this->getPortal()->id) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal with a token not related to portal.");
return false;
}
if (!$this->isPortal() && $authToken->get('portalId')) {
$GLOBALS['log']->debug("AUTH: Trying to login to crm with a token related to portal.");
return false;
}
}
if ($this->allowAnyAccess) {
if ($authToken->get('portalId') && !$this->isPortal()) {
$portal = $this->getEntityManager()->getEntity('Portal', $authToken->get('portalId'));
if ($portal) {
$this->setPortal($portal);
}
}
}
}
$user = $this->authentication->login($username, $password, $authToken);
@@ -84,21 +146,43 @@ class Auth
$GLOBALS['log']->debug("AUTH: Trying to login as user '".$user->get('userName')."' which is not active.");
return false;
}
$entityManager->setUser($user);
$this->container->setUser($user);
if (!$user->isAdmin() && !$this->isPortal() && $user->get('isPortalUser')) {
$GLOBALS['log']->debug("AUTH: Trying to login to crm as a portal user '".$user->get('userName')."'.");
return false;
}
if (!$user->isAdmin() && $this->isPortal() && !$user->get('isPortalUser')) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is not portal user.");
return false;
}
if ($this->isPortal()) {
if (!$user->isAdmin() && !$this->getEntityManager()->getRepository('Portal')->isRelated($this->getPortal(), 'users', $user)) {
$GLOBALS['log']->debug("AUTH: Trying to login to portal as user '".$user->get('userName')."' which is portal user but does not belongs to portal.");
return false;
}
$user->set('portalId', $this->getPortal()->id);
}
$this->getEntityManager()->setUser($user);
$this->getContainer()->setUser($user);
if ($this->request->headers->get('HTTP_ESPO_AUTHORIZATION')) {
if (!$authToken) {
$authToken = $entityManager->getEntity('AuthToken');
$authToken = $this->getEntityManager()->getEntity('AuthToken');
$token = $this->createToken($user);
$authToken->set('token', $token);
$authToken->set('hash', $user->get('password'));
$authToken->set('ipAddress', $_SERVER['REMOTE_ADDR']);
$authToken->set('userId', $user->id);
if ($this->isPortal()) {
$authToken->set('portalId', $this->getPortal()->id);
}
}
$authToken->set('lastAccess', date('Y-m-d H:i:s'));
$entityManager->saveEntity($authToken);
$this->getEntityManager()->saveEntity($authToken);
$user->set('token', $authToken->get('token'));
}
@@ -113,11 +197,9 @@ class Auth
public function destroyAuthToken($token)
{
$entityManager = $this->container->get('entityManager');
$authToken = $entityManager->getRepository('AuthToken')->where(array('token' => $token))->findOne();
$authToken = $this->getEntityManager()->getRepository('AuthToken')->where(array('token' => $token))->findOne();
if ($authToken) {
$entityManager->removeEntity($authToken);
$this->getEntityManager()->removeEntity($authToken);
return true;
}
}

View File

@@ -73,6 +73,5 @@ abstract class Base
return $this->passwordHash;
}
}

View File

@@ -45,7 +45,7 @@ class Espo extends Base
'whereClause' => array(
'userName' => $username,
'password' => $hash
),
)
));
return $user;

View File

@@ -0,0 +1,110 @@
<?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;
class ClientManager
{
private $themeManager;
private $config;
protected $mainHtmlFilePath = 'html/main.html';
protected $htmlFilePathForDeveloperMode = 'frontend/html/main.html';
protected $runScript = "app.start();";
protected $basePath = '';
public function __construct(Config $config, ThemeManager $themeManager)
{
$this->config = $config;
$this->themeManager = $themeManager;
}
protected function getThemeManager()
{
return $this->themeManager;
}
protected function getConfig()
{
return $this->config;
}
public function setBasePath($basePath)
{
$this->basePath = $basePath;
}
public function getBasePath()
{
return $this->basePath;
}
protected function getCacheTimestamp()
{
if (!$this->getConfig()->get('useCache')) {
return (string) time();
}
return $this->getConfig()->get('cacheTimestamp', 0);
}
public function display($runScript = null, $htmlFilePath = null, $vars = array())
{
if (is_null($runScript)) {
$runScript = $this->runScript;
}
if (is_null($htmlFilePath)) {
$htmlFilePath = $this->mainHtmlFilePath;
}
if ($this->getConfig()->get('isDeveloperMode')) {
if (file_exists('frontend/' . $htmlFilePath)) {
$htmlFilePath = 'frontend/' . $htmlFilePath;
}
}
$html = file_get_contents($htmlFilePath);
foreach ($vars as $key => $value) {
$html = str_replace('{{'.$key.'}}', $value, $html);
}
$html = str_replace('{{cacheTimestamp}}', $this->getCacheTimestamp(), $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);
echo $html;
exit;
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core\Utils;
class Config
{
/**
@@ -52,6 +53,13 @@ class Config
*/
protected $adminItems = array();
protected $associativeArrayAttributeList = [
'currencyRates',
'database',
'logger',
'defaultPermissions',
];
/**
* Contains content of config
@@ -95,8 +103,12 @@ class Config
$lastBranch = $this->loadConfig();
foreach ($keys as $keyName) {
if (isset($lastBranch[$keyName]) && is_array($lastBranch)) {
$lastBranch = $lastBranch[$keyName];
if (isset($lastBranch[$keyName]) && (is_array($lastBranch) || is_object($lastBranch))) {
if (is_array($lastBranch)) {
$lastBranch = $lastBranch[$keyName];
} else {
$lastBranch = $lastBranch->$keyName;
}
} else {
return $default;
}
@@ -112,20 +124,20 @@ class Config
* @param string $value
* @return bool
*/
public function set($name, $value = '')
public function set($name, $value = null, $dontMarkDirty = false)
{
if (!is_array($name)) {
$name = array($name => $value);
}
foreach ($name as $key => $value) {
if (is_object($value)) {
if (in_array($key, $this->associativeArrayAttributeList) && is_object($value)) {
$value = (array) $value;
}
$this->data[$key] = $value;
$this->changedData[$key] = $value;
if (!$dontMarkDirty) {
$this->changedData[$key] = $value;
}
}
}
@@ -170,7 +182,7 @@ class Config
}
}
$result = $this->getFileManager()->putPhpContents($this->configPath, $data);
$result = $this->getFileManager()->putPhpContents($this->configPath, $data, true);
if ($result) {
$this->changedData = array();
@@ -242,7 +254,7 @@ class Config
$values = array();
foreach ($data as $key => $item) {
if (!in_array($key, $restrictItems)) {
$values[$key]= $item;
$values[$key] = $item;
}
}

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
use Doctrine\DBAL\Types\BooleanType;
class Bool extends BooleanType
class BoolType extends BooleanType
{
const BOOL = 'bool';

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
use Doctrine\DBAL\Types\IntegerType;
class Int extends IntegerType
class IntType extends IntegerType
{
const INTtype = 'int';

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
class JsonArray extends \Doctrine\DBAL\Types\JsonArrayType
class JsonArrayType extends \Doctrine\DBAL\Types\JsonArrayType
{
const JSON_ARRAY = 'jsonArray';

View File

@@ -29,7 +29,7 @@
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
class JsonObject extends \Doctrine\DBAL\Types\ObjectType
class JsonObjectType extends \Doctrine\DBAL\Types\ObjectType
{
const JSON_OBJECT = 'jsonObject';

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
use Doctrine\DBAL\Types\StringType;
class Password extends StringType
class PasswordType extends StringType
{
const PASSWORD = 'password';

View File

@@ -31,7 +31,7 @@ namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
use Doctrine\DBAL\Types\StringType;
class Varchar extends StringType
class VarcharType extends StringType
{
const VARCHAR = 'varchar';

View File

@@ -0,0 +1,65 @@
<?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\Database\Orm\Relations;
class EntityUser extends Base
{
protected function load($linkName, $entityType)
{
$linkParams = $this->getLinkParams();
$foreignEntityName = $this->getForeignEntityName();
return array(
$entityType => array(
'relations' => array(
$linkName => array(
'type' => 'manyMany',
'entity' => $foreignEntityName,
'relationName' => lcfirst($linkParams['relationName']),
'midKeys' => array(
'entityId',
'userId'
),
'conditions' => array(
'entityType' => $entityType
),
'additionalColumns' => array(
'entityType' => array(
'type' => 'varchar',
'len' => 100
)
)
)
)
)
);
}
}

View File

@@ -0,0 +1,64 @@
<?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\Database\Orm\Relations;
class HasOne extends Base
{
protected function load($linkName, $entityName)
{
$linkParams = $this->getLinkParams();
$foreignLinkName = $this->getForeignLinkName();
$foreignEntityName = $this->getForeignEntityName();
$relation = array(
$entityName => array (
'fields' => array(
$linkName.'Id' => array(
'type' => 'varchar',
'notStorable' => true
),
$linkName.'Name' => array(
'type' => 'varchar',
'notStorable' => true
)
),
'relations' => array(
$linkName => array(
'type' => 'hasOne',
'entity' => $foreignEntityName,
'foreignKey' => lcfirst($foreignLinkName.'Id')
)
)
)
);
return $relation;
}
}

View File

@@ -161,11 +161,11 @@ class Schema
$typeList = $this->getFileManager()->getFileList($path, false, '\.php$');
if ($typeList !== false) {
foreach($typeList as $name) {
$typeName = preg_replace('/\.php$/i', '', $name);
foreach ($typeList as $name) {
$typeName = preg_replace('/Type\.php$/i', '', $name);
$dbalTypeName = strtolower($typeName);
$filePath = Util::concatPath($path, $typeName);
$filePath = Util::concatPath($path, $typeName . 'Type');
$class = Util::getClassName($filePath);
if( ! Type::hasType($dbalTypeName) ) {

View File

@@ -45,6 +45,7 @@ class DateTime
'MM/DD/YYYY' => 'm/d/Y',
'YYYY-MM-DD' => 'Y-m-d',
'DD.MM.YYYY' => 'd.m.Y',
'DD/MM/YYYY' => 'd/m/Y',
);
protected $timeFormats = array(
@@ -83,6 +84,16 @@ class DateTime
}
public function convertSystemDateToGlobal($string)
{
return $this->convertSystemDate($string);
}
public function convertSystemDateTimeToGlobal($string)
{
return $this->convertSystemDateTime($string);
}
public function convertSystemDate($string)
{
$dateTime = \DateTime::createFromFormat('Y-m-d', $string);
if ($dateTime) {
@@ -91,20 +102,6 @@ class DateTime
return null;
}
public function convertSystemDateTimeToGlobal($string)
{
$dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $string);
if ($dateTime) {
return $dateTime->setTimezone($this->timezone)->format($this->getPhpDateTimeFormat());
}
return null;
}
public function convertSystemDate($string)
{
return $this->convertSystemDateToGlobal($string);
}
public function convertSystemDateTime($string, $timezone = null)
{
$dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $string);

View File

@@ -123,6 +123,10 @@ class EntityManager
if (!empty($params['stream'])) {
$stream = $params['stream'];
}
$disabled = false;
if (!empty($params['disabled'])) {
$disabled = $params['disabled'];
}
$labelSingular = $name;
if (!empty($params['labelSingular'])) {
$labelSingular = $params['labelSingular'];
@@ -139,6 +143,7 @@ class EntityManager
$scopesData = Json::decode($scopesDataContents, true);
$scopesData['stream'] = $stream;
$scopesData['disabled'] = $disabled;
$scopesData['type'] = $type;
$scopesData['module'] = 'Custom';
$scopesData['object'] = true;
@@ -179,10 +184,14 @@ class EntityManager
throw new Error('Entity ['.$name.'] does not exist.');
}
if (isset($data['stream'])) {
$scopeData = array(
'stream' => (true == $data['stream'])
);
if (isset($data['stream']) || isset($data['disabled'])) {
$scopeData = array();
if (isset($data['stream'])) {
$scopeData['stream'] = true == $data['stream'];
}
if (isset($data['disabled'])) {
$scopeData['disabled'] = true == $data['disabled'];
}
$this->getMetadata()->set('scopes', $name, $scopeData);
}

View File

@@ -285,4 +285,55 @@ class FieldManager
return false;
}
private function getAttributeListByType($scope, $name, $type)
{
$fieldType = $this->getMetadata()->get('entityDefs.' . $scope . '.fields.' . $name . '.type');
if (!$fieldType) return [];
$defs = $this->getMetadata()->get('fields.' . $fieldType);
if (!$defs) return [];
if (is_object($defs)) {
$defs = get_object_vars($defs);
}
$fieldList = [];
if (isset($defs[$type . 'Fields'])) {
$list = $defs[$type . 'Fields'];
$naming = 'suffix';
if (isset($defs['naming'])) {
$naming = $defs['naming'];
}
if ($naming == 'prefix') {
foreach ($list as $f) {
$fieldList[] = $f . ucfirst($name);
}
} else {
foreach ($list as $f) {
$fieldList[] = $name . ucfirst($f);
}
}
} else {
if ($type == 'actual') {
$fieldList[] = $name;
}
}
return $fieldList;
}
public function getActualAttributeList($scope, $name)
{
return $this->getAttributeListByType($scope, $name, 'actual');
}
public function getNotActualAttributeList($scope, $name)
{
return $this->getAttributeListByType($scope, $name, 'notActual');
}
public function getAttributeList($scope, $name)
{
return array_merge($this->getActualAttributeList($scope, $name), $this->getNotActualAttributeList($scope, $name));
}
}

View File

@@ -230,9 +230,10 @@ class Manager
*
* @return bool
*/
public function putPhpContents($path, $data)
public function putPhpContents($path, $data, $withObjects = false)
{
return $this->putContents($path, $this->getPHPFormat($data), LOCK_EX);
return $this->putContents($path, $this->wrapForDataExport($data, $withObjects), LOCK_EX);
}
/**
@@ -793,17 +794,45 @@ class Manager
*
* @return string | false
*/
public function getPHPFormat($content)
public function wrapForDataExport($content, $withObjects = false)
{
if (!isset($content)) {
return false;
}
return '<?php
if (!$withObjects) {
return "<?php\n".
"return " . var_export($content, true) . ";\n".
"?>";
} else {
return "<?php\n".
"return " . $this->varExport($content) . ";\n".
"?>";
}
}
return '.var_export($content, true).';
public function varExport($variable, $level = 0)
{
$tab = '';
$tabElement = ' ';
for ($i = 0; $i <= $level; $i++) {
$tab .= $tabElement;
}
$prevTab = substr($tab, 0, strlen($tab) - strlen($tabElement));
?>';
if ($variable instanceof \StdClass) {
$result = "(object) " . $this->varExport(get_object_vars($variable), $level);
} else if (is_array($variable)) {
$array = array();
foreach ($variable as $key => $value) {
$array[] = var_export($key, true) . " => " . $this->varExport($value, $level + 1);
}
$result = "[\n" . $tab . implode(",\n" . $tab, $array) . "\n" . $prevTab . "]";
} else {
$result = var_export($variable, true);
}
return $result;
}
/**

View File

@@ -35,9 +35,13 @@ use \Espo\Core\Utils\Util,
class Language
{
private $fileManager;
private $config;
private $metadata;
private $preferences;
private $unifier;
/**

View File

@@ -28,18 +28,16 @@
************************************************************************/
namespace Espo\Core\Utils;
class Layout
{
private $fileManager;
private $metadata;
private $changedData = array();
private $user;
/**
* @var string - uses for loading default values
*/
private $name = 'layout';
protected $changedData = array();
protected $params = array(
'defaultsPath' => 'application/Espo/Core/defaults',
@@ -49,17 +47,18 @@ class Layout
/**
* @var array - path to layout files
*/
private $paths = array(
protected $paths = array(
'corePath' => 'application/Espo/Resources/layouts',
'modulePath' => 'application/Espo/Modules/{*}/Resources/layouts',
'customPath' => 'custom/Espo/Custom/Resources/layouts',
);
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata)
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata, \Espo\Entities\User $user)
{
$this->fileManager = $fileManager;
$this->metadata = $metadata;
$this->user = $user;
}
protected function getFileManager()
@@ -72,6 +71,11 @@ class Layout
return $this->metadata;
}
protected function getUser()
{
return $this->user;
}
protected function sanitizeInput($name)
{
return preg_replace("([\.]{2,})", '', $name);
@@ -94,16 +98,14 @@ class Layout
return Json::encode($this->changedData[$scope][$name]);
}
$fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name.'.json');
$fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name . '.json');
if (!file_exists($fileFullPath)) {
$fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name.'.json');
$fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name . '.json');
}
if (!file_exists($fileFullPath)) {
//load defaults
$defaultPath = $this->params['defaultsPath'];
$fileFullPath = Util::concatPath( Util::concatPath($defaultPath, $this->name), $name.'.json' );
//END: load defaults
$fileFullPath = Util::concatPath(Util::concatPath($defaultPath, 'layouts'), $name . '.json' );
if (!file_exists($fileFullPath)) {
return false;
@@ -162,11 +164,9 @@ class Layout
if (!empty($this->changedData)) {
foreach ($this->changedData as $scope => $rowData) {
foreach ($rowData as $layoutName => $layoutData) {
if (empty($scope) || empty($layoutName)) {
continue;
}
$layoutPath = $this->getLayoutPath($scope, true);
$data = Json::encode($layoutData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
@@ -221,17 +221,17 @@ class Layout
/**
* Get Layout path, ex. application/Modules/Crm/Layouts/Account
*
* @param string $entityName
* @param string $entityType
* @param bool $isCustom - if need to check custom folder
*
* @return string
*/
protected function getLayoutPath($entityName, $isCustom = false)
protected function getLayoutPath($entityType, $isCustom = false)
{
$path = $this->paths['customPath'];
if (!$isCustom) {
$moduleName = $this->getMetadata()->getScopeModuleName($entityName);
$moduleName = $this->getMetadata()->getScopeModuleName($entityType);
$path = $this->paths['corePath'];
if ($moduleName !== false) {
@@ -239,7 +239,7 @@ class Layout
}
}
$path = Util::concatPath($path, $entityName);
$path = Util::concatPath($path, $entityType);
return $path;
}

View File

@@ -32,38 +32,37 @@ use Espo\Core\Exceptions\Error;
class Metadata
{
protected $meta = null;
protected $data = null;
protected $objData = null;
private $config;
private $unifier;
private $fileManager;
private $converter;
private $moduleConfig;
private $metadataHelper;
/**
* @var string - uses for loading default values
*/
private $name = 'metadata';
protected $pathToModules = 'application/Espo/Modules';
/**
* Path to modules
*
* @var string
*/
private $pathToModules = 'application/Espo/Modules';
protected $cacheFile = 'data/cache/application/metadata.php';
private $cacheFile = 'data/cache/application/metadata.php';
protected $objCacheFile = 'data/cache/application/metadata.php';
private $paths = array(
protected $paths = array(
'corePath' => 'application/Espo/Resources/metadata',
'modulePath' => 'application/Espo/Modules/{*}/Resources/metadata',
'customPath' => 'custom/Espo/Custom/Resources/metadata',
);
protected $ormMeta = null;
protected $ormData = null;
private $ormCacheFile = 'data/cache/application/ormMetadata.php';
protected $ormCacheFile = 'data/cache/application/ormMetadata.php';
private $moduleList = null;
@@ -155,15 +154,15 @@ class Metadata
}
if (file_exists($this->cacheFile) && !$reload) {
$this->meta = $this->getFileManager()->getPhpContents($this->cacheFile);
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
} else {
$this->clearVars();
$this->meta = $this->getUnifier()->unify($this->name, $this->paths, true);
$this->meta = $this->setLanguageFromConfig($this->meta);
$this->meta = $this->addAdditionalFields($this->meta);
$this->data = $this->getUnifier()->unify('metadata', $this->paths, true);
$this->data = $this->setLanguageFromConfig($this->data);
$this->data = $this->addAdditionalFields($this->data);
if ($this->getConfig()->get('useCache')) {
$isSaved = $this->getFileManager()->putPhpContents($this->cacheFile, $this->meta);
$isSaved = $this->getFileManager()->putPhpContents($this->cacheFile, $this->data);
if ($isSaved === false) {
$GLOBALS['log']->emergency('Metadata:init() - metadata has not been saved to a cache file');
}
@@ -178,11 +177,11 @@ class Metadata
*/
protected function getData()
{
if (empty($this->meta) || !is_array($this->meta)) {
if (empty($this->data) || !is_array($this->data)) {
$this->init();
}
return $this->meta;
return $this->data;
}
/**
@@ -213,9 +212,9 @@ class Metadata
}
if ($isJSON) {
return Json::encode($this->meta);
return Json::encode($this->data);
}
return $this->meta;
return $this->data;
}
/**
@@ -249,31 +248,31 @@ class Metadata
* todo: move to a separate file
* Add additional fields defined from metadata -> fields
*
* @param array $meta
* @param array $data
*/
protected function addAdditionalFields(array $meta)
protected function addAdditionalFields(array $data)
{
$metaCopy = $meta;
$definitionList = $meta['fields'];
$dataCopy = $data;
$definitionList = $data['fields'];
foreach ($metaCopy['entityDefs'] as $entityName => $entityParams) {
foreach ($dataCopy['entityDefs'] as $entityName => $entityParams) {
foreach ($entityParams['fields'] as $fieldName => $fieldParams) {
$additionalFields = $this->getMetadataHelper()->getAdditionalFieldList($fieldName, $fieldParams, $definitionList);
if (!empty($additionalFields)) {
//merge or add to the end of meta array
//merge or add to the end of data array
foreach ($additionalFields as $subFieldName => $subFieldParams) {
if (isset($entityParams['fields'][$subFieldName])) {
$meta['entityDefs'][$entityName]['fields'][$subFieldName] = Util::merge($subFieldParams, $entityParams['fields'][$subFieldName]);
$data['entityDefs'][$entityName]['fields'][$subFieldName] = Util::merge($subFieldParams, $entityParams['fields'][$subFieldName]);
} else {
$meta['entityDefs'][$entityName]['fields'][$subFieldName] = $subFieldParams;
$data['entityDefs'][$entityName]['fields'][$subFieldName] = $subFieldParams;
}
}
}
}
}
return $meta;
return $data;
}
/**
@@ -295,7 +294,7 @@ class Metadata
);
$this->changedData = Util::merge($this->changedData, $newData);
$this->meta = Util::merge($this->getData(), $newData);
$this->data = Util::merge($this->getData(), $newData);
$this->undelete($key1, $key2, $data);
}
@@ -309,7 +308,7 @@ class Metadata
*
* @return bool
*/
public function delete($key1, $key2, $unsets)
public function delete($key1, $key2, $unsets = null)
{
if (!is_array($unsets)) {
$unsets = (array) $unsets;
@@ -318,22 +317,22 @@ class Metadata
$normalizedData = array(
'__APPEND__',
);
$metaUnsetData = array();
$metadataUnsetData = array();
foreach ($unsets as $unsetItem) {
$normalizedData[] = $unsetItem;
$metaUnsetData[] = implode('.', array($key1, $key2, $unsetItem));
$metadataUnsetData[] = implode('.', array($key1, $key2, $unsetItem));
}
$unsetData = array(
$key1 => array(
$key2 => $normalizedData,
),
)
);
$this->deletedData = Util::merge($this->deletedData, $unsetData);
$this->deletedData = Util::unsetInArrayByValue('__APPEND__', $this->deletedData);
$this->meta = Util::unsetInArray($this->getData(), $metaUnsetData);
$this->data = Util::unsetInArray($this->getData(), $metadataUnsetData);
}
/**
@@ -413,33 +412,33 @@ class Metadata
public function getOrmMetadata($reload = false)
{
if (!empty($this->ormMeta) && is_array($this->ormMeta) && !$reload) {
return $this->ormMeta;
if (!empty($this->ormData) && is_array($this->ormData) && !$reload) {
return $this->ormData;
}
if (!file_exists($this->ormCacheFile) || !$this->getConfig()->get('useCache') || $reload) {
$this->getConverter()->process();
}
if (empty($this->ormMeta)) {
$this->ormMeta = $this->getFileManager()->getPhpContents($this->ormCacheFile);
if (empty($this->ormData)) {
$this->ormData = $this->getFileManager()->getPhpContents($this->ormCacheFile);
}
return $this->ormMeta;
return $this->ormData;
}
public function setOrmMetadata(array $ormMeta)
public function setOrmMetadata(array $ormData)
{
$result = true;
if ($this->getConfig()->get('useCache')) {
$result = $this->getFileManager()->putPhpContents($this->ormCacheFile, $ormMeta);
$result = $this->getFileManager()->putPhpContents($this->ormCacheFile, $ormData);
if ($result == false) {
throw new \Espo\Core\Exceptions\Error('Metadata::setOrmMetadata() - Cannot save ormMetadata to a file');
}
}
$this->ormMeta = $ormMeta;
$this->ormData = $ormData;
return $result;
}
@@ -543,8 +542,8 @@ class Metadata
*/
protected function clearVars()
{
$this->meta = null;
$this->data = null;
$this->moduleList = null;
$this->ormMeta = null;
$this->ormData = null;
}
}

View File

@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Core\Utils;
class Route
{
protected $data = null;
@@ -66,7 +67,6 @@ class Route
return $this->metadata;
}
public function get($key = '', $returns = null)
{
if (!isset($this->data)) {
@@ -91,13 +91,11 @@ class Route
return $lastRoute;
}
public function getAll()
{
return $this->get();
}
protected function init()
{
if (file_exists($this->cacheFile) && $this->getConfig()->get('useCache')) {
@@ -146,17 +144,14 @@ class Route
return $currData;
}
protected function addToData($data, $newData)
{
if (!is_array($newData)) {
return $data;
}
foreach($newData as $route) {
foreach ($newData as $route) {
$route['route'] = $this->adjustPath($route['route']);
$data[] = $route;
}
@@ -174,11 +169,10 @@ class Route
{
$routePath = trim($routePath);
if ( substr($routePath,0,1) != '/') {
if (substr($routePath,0,1) != '/') {
return '/'.$routePath;
}
return $routePath;
}
}

View File

@@ -527,7 +527,10 @@ class Util
return uniqid() . substr(md5(rand()), 0, 4);
}
public static function sanitizeFileName($fileName)
{
return preg_replace("/([^\w\s\d\-_~,;:\[\]\(\).])/u", '_', $fileName);
}
}
?>

View File

@@ -43,7 +43,7 @@ return array (
'version' => '@@version',
'timeZone' => 'UTC',
'dateFormat' => 'MM/DD/YYYY',
'timeFormat' => 'HH:mm',
'timeFormat' => 'hh:mm a',
'weekStart' => 0,
'thousandSeparator' => ',',
'decimalMark' => '.',
@@ -63,6 +63,7 @@ return array (
'smtpPassword' => '',
'languageList' => [
'en_US',
'cs_CZ',
'de_DE',
'es_ES',
'fr_FR',
@@ -91,7 +92,7 @@ return array (
'Lead',
'Opportunity',
),
"tabList" => ["Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign"],
"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',
'exportDisabled' => false,
@@ -111,6 +112,39 @@ return array (
'inboundEmailMaxPortionSize' => 20,
'authTokenLifetime' => 0,
'authTokenMaxIdleTime' => 120,
'userNameRegularExpression' => '[^a-z0-9\-@_\.\s]',
'displayListViewRecordCount' => true,
'dashboardLayout' => [
(object) [
'name' => 'My Espo',
'layout' => [
(object) [
'id' => 'default-activities',
'name' => 'Activities',
'x' => 2,
'y' => 2,
'width' => 2,
'height' => 2
],
(object) [
'id' => 'default-stream',
'name' => 'Stream',
'x' => 0,
'y' => 0,
'width' => 2,
'height' => 4
],
(object) [
'id' => 'default-tasks',
'name' => 'Tasks',
'x' => 2,
'y' => 0,
'width' => 2,
'height' => 2
]
]
]
],
'isInstalled' => false
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -93,6 +93,7 @@ return array ( 'defaultPermissions' =>
'cryptKey',
'restrictedMode',
'userLimit',
'portalUserLimit',
'stylesheet'
),
'adminItems' =>

View File

@@ -25,11 +25,19 @@
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
************************************************************************/
namespace Espo\Entities;
class Attachment extends \Espo\Core\ORM\Entity
{
public function getSourceId()
{
$sourceId = $this->get('sourceId');
if (!$sourceId) {
$sourceId = $this->id;
}
return $sourceId;
}
}

View File

@@ -0,0 +1,54 @@
<?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 Portal extends \Espo\Core\ORM\Entity
{
protected $settingsAttributeList = [
'companyLogoId',
'tabList',
'quickCreateList',
'dashboardLayout',
'dashletsOptions',
'theme',
'language',
'timeZone',
'dateFormat',
'timeFormat',
'weekStart',
'defaultCurrency'
];
public function getSettingsAttributeList()
{
return $this->settingsAttributeList;
}
}

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 PortalRole extends \Espo\Core\ORM\Entity
{
}

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