Compare commits

...

563 Commits

Author SHA1 Message Date
yuri 3c5fe4c778 v 2016-05-20 15:41:08 +03:00
yuri f6f3f05aa5 fix meeting acl 2016-05-20 15:39:51 +03:00
yuri 10f34564d0 load teams for user 2016-05-20 15:38:40 +03:00
yuri 216ec2329d fix record isPermittedTeams 2016-05-20 15:30:49 +03:00
yuri c3cb4619cf disable formatting for autoincrement 2016-05-20 11:21:06 +03:00
yuri fcf2929b5b fix record/base 2016-05-19 17:56:19 +03:00
yuri 15c10ca553 fix notice 2016-05-18 16:38:40 +03:00
yuri 9bb417ab38 v 2016-05-18 16:32:25 +03:00
yuri 5b110ee7b2 fix warning 2016-05-18 16:30:46 +03:00
yuri f3951966f3 fix popup 2016-05-18 16:25:20 +03:00
yuri f580f0ba60 namimng 2016-05-18 12:34:53 +03:00
yuri a9e4ad3833 sidePanels refactoring 2016-05-18 11:22:18 +03:00
yuri 3e6c88eb27 lead-document relationship 2016-05-17 17:16:33 +03:00
yuri 7c4ef3f00f query: use distinct instead of group by id 2016-05-17 15:51:08 +03:00
yuri 762d7f71c6 fix date time 2016-05-17 11:18:30 +03:00
yuri 50cc658c20 fix campaign image 2016-05-16 16:09:18 +03:00
yuri 21a59cb198 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-05-16 12:56:36 +03:00
Taras Machyshyn 38ba3461be Fixed SemVer for PHP 7 2016-05-16 12:55:51 +03:00
yuri f70ee27809 remove notes from user layout 2016-05-16 10:59:08 +03:00
yuri 674c1588d9 iOS 9 support 2016-05-13 12:40:26 +03:00
yuri e9eb5402cc fix selector 2016-05-11 17:35:55 +03:00
yuri 7f746a72bb ui performance oprtimization 2016-05-11 16:22:02 +03:00
yuri bde6a810c4 cleanup 2016-05-11 11:42:12 +03:00
yuri ff3e7d1c5b fit add dashlet modal height 2016-05-11 11:41:27 +03:00
yuri 7ecb63189a fix full form from quick edit 2016-05-11 10:56:33 +03:00
yuri eb1e16d6bd phone/email search more optimized way 2016-05-10 16:45:43 +03:00
yuri f17b5d7244 small fix in RDB 2016-05-10 13:04:04 +03:00
yuri a5b44e9fdf entity manager: text filter fields 2016-05-10 12:34:29 +03:00
yuri 734219dbd4 customUrl portal layout 2016-05-09 11:26:02 +03:00
yuri 45c351ab39 user entity small fixes 2016-05-09 11:22:06 +03:00
yuri 74aa55a8aa store sent emails 2016-05-09 11:19:14 +03:00
yuri 64b3a88ba0 fix shared calendar 2016-05-09 11:06:12 +03:00
yuri 08ff00f602 fix user select manager 2016-05-09 11:05:52 +03:00
yuri 0d9853311a inbox dashlet layout change 2016-05-06 12:54:24 +03:00
yuri b1d17f9e84 fix shared calendar store 2016-05-06 12:02:00 +03:00
yuri 3a6041a8df shared calendar improvements 2016-05-06 11:57:37 +03:00
yuri 023171f19e merge 2016-05-05 15:17:29 +03:00
yuri 546fed83c8 version 4.0.6 2016-05-05 15:15:54 +03:00
yuri 6b37ce80fd fix kb 2016-05-05 15:14:32 +03:00
yuri a98c8b3bcd naming 2016-05-05 13:08:28 +03:00
yuri 7bd3dd26cf notificationSoundsDisabled 2016-05-05 13:03:15 +03:00
yuri fe7684b46c naming 2016-05-05 12:56:16 +03:00
yuri ea5dcfe630 entity manager trimming 2016-05-05 12:12:40 +03:00
yuri ad5f29338b field manager: trim 2016-05-05 11:59:35 +03:00
yuri 89d2b26613 cleanup 2016-05-05 11:10:56 +03:00
yuri 7206dc38bc meeting/call acl fix: allow read own record if no assigned 2016-05-05 11:08:26 +03:00
yuri 4b9ff21743 version 4.2.0 2016-05-04 17:05:09 +03:00
yuri fbe7910349 merge 2016-05-04 16:32:19 +03:00
yuri 7b098095cb v 4.0.5 2016-05-04 16:18:36 +03:00
yuri 7966be90e7 fix acl 2016-05-04 16:18:12 +03:00
yuri b64db89307 fix acl 2016-05-04 16:17:25 +03:00
yuri cb9b7c3e72 code fix suite 2016-05-04 16:16:57 +03:00
yuri a268962c15 dont save email reminder 2016-05-04 15:16:29 +03:00
yuri 36c1b56916 scheduled job mass update 2016-05-04 14:51:49 +03:00
yuri 39a2e30e3f version 2016-05-04 11:29:12 +03:00
yuri b91d237f0b portal users activities 2016-05-04 11:25:12 +03:00
yuri f7a0a0daa0 config textFilterUseContainsForVarchar 2016-05-04 10:52:25 +03:00
yuri 4f270d003b linkParent field entityList filtered by disabled 2016-05-04 10:23:34 +03:00
yuri e3ebc8c2e2 portal customUrl and fix 2016-05-04 09:57:42 +03:00
yuri c31c5618dd fix portal user sendPassword 2016-05-04 09:47:10 +03:00
yuri 341d5f8d7c fix InboundEmail namespace 2016-05-04 09:45:24 +03:00
yuri 04d2bd69c1 fix jobs 2016-04-29 16:40:05 +03:00
yuri 47efa792ee kb/documents buttons 2016-04-29 12:15:04 +03:00
yuri a71c6789bc fix dateTimeOptional required 2016-04-29 11:49:50 +03:00
yuri 3a41aad935 fix job.php 2016-04-28 17:14:42 +03:00
yuri fad56c1c0a email craetedAt filter 2016-04-28 17:09:08 +03:00
yuri d6ad7f16c1 cron populate logic 2016-04-28 16:16:31 +03:00
yuri ae89a9216d fix email importer 2016-04-28 15:18:14 +03:00
yuri 6927c82a0f Application setupSystemUser 2016-04-28 11:00:12 +03:00
yuri e06b553653 fix avatar 2016-04-27 16:24:11 +03:00
yuri e9a63c0c36 merge all links 2016-04-27 12:26:06 +03:00
yuri 582c23ecf4 metadata get by array 2016-04-27 12:24:42 +03:00
yuri d86ff1e905 user mass update layout change 2016-04-27 11:43:33 +03:00
yuri 773bca8448 user mass update fix 2016-04-27 11:41:45 +03:00
yuri a6258cd334 afterMassUpdate afterMassRemove methods 2016-04-27 11:41:35 +03:00
yuri 1cdf88ef9b cleanup 2016-04-27 11:41:14 +03:00
yuri 58ffbad422 fix jobs 2016-04-27 11:40:57 +03:00
yuri aafcd5767e emails dashlet 2016-04-26 11:59:22 +03:00
yuri 2bfac8352b fix wysiwyg 2016-04-26 11:39:07 +03:00
yuri 954eff0c94 acl change 2016-04-26 11:36:42 +03:00
yuri 9692076ba8 int format 2016-04-26 11:08:30 +03:00
yuri 5ae64989eb system avatar color 2016-04-25 17:45:57 +03:00
yuri 3fd34fc48d show more after remove issue fix 2016-04-25 15:28:39 +03:00
yuri 94c207677f int count 2016-04-25 15:23:04 +03:00
yuri 39188f0b3a fix phone number import 2016-04-25 11:07:44 +03:00
yuri 7665ec2730 naming 2016-04-22 11:20:12 +03:00
yuri bc57ac3065 fix task dashlet 2016-04-22 11:20:07 +03:00
yuri 6cf62736bb wysiwyg fix base tag 2016-04-20 17:59:46 +03:00
yuri d5e78c1a7a field manager: foreign fielld 2016-04-20 15:37:18 +03:00
yuri 4a0aa1bca0 global search fix 2016-04-20 10:32:05 +03:00
yuri 2d06e8a801 calendar custom entity support 2016-04-19 16:32:40 +03:00
yuri 25b1479c23 fix layouts 2016-04-19 16:09:10 +03:00
yuri 09ab17f18b naming fix 2016-04-19 16:03:38 +03:00
yuri be438a2f18 pl_PL fix 2016-04-19 11:51:48 +03:00
yuri c9f91bb8da merge fix 2016-04-19 11:51:06 +03:00
yuri cd01178151 fix activities service 2016-04-19 11:48:09 +03:00
yuri 3e8ed72246 mass update keep emails and phones 2016-04-19 11:32:56 +03:00
yuri c500520130 remove dashlet confirmation 2016-04-18 16:45:48 +03:00
yuri 0fc2eb6817 fix lang 2016-04-18 16:42:43 +03:00
yuri 4db2f49233 fix json 2016-04-15 16:30:45 +03:00
yuri 8215c44be9 fix address format 2016-04-15 15:35:10 +03:00
yuri dbee926a94 address formats 2016-04-15 15:28:54 +03:00
yuri d85e13cae0 timeline today by default 2016-04-14 16:42:31 +03:00
yuri 370eee8d9d fix timeline lang 2016-04-14 16:34:26 +03:00
yuri 98f317cb1c calendar scope list for create 2016-04-14 12:43:17 +03:00
yuri 3ec9613b0e timeline and calendar less 2016-04-14 12:02:37 +03:00
yuri 8e27d50d5c about.tpl 2016-04-13 17:17:53 +03:00
yuri fc50991317 fix deprecated ajax usage 2016-04-13 16:37:19 +03:00
yuri 52382bb958 fix deprecated moment usage 2016-04-13 16:22:41 +03:00
yuri 66fdd40659 timeline dashlet fix 2016-04-13 15:50:20 +03:00
yuri f0f402a8b3 calendar timeline dashlet 2016-04-13 12:40:45 +03:00
yuri 591cbf4484 opp dashlet sorting 2016-04-13 10:00:10 +03:00
yuri 44d813bf5b fix person name template 2016-04-13 09:36:02 +03:00
yuri fceff4ad7f calendar changes 2016-04-12 17:19:15 +03:00
yuri f47b41928e fix view field 2016-04-12 17:14:58 +03:00
yuri c559a97953 timeline dev 2016-04-11 17:16:14 +03:00
yuri d1d64c84b0 fix typo 2016-04-11 12:46:16 +03:00
yuri cb1f32ec3a Merge branch 'hotfix/4.0.5' 2016-04-11 11:36:13 +03:00
yuri e76116810f fix calendar weekStart 2016-04-11 11:36:01 +03:00
yuri 974b69eed0 timeline dev 2016-04-08 17:20:38 +03:00
yuri bb379c7a0e fix invitation 2016-04-08 10:30:00 +03:00
yuri 5664ede648 fix invitation 2016-04-08 10:28:31 +03:00
yuri 881a3db412 dev timeline 2016-04-08 10:26:53 +03:00
yuri b74f1cc5d6 Merge branch 'hotfix/4.0.5' 2016-04-07 12:51:38 +03:00
yuri 265ec60614 remove empty side panel box 2016-04-07 12:51:22 +03:00
yuri d79d716612 download file name escape 2016-04-07 12:51:09 +03:00
yuri aa61f322bc fix email attachment filename parsing 2016-04-07 12:50:42 +03:00
yuri c8d2f08c13 fix naming 2016-04-07 10:41:36 +03:00
yuri 920b6e2eba dev 2016-04-06 16:09:11 +03:00
yuri 6e7908de52 timeline dev 1 2016-04-05 16:22:13 +03:00
yuri 10de6aedbd Merge branch 'hotfix/4.0.5' 2016-04-05 11:23:46 +03:00
yuri ce0efed7b8 fix calendar 2016-04-05 11:23:32 +03:00
yuri 204b9aa49d dev 2016-04-05 10:55:02 +03:00
yuri ae1db7990e Merge branch 'hotfix/4.0.5' 2016-04-04 16:37:11 +03:00
yuri 18f13acfa9 assigner user field: dont display avatar in list view 2016-04-04 16:10:06 +03:00
yuri a15b247952 fix lang 2016-04-01 16:34:00 +03:00
yuri 2e457e1f6f fix entity manager link conflicts 2016-04-01 16:30:53 +03:00
yuri e0375a52f9 fix relationship manager ui 2016-04-01 16:30:38 +03:00
yuri 351a70015a fix email address and phone number duplicates 2016-04-01 12:27:52 +03:00
yuri 8faa001a56 use teams view 2016-04-01 11:34:48 +03:00
yuri 182fc8b65e Merge branch 'hotfix/4.0.5' 2016-04-01 11:16:34 +03:00
yuri 49b581dafd assigned user view 2016-04-01 11:16:25 +03:00
yuri 1319c54365 fix assignment permission no 2016-04-01 11:12:33 +03:00
yuri c9db2687f5 fetch only header for emails w/ exceeding size 2016-03-31 12:38:37 +03:00
yuri 21252a6eb7 Merge branch 'hotfix/4.0.5' 2016-03-31 11:27:21 +03:00
yuri 1054050542 fix list modal 2 2016-03-31 11:27:12 +03:00
yuri 90e2d7ef0b cleanup 2016-03-31 11:24:56 +03:00
yuri de9c1e7a20 merge 2016-03-31 11:24:30 +03:00
yuri 08b5f09c55 fix list modal 2016-03-31 11:21:46 +03:00
yuri 23a0ed86e0 cleanup 2016-03-31 10:34:50 +03:00
yuri 0ca7da454b fix notice 2016-03-31 10:33:16 +03:00
yuri ffc22673f7 Merge branch 'hotfix/4.0.5' 2016-03-30 14:59:08 +03:00
yuri 1faa75c303 add email to quick create list 2016-03-30 12:30:01 +03:00
yuri 8136eed152 orm improvements 2016-03-30 12:29:07 +03:00
yuri 13dc6f0d76 email quick create 2016-03-30 11:19:46 +03:00
yuri dd55141422 Merge branch 'hotfix/4.0.5' 2016-03-30 10:36:14 +03:00
yuri d3bcadce13 fix contact filter is empty account 2016-03-30 10:36:06 +03:00
yuri 99533a5416 dont show notification about note if no access to entity 2016-03-29 16:20:05 +03:00
yuri 4a288434bc lead - case 2016-03-29 16:00:40 +03:00
yuri 9218bce3e4 kb email addition 2016-03-29 15:36:50 +03:00
yuri b566413b78 Merge branch 'hotfix/4.0.5' 2016-03-29 15:30:46 +03:00
yuri f624b441e4 KB: send email 2016-03-29 15:30:01 +03:00
yuri ebe7834092 error message if restore password with empty smtp 2016-03-28 15:34:34 +03:00
yuri 9c8f54fd24 fix send test email button 2016-03-28 15:34:01 +03:00
yuri 4a20d74258 Merge branch 'hotfix/4.0.4' 2016-03-25 16:02:53 +02:00
yuri f47915d077 fix list 2016-03-25 15:20:19 +02:00
yuri eb2305712a user smtp info 2016-03-25 12:30:10 +02:00
yuri f75414a5d2 fix intaller 2016-03-25 12:30:02 +02:00
yuri a88cb05897 fix installer 2016-03-25 11:55:30 +02:00
yuri 2435ae67c9 v 2016-03-25 11:51:02 +02:00
yuri 4c49be5203 installer fix 2016-03-25 11:50:58 +02:00
yuri 2d8a1dad80 fix installer 2016-03-25 05:30:16 -04:00
yuri a15b009133 user filters 2016-03-25 05:11:11 -04:00
yuri 25d2033b7c user select default filter active 2016-03-25 05:08:08 -04:00
yuri 9abeb6aec8 Merge branch 'hotfix/4.0.4' 2016-03-25 05:01:28 -04:00
yuri c509eeae49 fix notices 2016-03-25 05:01:05 -04:00
yuri 2a4b0dbcb4 remove task dublicate index 2016-03-25 04:56:47 -04:00
yuri e45a863e8d fix invitation 2016-03-24 11:27:53 +02:00
yuri 2ca40a6b4e Merge branch 'hotfix/4.0.4' 2016-03-23 17:01:30 +02:00
yuri c4819e29e0 fix inv email date time 2016-03-23 17:01:00 +02:00
yuri 176fae228c Merge branch 'hotfix/4.0.4' 2016-03-23 16:57:33 +02:00
yuri c0242a18e3 fix invitation email and email template 2016-03-23 16:57:18 +02:00
yuri 7a953c9a47 use word-break for long field 2016-03-23 16:09:07 +02:00
yuri 27cc0d812e url field title 2016-03-23 16:06:45 +02:00
Yuri Kuznetsov 2a1df998eb Merge pull request #123 from ecm4u/master
allow "Ends With" and "Like (%)" on varchar field
2016-03-23 14:59:56 +02:00
yuri 78573b85b5 Merge branch 'hotfix/4.0.4' 2016-03-23 14:59:09 +02:00
yuri 7a433c1890 fix list remove 2016-03-23 13:03:44 +02:00
yuri de26d87400 list title 2016-03-23 12:02:03 +02:00
yuri 07da707503 Record controller fix sort 2016-03-23 11:40:24 +02:00
yuri b473b19b45 fix opportunity dashlet acl 2016-03-22 16:19:44 +02:00
yuri 6c291999f4 keep meeting duration if rescheduled 2016-03-22 13:07:14 +02:00
yuri 2e92d0d3a5 case email compose fix 2016-03-22 12:03:21 +02:00
yuri 2ac5695e45 case email compose fix 2016-03-22 12:02:44 +02:00
yuri 0af4ab54f9 calendar quick view 2016-03-22 11:50:46 +02:00
yuri 8da92966de Merge branch 'hotfix/4.0.4' 2016-03-22 10:41:47 +02:00
yuri bc3dfc7ff9 fix warnings 2016-03-18 16:05:41 +02:00
yuri e81d5707f2 fix warnings 2016-03-18 15:26:04 +02:00
yuri 4cb42f8762 Merge branch 'hotfix/4.0.4' 2016-03-18 14:47:51 +02:00
yuri 1d2e3aff89 fix job rep 2016-03-18 14:47:21 +02:00
yuri e36c8d6053 change year 2016-03-18 11:25:45 +02:00
yuri 4ea1d50caf fix detail getFieldViews 2016-03-18 11:19:14 +02:00
yuri a50361126f Merge branch 'hotfix/4.0.4' 2016-03-17 12:32:49 +02:00
yuri 865a8e2abc fix E_STRICT notices 2016-03-16 17:34:50 +02:00
yuri 037ef7ea78 Merge branch 'hotfix/4.0.4' 2016-03-16 12:49:17 +02:00
yuri 5783f4e708 fix autoincrement 2016-03-16 12:48:44 +02:00
yuri 9991dd350f fix autoincrement 2016-03-16 12:48:18 +02:00
yuri 557a48ec6a isDraggable 2016-03-15 16:01:43 +02:00
yuri d5fa18975e try catch for send password 2016-03-14 16:54:35 +02:00
yuri 23ee06e123 hide panels and buttons from portal user 2016-03-14 16:50:44 +02:00
yuri e47eab0ce7 fix select manager 2016-03-14 15:52:06 +02:00
yuri 17ae5b6b5a fix file permissions 2016-03-14 15:51:58 +02:00
yuri fa29bf3309 v 2016-03-14 11:43:24 +02:00
Heiko Robert e839e505c8 en localisation for new search filter 2016-03-11 15:50:47 +01:00
Heiko Robert 7dc9c3e6b6 added search filter "Ends With", "Like" for varchar fields 2016-03-11 15:46:15 +01:00
yuri 684585278d fix email import 2016-03-11 12:02:21 +02:00
yuri e303be8155 fix german salutations 2016-03-10 15:17:40 +02:00
yuri f1285f0615 fix default side panel tpl 2016-03-10 12:46:21 +02:00
yuri 3fd1974d86 fix build permissions 2016-03-09 15:57:13 +02:00
yuri d48716e65a account: text search by email address 2016-03-09 11:20:57 +02:00
yuri a0432051f4 fix lead service 2016-03-09 11:03:56 +02:00
yuri 076c3aa65b trim search 2016-03-09 10:44:32 +02:00
yuri 92abd16032 remove notification if record removed 2016-03-04 12:40:30 +02:00
yuri e6632066a6 aclPortal case: default status readOnly 2016-03-04 12:28:43 +02:00
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
1525 changed files with 43553 additions and 11638 deletions
+6 -5
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
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).
+46 -20
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',
@@ -33,7 +34,7 @@ module.exports = function (grunt) {
'client/lib/jquery.autocomplete.js',
'client/lib/bootstrap.min.js',
'client/lib/bootstrap-datepicker.js',
'client/lib/bull.min.js',
'client/lib/bull.js',
'client/src/namespace.js',
'client/src/exceptions.js',
'client/src/loader.js',
@@ -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',
@@ -195,8 +214,11 @@ module.exports = function (grunt) {
},
src: [
'build/EspoCRM-<%= pkg.version %>/install',
'build/EspoCRM-<%= pkg.version %>/portal',
'build/EspoCRM-<%= pkg.version %>/api',
'build/EspoCRM-<%= pkg.version %>/api/v1',
'build/EspoCRM-<%= pkg.version %>/api/v1/portal-access',
'build/EspoCRM-<%= pkg.version %>',
]
}
},
@@ -212,8 +234,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'
}
]
},
+2 -2
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
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]
+39
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
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>
+93
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;
}
}
+8 -7
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;
}
}
+45
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;
}
}
+41
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;
}
}
+93
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;
}
}
+71
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;
}
}
@@ -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;
}
}
+41
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;
}
}
+6 -2
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')
+4 -1
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);
+5 -2
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);
}
@@ -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) {
@@ -69,12 +69,18 @@ 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'];
}
if (!empty($data['sortDirection'])) {
$params['asc'] = $data['sortDirection'] === 'asc';
}
if (isset($data['textFilterFields']) && is_array($data['textFilterFields'])) {
$params['textFilterFields'] = $data['textFilterFields'];
}
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
@@ -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();
-1
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();
+5 -5
View File
@@ -41,17 +41,17 @@ class Job extends \Espo\Core\Controllers\Record
}
}
public function actionCreate($params, $data)
public function actionCreate($params, $data, $request)
{
throw new Forbidden();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
throw new Forbidden();
}
@@ -66,12 +66,12 @@ class Job extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
public function actionCreateLink($params, $data)
public function actionCreateLink($params, $data, $request)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data)
public function actionRemoveLink($params, $data, $request)
{
throw new Forbidden();
}
+1 -1
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;
+28 -4
View File
@@ -31,23 +31,22 @@ 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';
public function actionList($params, $data, $request)
{
$scope = $params['scope'];
$id = $params['id'];
$userId = $this->getUser()->id;
$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 +68,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();
}
}
+43
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();
}
}
}
@@ -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
{
}
@@ -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();
}
}
@@ -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;
}
+13 -15
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);
}
}
+29 -4
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);
}
}
+101 -66
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;
}
}
+519 -84
View File
@@ -32,54 +32,114 @@ 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 (!$this->user->isFetched()) {
throw new Error();
if ($fieldManager) {
$this->fieldManager = $fieldManager;
}
$this->user->loadLinkMultipleField('teams');
if (!$this->user->isFetched()) {
throw new Error('User must be fetched before ACL check.');
}
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->getUser()->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 +147,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 +164,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 +182,349 @@ 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->getUser()->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->getUser()->isAdmin()) {
foreach ($this->valuePermissionList as $permission) {
$this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get('app.'.$this->type.'.default.' . $permission, 'yes'));
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
$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');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$teamList = $this->getUser()->get('teams');
if (!(is_array($teamList) || $teamList instanceof \Traversable)) {
throw new Error();
}
foreach ($teamList as $team) {
$teamRoleList = $team->get('roles');
foreach ($teamRoleList as $role) {
$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->getUser()->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->getUser()->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->getUser()->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 +544,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 +620,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;
}
}
+53 -31
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 {
@@ -91,14 +98,18 @@ class AclManager
protected function getTable(User $user)
{
$key = spl_object_hash($user);
$key = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$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 +130,6 @@ class AclManager
public function get(User $user, $permission)
{
if ($user->isAdmin()) {
return true;
}
return $this->getTable($user)->get($permission);
}
@@ -143,46 +151,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 +223,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);
}
}
+212
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;
}
}
+131
View File
@@ -0,0 +1,131 @@
<?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');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$portalRoleList = $this->getPortal()->get('portalRoles');
if (!(is_array($portalRoleList) || $portalRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($portalRoleList as $role) {
$roleList[] = $role;
}
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 (!empty($d['disabled']) || !empty($d['portalDisabled'])) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
}
+78 -36
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,37 @@ 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;
}
public function setupSystemUser()
{
$user = $this->getContainer()->get('entityManager')->getEntity('User', 'system');
$this->getContainer()->setUser($user);
$this->getContainer()->get('entityManager')->setUser($user);
}
}
+50 -30
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,24 +187,26 @@ 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'),
$this->get('user'),
$this->get('acl'),
$this->get('metadata')
$this->get('aclManager'),
$this->get('metadata'),
$this->get('config')
);
}
private function loadMetadata()
protected function loadMetadata()
{
return new \Espo\Core\Utils\Metadata(
$this->get('config'),
@@ -203,15 +214,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 +231,7 @@ class Container
);
}
private function loadAcl()
protected function loadAcl()
{
$className = $this->getServiceClassName('acl', '\\Espo\\Core\\Acl');
return new $className(
@@ -228,7 +240,7 @@ class Container
);
}
private function loadSchema()
protected function loadSchema()
{
return new \Espo\Core\Utils\Database\Schema\Schema(
$this->get('config'),
@@ -239,7 +251,7 @@ class Container
);
}
private function loadClassParser()
protected function loadClassParser()
{
return new \Espo\Core\Utils\File\ClassParser(
$this->get('fileManager'),
@@ -248,7 +260,7 @@ class Container
);
}
private function loadLanguage()
protected function loadLanguage()
{
return new \Espo\Core\Utils\Language(
$this->get('fileManager'),
@@ -258,28 +270,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 +299,7 @@ class Container
);
}
private function loadThemeManager()
protected function loadThemeManager()
{
return new \Espo\Core\Utils\ThemeManager(
$this->get('config'),
@@ -295,9 +307,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);
}
}
+1 -1
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);
}
+12 -12
View File
@@ -64,7 +64,7 @@ class Record extends Base
return $service;
}
public function actionRead($params)
public function actionRead($params, $data, $request)
{
$id = $params['id'];
$entity = $this->getRecordService()->getEntity($id);
@@ -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();
}
@@ -128,7 +128,7 @@ class Record extends Base
$where = $request->get('where');
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$asc = $request->get('asc') === 'true';
$asc = $request->get('asc', 'true') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$primaryFilter = $request->get('primaryFilter');
@@ -174,7 +174,7 @@ class Record extends Base
$where = $request->get('where');
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$asc = $request->get('asc') === 'true';
$asc = $request->get('asc', 'true') === 'true';
$sortBy = $request->get('sortBy');
$q = $request->get('q');
$textFilter = $request->get('textFilter');
@@ -292,7 +292,6 @@ class Record extends Base
$params['where'] = $where;
}
if (array_key_exists('ids', $data)) {
$where = json_decode(json_encode($data['where']), true);
$params['ids'] = $data['ids'];
}
@@ -321,18 +320,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 +385,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'];
@@ -411,17 +410,18 @@ class Record extends Base
throw new BadRequest();
}
if (empty($data['targetId']) || empty($data['sourceIds']) || !is_array($data['sourceIds'])) {
if (empty($data['targetId']) || empty($data['sourceIds']) || !is_array($data['sourceIds']) || !($data['attributes'] instanceof \StdClass)) {
throw new BadRequest();
}
$targetId = $data['targetId'];
$sourceIds = $data['sourceIds'];
$attributes = get_object_vars($data['attributes']);
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
}
return $this->getRecordService()->merge($targetId, $sourceIds);
return $this->getRecordService()->merge($targetId, $sourceIds, $attributes);
}
}
@@ -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(),
+58 -48
View File
@@ -35,15 +35,21 @@ use Espo\Core\Exceptions\NotFound;
class CronManager
{
private $container;
private $config;
private $fileManager;
private $entityManager;
private $scheduledJobUtil;
const PENDING = 'Pending';
const RUNNING = 'Running';
const SUCCESS = 'Success';
const FAILED = 'Failed';
protected $lastRunTime = 'data/cache/application/cronLastRunTime.php';
@@ -149,19 +155,15 @@ class CronManager
$this->setLastRunTime(time());
$entityManager = $this->getEntityManager();
$cronJob = $this->getCronJob();
$cronScheduledJob = $this->getCronScheduledJob();
//Check scheduled jobs and create related jobs
$this->getCronJob()->markFailedJobs();
$this->getCronJob()->updateFailedJobAttempts();
$this->createJobsFromScheduledJobs();
$this->getCronJob()->removePendingJobDuplicates();
$pendingJobs = $cronJob->getPendingJobs();
$pendingJobList = $this->getCronJob()->getPendingJobList();
foreach ($pendingJobs as $job) {
$jobEntity = $entityManager->getEntity('Job', $job['id']);
foreach ($pendingJobList as $job) {
$jobEntity = $this->getEntityManager()->getEntity('Job', $job['id']);
if (!isset($jobEntity)) {
$GLOBALS['log']->error('CronManager: empty Job entity ['.$job['id'].'].');
@@ -169,7 +171,7 @@ class CronManager
}
$jobEntity->set('status', self::RUNNING);
$entityManager->saveEntity($jobEntity);
$this->getEntityManager()->saveEntity($jobEntity);
$isSuccess = true;
@@ -187,11 +189,10 @@ class CronManager
$status = $isSuccess ? self::SUCCESS : self::FAILED;
$jobEntity->set('status', $status);
$entityManager->saveEntity($jobEntity);
$this->getEntityManager()->saveEntity($jobEntity);
//set status in the schedulerJobLog
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], $status);
$this->getCronScheduledJob()->addLogRecord($job['scheduled_job_id'], $status, null, $job['target_id'], $job['target_type']);
}
}
}
@@ -213,12 +214,20 @@ class CronManager
}
$jobClass = new $className($this->container);
$method = $this->getScheduledJobUtil()->getMethodName();
$method = 'run';
if (!method_exists($jobClass, $method)) {
throw new NotFound();
}
$jobClass->$method();
$data = null;
if (!empty($job['data'])) {
$data = $job['data'];
if (Json::isJSON($data)) {
$data = Json::decode($data, true);
}
}
$jobClass->$method($data, $job['target_id'], $job['target_type']);
}
/**
@@ -248,7 +257,7 @@ class CronManager
$data = Json::decode($data, true);
}
$service->$serviceMethod($data);
$service->$serviceMethod($data, $job['target_id'], $job['target_type']);
}
/**
@@ -258,55 +267,56 @@ class CronManager
*/
protected function createJobsFromScheduledJobs()
{
$entityManager = $this->getEntityManager();
$activeScheduledJobList = $this->getCronScheduledJob()->getActiveScheduledJobList();
$activeScheduledJobs = $this->getCronScheduledJob()->getActiveJobs();
$cronJob = $this->getCronJob();
$runningScheduledJobs = $cronJob->getActiveJobs('scheduled_job_id', self::RUNNING, PDO::FETCH_COLUMN);
$createdJobs = array();
foreach ($activeScheduledJobs as $scheduledJob) {
if (in_array($scheduledJob['id'], $runningScheduledJobs)) {
continue;
}
$runningScheduledJobIdList = $this->getCronJob()->getRunningScheduledJobIdList();
$createdJobIdList = array();
foreach ($activeScheduledJobList as $scheduledJob) {
$scheduling = $scheduledJob['scheduling'];
$cronExpression = \Cron\CronExpression::factory($scheduling);
try {
$prevDate = $cronExpression->getPreviousRunDate()->format('Y-m-d H:i:s');
$previousDate = $cronExpression->getPreviousRunDate()->format('Y-m-d H:i:s');
} catch (\Exception $e) {
$GLOBALS['log']->error('CronManager: ScheduledJob ['.$scheduledJob['id'].']: CronExpression - Impossible CRON expression ['.$scheduling.']');
continue;
}
if ($cronExpression->isDue()) {
$prevDate = date('Y-m-d H:i:s');
$previousDate = date('Y-m-d H:i:s');
}
$existsJob = $cronJob->getJobByScheduledJob($scheduledJob['id'], $prevDate);
if (!isset($existsJob) || empty($existsJob)) {
//create a new job
$jobEntity = $entityManager->getEntity('Job');
$jobEntity->set(array(
'name' => $scheduledJob['name'],
'status' => self::PENDING,
'scheduledJobId' => $scheduledJob['id'],
'executeTime' => $prevDate,
'method' => $scheduledJob['job'],
));
$jobEntityId = $entityManager->saveEntity($jobEntity);
if (!empty($jobEntityId)) {
$createdJobs[] = $jobEntityId;
$className = $this->getScheduledJobUtil()->get($scheduledJob['job']);
if ($className) {
if (method_exists($className, 'prepare')) {
$implementation = new $className($this->container);
$implementation->prepare($scheduledJob, $previousDate);
continue;
}
}
if (in_array($scheduledJob['id'], $runningScheduledJobIdList)) {
continue;
}
$existingJob = $this->getCronJob()->getJobByScheduledJob($scheduledJob['id'], $previousDate);
if ($existingJob) continue;
$jobEntity = $this->getEntityManager()->getEntity('Job');
$jobEntity->set(array(
'name' => $scheduledJob['name'],
'status' => self::PENDING,
'scheduledJobId' => $scheduledJob['id'],
'executeTime' => $previousDate,
'method' => $scheduledJob['job']
));
$this->getEntityManager()->saveEntity($jobEntity);
$createdJobIdList[] = $jobEntity->id;
}
return $createdJobs;
return $createdJobIdList;
}
}
+11 -2
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)
+7 -2
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();
}
-2
View File
@@ -70,7 +70,5 @@ abstract class Base
$this->container = $container;
}
abstract public function run();
}
+93 -52
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 = [], $fetchOnlyHeader = false)
{
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)) {
@@ -172,28 +166,37 @@ class Importer
$inlineIds = array();
if ($message->isMultipart()) {
foreach (new \RecursiveIteratorIterator($message) as $part) {
$this->importPartDataToEmail($email, $part, $inlineIds);
if (!$fetchOnlyHeader) {
if ($message->isMultipart()) {
foreach (new \RecursiveIteratorIterator($message) as $part) {
$this->importPartDataToEmail($email, $part, $inlineIds);
}
} else {
$this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain');
}
if (!$email->get('body') && $email->get('bodyPlain')) {
$email->set('body', $email->get('bodyPlain'));
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
if (strpos($body, 'cid:' . $cid) !== false) {
$body = str_replace('cid:' . $cid, '?entryPoint=attachment&amp;id=' . $attachmentId, $body);
} else {
$email->addLinkMultipleId('attachments', $attachmentId);
}
}
$email->set('body', $body);
}
if ($this->getFiltersMatcher()->matchBody($email, $filterList)) {
return false;
}
} else {
$this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain');
}
if (!$email->get('body') && $email->get('bodyPlain')) {
$email->set('body', $email->get('bodyPlain'));
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
$body = str_replace('cid:' . $cid, '?entryPoint=attachment&amp;id=' . $attachmentId, $body);
}
$email->set('body', $body);
}
if ($this->getFiltersMatcher()->matchBody($email, $filterList)) {
return false;
$email->set('body', '(Not fetched)');
$email->set('isHtml', false);
}
$parentFound = false;
@@ -358,6 +361,8 @@ class Importer
} else if (strpos(strtolower($part->ContentDisposition), 'inline') === 0) {
$contentDisposition = 'inline';
}
} else if (isset($part->contentID)) {
$contentDisposition = 'inline';
}
if (empty($type)) {
@@ -401,7 +406,6 @@ class Importer
$contentId = null;
if ($contentDisposition) {
if ($contentDisposition === 'attachment') {
$fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition);
if ($fileName) {
@@ -446,7 +450,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') {
@@ -460,24 +464,61 @@ class Importer
} catch (\Exception $e) {}
}
protected function fetchFileNameFromContentDisposition($contentDisposition)
protected function decodeAttachmentFileName($fileName)
{
$m = array();
if (preg_match('/filename="?([^"]+)"?/i', $contentDisposition, $m)) {
$fileName = $m[1];
return $fileName;
} else if (preg_match('/filename\*="?([^"]+)"?/i', $contentDisposition, $m)) {
$fileName = $m[1];
if ($fileName && stripos($fileName, "''") !== false) {
list($encoding, $fileName) = explode("''", $fileName);
$fileName = rawurldecode($fileName);
if (strtoupper($encoding) !== 'UTF-8') {
if ($fileName && stripos($fileName, "''") !== false) {
list($encoding, $fileName) = explode("''", $fileName);
$fileName = rawurldecode($fileName);
if (strtoupper($encoding) !== 'UTF-8') {
if ($encoding) {
$fileName = mb_convert_encoding($fileName, 'UTF-8', $encoding);
}
return $fileName;
}
}
return false;
return $fileName;
}
protected function fetchFileNameFromContentDisposition($contentDisposition)
{
$contentDisposition = preg_replace('/\\\\"/', "{{_!Q!U!O!T!E!_}}", $contentDisposition);
$fileName = false;
$m = array();
if (preg_match('/filename="([^"]+)";?/i', $contentDisposition, $m)) {
$fileName = $m[1];
} else if (preg_match('/filename=([^";]+);?/i', $contentDisposition, $m)) {
$fileName = $m[1];
} else if (preg_match('/filename\*="([^"]+)";?/i', $contentDisposition, $m)) {
$fileName = $m[1];
$fileName = $this->decodeAttachmentFileName($fileName);
} else if (preg_match('/filename\*=([^";]+);?/i', $contentDisposition, $m)) {
$fileName = $m[1];
$fileName = $this->decodeAttachmentFileName($fileName);
} else {
$fileName = '';
foreach (['0', '1'] as $i) {
if (preg_match('/filename\*'.$i.'[\*]?="([^"]+)";?/i', $contentDisposition, $m)) {
$part = $m[1];
$fileName .= $part;
} else if (preg_match('/filename\*'.$i.'[\*]?=([^";]+);?/i', $contentDisposition, $m)) {
$part = $m[1];
$fileName .= $part;
}
}
if ($fileName === '') {
$fileName = null;
} else {
$fileName = $this->decodeAttachmentFileName($fileName);
}
}
if ($fileName) {
$fileName = str_replace('{{_!Q!U!O!T!E!_}}', '"', $fileName);
}
return $fileName;
}
protected function getContentFromPart($part)
+16 -3
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;
+133 -28
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;
}
}
+76 -12
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);
}
}
}
}
}
+57
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);
}
}
+122
View File
@@ -0,0 +1,122 @@
<?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 = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManager');
$portal = $this->getContainer()->get('portal');
$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);
}
}
@@ -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
));
}
}
+126
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);
}
}
}
@@ -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'));
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -29,11 +29,11 @@
namespace Espo\Core\Repositories;
use \Espo\Core\Entities\CategoryTreeItem as Entity;
use \Espo\ORM\Entity;
class CategoryTree extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, $options)
protected function afterSave(Entity $entity, array $options = array())
{
parent::afterSave($entity, $options);
@@ -86,7 +86,7 @@ class CategoryTree extends \Espo\Core\ORM\Repositories\RDB
}
}
public function afterRemove(Entity $entity, $options)
protected function afterRemove(Entity $entity, array $options = array())
{
parent::afterRemove($entity, $options);
@@ -43,12 +43,14 @@ class SelectManagerFactory
private $metadata;
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Utils\Metadata $metadata, Utils\Config $config)
{
$this->entityManager = $entityManager;
$this->user = $user;
$this->acl = $acl;
$this->aclManager = $aclManager;
$this->metadata = $metadata;
$this->config = $config;
}
public function create($entityType)
@@ -68,7 +70,7 @@ class SelectManagerFactory
}
}
$selectManager = new $className($this->entityManager, $this->user, $this->acl, $this->metadata);
$selectManager = new $className($this->entityManager, $this->user, $this->acl, $this->aclManager, $this->metadata, $this->config);
$selectManager->setEntityType($entityType);
return $selectManager;
+480 -111
View File
@@ -30,8 +30,12 @@
namespace Espo\Core\SelectManagers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Acl;
use \Espo\Core\AclManager;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\Config;
class Base
{
@@ -47,19 +51,25 @@ class Base
protected $metadata;
private $config;
private $seed = null;
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)
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config)
{
$this->entityManager = $entityManager;
$this->user = $user;
$this->acl = $acl;
$this->aclManager = $aclManager;
$this->metadata = $metadata;
$this->config = $config;
}
protected function getEntityManager()
@@ -77,6 +87,16 @@ class Base
return $this->acl;
}
protected function getConfig()
{
return $this->config;
}
protected function getAclManager()
{
return $this->aclManager;
}
public function setEntityType($entityType)
{
$this->entityType = $entityType;
@@ -115,9 +135,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 +177,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 +343,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 +376,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 +612,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 +643,9 @@ class Base
}
if (!empty($params['where']) && is_array($params['where'])) {
if ($checkWherePermission) {
$this->checkWhere($params['where']);
}
$this->where($params['where'], $result);
}
@@ -418,9 +659,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 +849,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);
}
@@ -617,6 +892,9 @@ class Base
case 'startsWith':
$part[$item['field'] . '*'] = $item['value'] . '%';
break;
case 'endsWith':
$part[$item['field'] . '*'] = $item['value'] . '%';
break;
case 'contains':
$part[$item['field'] . '*'] = '%' . $item['value'] . '%';
break;
@@ -648,6 +926,7 @@ class Base
$part[$item['field'] . '='] = null;
break;
case 'isNotNull':
case 'ever':
$part[$item['field'] . '!='] = null;
break;
case 'isTrue':
@@ -798,15 +1077,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['leftJoins']);
}
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 +1129,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,22 +1164,40 @@ 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) {
$expression = $textFilter . '%';
if (
strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
&&
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
(
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
||
$this->getConfig()->get('textFilterUseContainsForVarchar')
)
) {
$d[$field . '*'] = '%' . $textFilter . '%';
$expression = '%' . $textFilter . '%';
} else {
$d[$field . '*'] = $textFilter . '%';
$expression = $textFilter . '%';
}
$d[$field . '*'] = $expression;
}
$result['whereClause'][] = array(
'OR' => $d
@@ -872,9 +1229,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)
@@ -2,7 +2,8 @@
"fields": {
"name": {
"type": "varchar",
"required": true
"required": true,
"trim": true
},
"description": {
"type": "text"
@@ -17,18 +18,22 @@
},
"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/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": true,
"notifications": true
@@ -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"
@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": false,
"notifications": false
@@ -59,18 +59,22 @@
},
"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/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
@@ -3,6 +3,7 @@
"layouts": true,
"tab": true,
"acl": true,
"aclPortal": true,
"customizable": true,
"importable": true,
"notifications": true
@@ -32,7 +32,7 @@ use Espo\Core\Utils\Util;
use Espo\Core\Utils\System;
use Espo\Core\Utils\Json;
use Espo\Core\Exceptions\Error;
use vierbergenlars\SemVer;
use Composer\Semver\Semver;
abstract class Base
{
@@ -221,20 +221,12 @@ abstract class Base
$versionList = (array) $versionList;
}
try {
$semver = new SemVer\version($currentVersion);
} catch (\Exception $e) {
$GLOBALS['log']->error('Cannot recognize currentVersion ['.$currentVersion.'], error: '.$e->getMessage().'.');
return false;
}
foreach ($versionList as $version) {
$isInRange = false;
try {
$isInRange = $semver->satisfies(new SemVer\expression($version));
$isInRange = Semver::satisfies($currentVersion, $version);
} catch (\Exception $e) {
$GLOBALS['log']->error('Version identification error: '.$e->getMessage().'.');
$GLOBALS['log']->error('SemVer: Version identification error: '.$e->getMessage().'.');
}
if ($isInRange) {
-18
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>');
}
}
+105 -21
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,45 @@ 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);
} else {
$user->loadLinkMultipleField('teams');
}
$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 +199,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;
}
}
@@ -73,6 +73,5 @@ abstract class Base
return $this->passwordHash;
}
}
@@ -45,7 +45,7 @@ class Espo extends Base
'whereClause' => array(
'userName' => $username,
'password' => $hash
),
)
));
return $user;
@@ -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;
}
}
+21 -9
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;
}
}
+127 -71
View File
@@ -69,20 +69,18 @@ class Job
*
* @return array
*/
public function getPendingJobs()
public function getPendingJobList()
{
/** Mark Failed old jobs and remove pending duplicates */
$this->markFailedJobs();
$this->markJobAttempts();
$this->removePendingJobDuplicates();
$jobConfigs = $this->getConfig()->get('cron');
$limit = !empty($jobConfigs['maxJobNumber']) ? intval($jobConfigs['maxJobNumber']) : 0;
$jobList = $this->getActiveJobs();
$jobList = $this->getJobList(CronManager::PENDING, $limit);
$runningScheduledJobs = $this->getActiveJobs('scheduled_job_id', CronManager::RUNNING, PDO::FETCH_COLUMN);
$runningScheduledJobIdList = $this->getRunningScheduledJobIdList();
$list = array();
$list = [];
foreach ($jobList as $row) {
if (!in_array($row['scheduled_job_id'], $runningScheduledJobs)) {
if (!in_array($row['scheduled_job_id'], $runningScheduledJobIdList)) {
$list[] = $row;
}
}
@@ -90,32 +88,63 @@ class Job
return $list;
}
/**
* Get active Jobs, which execution date in jobPeriod time
*
* @param string $displayColumns
* @param string $status
*
* @return array
*/
public function getActiveJobs($displayColumns = '*', $status = CronManager::PENDING, $fetchMode = PDO::FETCH_ASSOC)
public function getRunningScheduledJobIdList()
{
$jobConfigs = $this->getConfig()->get('cron');
$currentTime = time();
$limit = empty($jobConfigs['maxJobNumber']) ? '' : 'LIMIT '.$jobConfigs['maxJobNumber'];
$query = "SELECT " . $displayColumns . " FROM job WHERE
`status` = '" . $status . "'
AND execute_time <= '".date('Y-m-d H:i:s', $currentTime)."'
AND deleted = 0
ORDER BY execute_time ASC ".$limit;
$list = [];
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT scheduled_job_id FROM job
WHERE
`status` = 'Running' AND
scheduled_job_id IS NOT NULL AND
target_id IS NULL AND
deleted = 0
ORDER BY execute_time
";
$sth = $pdo->prepare($query);
$sth->execute();
$rows = $sth->fetchAll($fetchMode);
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($rowList as $row) {
$list[] = $row['scheduled_job_id'];
}
return $list;
}
/**
* Get job list, which execution date in jobPeriod time
*
* @param string $status
* @param int $limit
*
* @return array
*/
public function getJobList($status = CronManager::PENDING, $limit = null)
{
$currentTime = time();
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT * FROM job
WHERE
`status` = " . $pdo->quote($status) . "
AND execute_time <= '".date('Y-m-d H:i:s', $currentTime)."'
AND deleted = 0
ORDER BY execute_time ASC
";
if ($limit) {
$query .= " LIMIT " . $limit ;
}
$sth = $pdo->prepare($query);
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
return $rows;
}
@@ -133,13 +162,17 @@ class Job
$dateObj = new \DateTime($time);
$timeWithoutSeconds = $dateObj->format('Y-m-d H:i:');
$query = "SELECT * FROM job WHERE
scheduled_job_id = '".$scheduledJobId."'
AND execute_time LIKE '".$timeWithoutSeconds."%'
AND deleted = 0
LIMIT 1";
$pdo = $this->getEntityManager()->getPDO();
$query = "
SELECT * FROM job
WHERE
scheduled_job_id = ".$pdo->quote($scheduledJobId)."
AND execute_time LIKE ". $pdo->quote($timeWithoutSeconds . '%') . "
AND deleted = 0
LIMIT 1
";
$sth = $pdo->prepare($query);
$sth->execute();
@@ -153,7 +186,7 @@ class Job
*
* @return void
*/
protected function markFailedJobs()
public function markFailedJobs()
{
$jobConfigs = $this->getConfig()->get('cron');
@@ -162,9 +195,11 @@ class Job
$pdo = $this->getEntityManager()->getPDO();
$select = "SELECT id, scheduled_job_id, execute_time FROM `job` WHERE
(`status` = '" . CronManager::RUNNING ."')
AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."' ";
$select = "
SELECT id, scheduled_job_id, execute_time, target_id, target_type FROM `job`
WHERE
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."'
";
$sth = $pdo->prepare($select);
$sth->execute();
@@ -173,7 +208,11 @@ class Job
$jobData[$row['id']] = $row;
}
$update = "UPDATE job SET `status` = '". CronManager::FAILED ."' WHERE id IN ('".implode("', '", array_keys($jobData))."')";
$update = "
UPDATE job
SET `status` = '". CronManager::FAILED ."'
WHERE id IN ('".implode("', '", array_keys($jobData))."')
";
$sth = $pdo->prepare($update);
$sth->execute();
@@ -181,7 +220,7 @@ class Job
$cronScheduledJob = $this->getCronScheduledJob();
foreach ($jobData as $jobId => $job) {
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time']);
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
}
}
}
@@ -191,38 +230,50 @@ class Job
*
* @return void
*/
protected function removePendingJobDuplicates()
public function removePendingJobDuplicates()
{
$pdo = $this->getEntityManager()->getPDO();
$query = "SELECT scheduled_job_id FROM job
WHERE
scheduled_job_id IS NOT NULL
AND `status` = '".CronManager::PENDING."'
AND execute_time <= '".date('Y-m-d H:i:s')."'
AND deleted = 0
GROUP BY scheduled_job_id
HAVING count( * ) > 1
ORDER BY execute_time ASC";
$query = "
SELECT scheduled_job_id
FROM job
WHERE
scheduled_job_id IS NOT NULL AND
`status` = '".CronManager::PENDING."' AND
execute_time <= '".date('Y-m-d H:i:s')."' AND
target_id IS NULL AND
deleted = 0
GROUP BY scheduled_job_id
HAVING count( * ) > 1
ORDER BY execute_time ASC
";
$sth = $pdo->prepare($query);
$sth->execute();
$duplicateJobs = $sth->fetchAll(PDO::FETCH_ASSOC);
$duplicateJobList = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($duplicateJobs as $row) {
foreach ($duplicateJobList as $row) {
if (!empty($row['scheduled_job_id'])) {
/* no possibility to use limit in update or subqueries */
$query = "SELECT id FROM `job` WHERE scheduled_job_id = '" . $row['scheduled_job_id'] . "'
AND `status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000 ";
$query = "
SELECT id FROM `job`
WHERE
scheduled_job_id = '" . $row['scheduled_job_id'] . "' AND
`status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000
";
$sth = $pdo->prepare($query);
$sth->execute();
$jobIds = $sth->fetchAll(PDO::FETCH_COLUMN);
$update = "UPDATE job SET deleted = 1 WHERE
id IN ('". implode("', '", $jobIds)."') ";
$update = "
UPDATE job
SET deleted = 1
WHERE
id IN ('". implode("', '", $jobIds)."')
";
$sth = $pdo->prepare($update);
$sth->execute();
@@ -235,13 +286,16 @@ class Job
*
* @return void
*/
protected function markJobAttempts()
public function updateFailedJobAttempts()
{
$query = "SELECT * FROM job WHERE
`status` = '" . CronManager::FAILED . "'
AND deleted = 0
AND execute_time <= '".date('Y-m-d H:i:s')."'
AND attempts > 0";
$query = "
SELECT * FROM job
WHERE
`status` = '" . CronManager::FAILED . "' AND
deleted = 0 AND
execute_time <= '".date('Y-m-d H:i:s')."' AND
attempts > 0
";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -255,12 +309,14 @@ class Job
$attempts = $row['attempts'] - 1;
$failedAttempts = $row['failed_attempts'] + 1;
$update = "UPDATE job SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
$update = "
UPDATE job
SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
";
$pdo->prepare($update)->execute();
}
@@ -59,11 +59,14 @@ class ScheduledJob
*
* @return array
*/
public function getActiveJobs()
public function getActiveScheduledJobList()
{
$query = "SELECT * FROM scheduled_job WHERE
`status` = 'Active'
AND deleted = 0";
$query = "
SELECT * FROM scheduled_job
WHERE
`status` = 'Active' AND
deleted = 0
";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -87,7 +90,7 @@ class ScheduledJob
*
* @return string ID of created ScheduledJobLogRecord
*/
public function addLogRecord($scheduledJobId, $status, $runTime = null)
public function addLogRecord($scheduledJobId, $status, $runTime = null, $targetId = null, $targetType = null)
{
if (!isset($runTime)) {
$runTime = date('Y-m-d H:i:s');
@@ -96,6 +99,11 @@ class ScheduledJob
$entityManager = $this->getEntityManager();
$scheduledJob = $entityManager->getEntity('ScheduledJob', $scheduledJobId);
if (!$scheduledJob) {
return;
}
$scheduledJob->set('lastRun', $runTime);
$entityManager->saveEntity($scheduledJob);
@@ -105,6 +113,8 @@ class ScheduledJob
'name' => $scheduledJob->get('name'),
'status' => $status,
'executionTime' => $runTime,
'targetId' => $targetId,
'targetType' => $targetType
));
$scheduledJobLogId = $entityManager->saveEntity($scheduledJobLog);
@@ -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';
@@ -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';
@@ -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';
@@ -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';
@@ -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';
@@ -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';
@@ -66,7 +66,9 @@ class Currency extends Base
"<" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate < {value}",
">=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate >= {value}",
"<=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <= {value}",
"<>" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <> {value}"
"<>" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <> {value}",
"IS NULL" => Util::toUnderScore($entityName) . "." . $currencyColumnName . ' IS NULL',
"IS NOT NULL" => Util::toUnderScore($entityName) . "." . $currencyColumnName . ' IS NOT NULL',
),
'notStorable' => true,
'orderBy' => $converedFieldName . " {direction}"
@@ -48,22 +48,36 @@ class Email extends Base
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
email_address.deleted = 0 AND email_address.name LIKE {value}
)",
'=' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_email_address
JOIN email_address ON email_address.id = entity_email_address.email_address_id
WHERE
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
email_address.deleted = 0 AND email_address.name = {value}
)",
'<>' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_email_address
JOIN email_address ON email_address.id = entity_email_address.email_address_id
WHERE
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
email_address.deleted = 0 AND email_address.name <> {value}
)"
'=' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name = {value}',
'distinct' => true
),
'<>' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name <> {value}',
'distinct' => true
),
'IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IN {value}',
'distinct' => true
),
'NOT IN' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name NOT IN {value}',
'distinct' => true
),
'IS NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NULL',
'distinct' => true
),
'IS NOT NULL' => array(
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
'sql' => 'emailAddressesMultiple.name IS NOT NULL',
'distinct' => true
)
),
'orderBy' => 'emailAddresses.name {direction}',
),
@@ -73,7 +87,7 @@ class Email extends Base
),
),
'relations' => array(
$fieldName.'es' => array(
'emailAddresses' => array(
'type' => 'manyMany',
'entity' => 'EmailAddress',
'relationName' => 'entityEmailAddress',
@@ -48,22 +48,36 @@ class Phone extends Base
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name LIKE {value}
)",
'=' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_phone_number
JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id
WHERE
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name = {value}
)",
'<>' => \Espo\Core\Utils\Util::toUnderScore($entityName) . ".id IN (
SELECT entity_id
FROM entity_phone_number
JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id
WHERE
entity_phone_number.deleted = 0 AND entity_phone_number.entity_type = '{$entityName}' AND
phone_number.deleted = 0 AND phone_number.name <> {value}
)"
'=' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name = {value}',
'distinct' => true
),
'<>' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name <> {value}',
'distinct' => true
),
'IN' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IN {value}',
'distinct' => true
),
'NOT IN' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name NOT IN {value}',
'distinct' => true
),
'IS NULL' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IS NULL',
'distinct' => true
),
'IS NOT NULL' => array(
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
'sql' => 'phoneNumbersMultiple.name IS NOT NULL',
'distinct' => true
)
),
'orderBy' => 'phoneNumbers.name {direction}',
),
@@ -73,7 +87,7 @@ class Phone extends Base
),
),
'relations' => array(
$fieldName.'s' => array(
'phoneNumbers' => array(
'type' => 'manyMany',
'entity' => 'PhoneNumber',
'relationName' => 'entityPhoneNumber',
@@ -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
)
)
)
)
)
);
}
}
@@ -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;
}
}
@@ -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) ) {
+11 -14
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);
+40 -8
View File
@@ -82,6 +82,8 @@ class EntityManager
throw new Error();
}
$name = trim($name);
$normalizedName = Util::normilizeClassName($name);
$contents = "<" . "?" . "php\n\n".
@@ -123,6 +125,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 +145,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 +186,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);
}
@@ -208,6 +219,15 @@ class EntityManager
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
if (isset($data['textFilterFields'])) {
$entityDefsData = array(
'collection' => array(
'textFilterFields' => $data['textFilterFields']
)
);
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
$this->getMetadata()->save();
$this->getLanguage()->save();
@@ -261,9 +281,9 @@ class EntityManager
$linkType = $params['linkType'];
$entity = $params['entity'];
$link = $params['link'];
$link = trim($params['link']);
$entityForeign = $params['entityForeign'];
$linkForeign = $params['linkForeign'];
$linkForeign = trim($params['linkForeign']);
$label = $params['label'];
$labelForeign = $params['labelForeign'];
@@ -311,9 +331,15 @@ class EntityManager
switch ($linkType) {
case 'oneToMany':
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.field.' . $linkForeign)) {
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign)) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Id')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Name')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(
@@ -352,9 +378,15 @@ class EntityManager
);
break;
case 'manyToOne':
if ($this->getMetadata()->get('entityDefs.' . $entity . '.field.' . $link)) {
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link)) {
throw new Conflict('Field ['.$entity.'::'.$link.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Id')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Name')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(
@@ -89,6 +89,7 @@ class FieldManager
public function update($name, $fieldDefs, $scope)
{
$name = trim($name);
/*Add option to metadata to identify the custom field*/
if (!$this->isCore($name, $scope)) {
$fieldDefs[$this->customOptionName] = true;
@@ -285,4 +286,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));
}
}
+37 -8
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);
}
/**
@@ -248,7 +249,7 @@ class Manager
public function putContentsJson($path, $data)
{
if (!Utils\Json::isJSON($data)) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return $this->putContents($path, $data, LOCK_EX);
@@ -289,7 +290,7 @@ class Manager
$data = Utils\Util::merge($savedDataArray, $newDataArray);
if ($isReturnJson) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if ($isPhp) {
@@ -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;
}
/**
+4
View File
@@ -35,9 +35,13 @@ use \Espo\Core\Utils\Util,
class Language
{
private $fileManager;
private $config;
private $metadata;
private $preferences;
private $unifier;
/**
+18 -18
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;
}
+49 -50
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,17 +177,17 @@ 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;
}
/**
* Get Metadata
*
* @param string $key
* @param mixed string|array $key
* @param mixed $default
*
* @return array
@@ -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;
}
}
+3 -9
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;
}
}
+11 -4
View File
@@ -449,7 +449,7 @@ class Util
* Return values of defined $key.
*
* @param array $array
* @param string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed array|string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed $default
* @return mixed
*/
@@ -459,7 +459,11 @@ class Util
return $array;
}
$keys = explode('.', $key);
if (is_array($key)) {
$keys = $key;
} else {
$keys = explode('.', $key);
}
$lastItem = $array;
foreach($keys as $keyName) {
@@ -527,7 +531,10 @@ class Util
return uniqid() . substr(md5(rand()), 0, 4);
}
public static function sanitizeFileName($fileName)
{
return preg_replace("/([^\w\s\d\-_~,;:\[\]\(\).])/u", '_', $fileName);
}
}
?>
+38 -3
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,8 +92,8 @@ return array (
'Lead',
'Opportunity',
),
"tabList" => ["Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign"],
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case"],
"tabList" => ["Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign", "KnowledgeBaseArticle"],
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case", "Email"],
'calendarDefaultEntity' => 'Meeting',
'exportDisabled' => false,
'assignmentEmailNotifications' => false,
@@ -111,6 +112,40 @@ return array (
'inboundEmailMaxPortionSize' => 20,
'authTokenLifetime' => 0,
'authTokenMaxIdleTime' => 120,
'userNameRegularExpression' => '[^a-z0-9\-@_\.\s]',
'addressFormat' => 1,
'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
);
@@ -1 +0,0 @@
[{"name":"name","link":true,"width":"30"}]
@@ -1 +0,0 @@
[{"name":"name"}]
@@ -1 +0,0 @@
[{"name":"name","link":true,"width":"30"}]

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