Compare commits

..

378 Commits

Author SHA1 Message Date
yuri f87ff8db8a fix 2016-06-29 15:38:21 +03:00
yuri 60d121f1e1 portal acl fixes 2016-06-29 15:17:52 +03:00
yuri 1497abff07 v 2016-06-29 13:12:09 +03:00
yuri 935ba7d4e4 fix 2016-06-29 13:10:36 +03:00
yuri ea5f6300f3 fix text overflow in list expanded 2016-06-24 12:15:51 +03:00
yuri b6da94fe2e fix modal backdrop close 2016-06-17 11:54:09 +03:00
yuri 6c9d1dbb3d fix notification sounds 2016-06-14 11:43:31 +03:00
yuri c78254a8fa no dashboard layout for portal useres 2016-06-13 10:52:20 +03:00
yuri 4ca2a7fa1a fix notice 2016-06-10 16:03:10 +03:00
yuri 55e5a21dcd Merge branch 'hotfix/4.1.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.1.5 2016-06-10 15:54:14 +03:00
Taras Machyshyn 792f422f76 Bug fixing 2016-06-10 15:53:37 +03:00
yuri 0cfe91d960 loadAdditionalFieldsForPdf 2016-06-10 14:44:09 +03:00
yuri 4dea1762e8 stream textarea fix 2016-06-10 13:08:22 +03:00
Taras Machyshyn b1a9b8d8b1 Code improvement 2016-06-10 10:57:56 +03:00
yuri eb0adf7c28 fix afterMassRemove 2016-06-09 14:03:23 +03:00
yuri cbbfa44174 fix field manager ui 2016-06-08 17:58:18 +03:00
yuri 8451949805 database charset param 2016-06-08 11:47:52 +03:00
yuri af5750cdf6 color change 2016-06-07 19:00:57 +03:00
yuri 1a4dfd6f67 version 2016-06-07 12:29:29 +03:00
yuri 6aa800d453 fix fetchOnModelAfterRelate 2016-06-07 12:26:20 +03:00
yuri 82aa3b9508 fix massRelate 2016-06-07 12:15:04 +03:00
yuri 6910a113da email filter: filters 2016-06-06 16:49:07 +03:00
yuri bd20aaa577 fix htmlizer 2016-06-06 12:55:04 +03:00
yuri 0aef3c0b04 htmlizer and password 2016-06-06 12:53:10 +03:00
yuri 2f97010b54 htmlizer improvements 2016-06-06 12:31:26 +03:00
yuri 5bcdad2996 wysiwyg htmlToPlain fix 2016-06-06 11:59:49 +03:00
yuri 1e8a8d94c2 fix timeline timezone 2016-06-06 11:51:21 +03:00
yuri 9aeefd7685 contact: fix accounts field 2016-06-03 11:29:46 +03:00
yuri f72385471c naming fix 2016-06-03 11:27:41 +03:00
yuri f7a542560a check duplicates: all email 2016-06-03 11:19:03 +03:00
yuri 400f43447d v 2016-06-02 17:22:15 +03:00
yuri 866593a831 Storage Message: define ErrorHandler 2016-06-02 17:15:30 +03:00
yuri c90cdc62fb global search changes 2016-06-02 12:39:06 +03:00
yuri d5c93f21b5 audited for miltiEnum and arrays 2016-06-02 12:26:47 +03:00
yuri b58d78a29e preferences: dashboardLayout 2016-05-31 12:05:37 +03:00
yuri f5b41eb78b field manager: translatedOptions param 2016-05-31 11:01:17 +03:00
yuri c2a7d90944 fix view-helper options 2016-05-27 13:17:51 +03:00
yuri b2a4ec238c fix preferences fields 2016-05-27 12:11:17 +03:00
yuri df69584c7d v 2016-05-27 11:46:31 +03:00
yuri 574da55be7 Merge branch 'hotfix/4.1.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/4.1.3 2016-05-26 17:15:40 +03:00
Taras Machyshyn 52b808b902 Removed unnecessary debugging messages 2016-05-26 16:53:52 +03:00
Taras Machyshyn 714c7b0a33 Fixed an issue for MySQL 5.7 2016-05-26 16:41:50 +03:00
yuri 3d774e3afa fix wysywyg modals 2016-05-26 16:26:16 +03:00
yuri c91db1699b upgrade bootstrap 2016-05-26 15:39:04 +03:00
yuri 2b5695d8dc fix wysywig field detail view height 2016-05-26 12:23:52 +03:00
yuri b562fc33bd dont allow email to modify 2016-05-26 11:43:52 +03:00
yuri 425414b8f3 fix add email address 2016-05-26 11:24:21 +03:00
yuri 3b363d5ee3 cleanup 2016-05-26 11:10:34 +03:00
yuri 4fc3b9a99e fix kb massUpdate layout 2016-05-25 15:23:12 +03:00
yuri ab2b9bafeb fix meeting/call select manager 2016-05-24 15:53:09 +03:00
yuri e99fc6302f reminders for all users 2016-05-24 12:35:46 +03:00
yuri 82996a807f calendar: getCalendarSelectParams method 2016-05-24 11:38:14 +03:00
yuri 86b31b1f26 clear role cach if isAdmin changed 2016-05-23 17:25:58 +03:00
yuri 19c9b38f82 applicationName in settings 2016-05-23 12:24:18 +03:00
yuri b1d0d1cd27 activities: status not required 2016-05-23 11:47:04 +03:00
yuri c916011530 add id_ID language 2016-05-23 11:36:58 +03:00
yuri 5db6327272 de_DE 2016-05-23 11:10:03 +03:00
yuri 3c5fe4c778 v 2016-05-20 15:41:08 +03:00
yuri f6f3f05aa5 fix meeting acl 2016-05-20 15:39:51 +03:00
yuri 10f34564d0 load teams for user 2016-05-20 15:38:40 +03:00
yuri 216ec2329d fix record isPermittedTeams 2016-05-20 15:30:49 +03:00
yuri c3cb4619cf disable formatting for autoincrement 2016-05-20 11:21:06 +03:00
yuri fcf2929b5b fix record/base 2016-05-19 17:56:19 +03:00
yuri 15c10ca553 fix notice 2016-05-18 16:38:40 +03:00
yuri 9bb417ab38 v 2016-05-18 16:32:25 +03:00
yuri 5b110ee7b2 fix warning 2016-05-18 16:30:46 +03:00
yuri f3951966f3 fix popup 2016-05-18 16:25:20 +03:00
yuri f580f0ba60 namimng 2016-05-18 12:34:53 +03:00
yuri a9e4ad3833 sidePanels refactoring 2016-05-18 11:22:18 +03:00
yuri 3e6c88eb27 lead-document relationship 2016-05-17 17:16:33 +03:00
yuri 7c4ef3f00f query: use distinct instead of group by id 2016-05-17 15:51:08 +03:00
yuri 762d7f71c6 fix date time 2016-05-17 11:18:30 +03:00
yuri 50cc658c20 fix campaign image 2016-05-16 16:09:18 +03:00
yuri 21a59cb198 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2016-05-16 12:56:36 +03:00
Taras Machyshyn 38ba3461be Fixed SemVer for PHP 7 2016-05-16 12:55:51 +03:00
yuri f70ee27809 remove notes from user layout 2016-05-16 10:59:08 +03:00
yuri 674c1588d9 iOS 9 support 2016-05-13 12:40:26 +03:00
yuri e9eb5402cc fix selector 2016-05-11 17:35:55 +03:00
yuri 7f746a72bb ui performance oprtimization 2016-05-11 16:22:02 +03:00
yuri bde6a810c4 cleanup 2016-05-11 11:42:12 +03:00
yuri ff3e7d1c5b fit add dashlet modal height 2016-05-11 11:41:27 +03:00
yuri 7ecb63189a fix full form from quick edit 2016-05-11 10:56:33 +03:00
yuri eb1e16d6bd phone/email search more optimized way 2016-05-10 16:45:43 +03:00
yuri f17b5d7244 small fix in RDB 2016-05-10 13:04:04 +03:00
yuri a5b44e9fdf entity manager: text filter fields 2016-05-10 12:34:29 +03:00
yuri 734219dbd4 customUrl portal layout 2016-05-09 11:26:02 +03:00
yuri 45c351ab39 user entity small fixes 2016-05-09 11:22:06 +03:00
yuri 74aa55a8aa store sent emails 2016-05-09 11:19:14 +03:00
yuri 64b3a88ba0 fix shared calendar 2016-05-09 11:06:12 +03:00
yuri 08ff00f602 fix user select manager 2016-05-09 11:05:52 +03:00
yuri 0d9853311a inbox dashlet layout change 2016-05-06 12:54:24 +03:00
yuri b1d17f9e84 fix shared calendar store 2016-05-06 12:02:00 +03:00
yuri 3a6041a8df shared calendar improvements 2016-05-06 11:57:37 +03:00
yuri 023171f19e merge 2016-05-05 15:17:29 +03:00
yuri 546fed83c8 version 4.0.6 2016-05-05 15:15:54 +03:00
yuri 6b37ce80fd fix kb 2016-05-05 15:14:32 +03:00
yuri a98c8b3bcd naming 2016-05-05 13:08:28 +03:00
yuri 7bd3dd26cf notificationSoundsDisabled 2016-05-05 13:03:15 +03:00
yuri fe7684b46c naming 2016-05-05 12:56:16 +03:00
yuri ea5dcfe630 entity manager trimming 2016-05-05 12:12:40 +03:00
yuri ad5f29338b field manager: trim 2016-05-05 11:59:35 +03:00
yuri 89d2b26613 cleanup 2016-05-05 11:10:56 +03:00
yuri 7206dc38bc meeting/call acl fix: allow read own record if no assigned 2016-05-05 11:08:26 +03:00
yuri 4b9ff21743 version 4.2.0 2016-05-04 17:05:09 +03:00
yuri fbe7910349 merge 2016-05-04 16:32:19 +03:00
yuri 7b098095cb v 4.0.5 2016-05-04 16:18:36 +03:00
yuri 7966be90e7 fix acl 2016-05-04 16:18:12 +03:00
yuri b64db89307 fix acl 2016-05-04 16:17:25 +03:00
yuri cb9b7c3e72 code fix suite 2016-05-04 16:16:57 +03:00
yuri a268962c15 dont save email reminder 2016-05-04 15:16:29 +03:00
yuri 36c1b56916 scheduled job mass update 2016-05-04 14:51:49 +03:00
yuri 39a2e30e3f version 2016-05-04 11:29:12 +03:00
yuri b91d237f0b portal users activities 2016-05-04 11:25:12 +03:00
yuri f7a0a0daa0 config textFilterUseContainsForVarchar 2016-05-04 10:52:25 +03:00
yuri 4f270d003b linkParent field entityList filtered by disabled 2016-05-04 10:23:34 +03:00
yuri e3ebc8c2e2 portal customUrl and fix 2016-05-04 09:57:42 +03:00
yuri c31c5618dd fix portal user sendPassword 2016-05-04 09:47:10 +03:00
yuri 341d5f8d7c fix InboundEmail namespace 2016-05-04 09:45:24 +03:00
yuri 04d2bd69c1 fix jobs 2016-04-29 16:40:05 +03:00
yuri 47efa792ee kb/documents buttons 2016-04-29 12:15:04 +03:00
yuri a71c6789bc fix dateTimeOptional required 2016-04-29 11:49:50 +03:00
yuri 3a41aad935 fix job.php 2016-04-28 17:14:42 +03:00
yuri fad56c1c0a email craetedAt filter 2016-04-28 17:09:08 +03:00
yuri d6ad7f16c1 cron populate logic 2016-04-28 16:16:31 +03:00
yuri ae89a9216d fix email importer 2016-04-28 15:18:14 +03:00
yuri 6927c82a0f Application setupSystemUser 2016-04-28 11:00:12 +03:00
yuri e06b553653 fix avatar 2016-04-27 16:24:11 +03:00
yuri e9a63c0c36 merge all links 2016-04-27 12:26:06 +03:00
yuri 582c23ecf4 metadata get by array 2016-04-27 12:24:42 +03:00
yuri d86ff1e905 user mass update layout change 2016-04-27 11:43:33 +03:00
yuri 773bca8448 user mass update fix 2016-04-27 11:41:45 +03:00
yuri a6258cd334 afterMassUpdate afterMassRemove methods 2016-04-27 11:41:35 +03:00
yuri 1cdf88ef9b cleanup 2016-04-27 11:41:14 +03:00
yuri 58ffbad422 fix jobs 2016-04-27 11:40:57 +03:00
yuri aafcd5767e emails dashlet 2016-04-26 11:59:22 +03:00
yuri 2bfac8352b fix wysiwyg 2016-04-26 11:39:07 +03:00
yuri 954eff0c94 acl change 2016-04-26 11:36:42 +03:00
yuri 9692076ba8 int format 2016-04-26 11:08:30 +03:00
yuri 5ae64989eb system avatar color 2016-04-25 17:45:57 +03:00
yuri 3fd34fc48d show more after remove issue fix 2016-04-25 15:28:39 +03:00
yuri 94c207677f int count 2016-04-25 15:23:04 +03:00
yuri 39188f0b3a fix phone number import 2016-04-25 11:07:44 +03:00
yuri 7665ec2730 naming 2016-04-22 11:20:12 +03:00
yuri bc57ac3065 fix task dashlet 2016-04-22 11:20:07 +03:00
yuri 6cf62736bb wysiwyg fix base tag 2016-04-20 17:59:46 +03:00
yuri d5e78c1a7a field manager: foreign fielld 2016-04-20 15:37:18 +03:00
yuri 4a0aa1bca0 global search fix 2016-04-20 10:32:05 +03:00
yuri 2d06e8a801 calendar custom entity support 2016-04-19 16:32:40 +03:00
yuri 25b1479c23 fix layouts 2016-04-19 16:09:10 +03:00
yuri 09ab17f18b naming fix 2016-04-19 16:03:38 +03:00
yuri be438a2f18 pl_PL fix 2016-04-19 11:51:48 +03:00
yuri c9f91bb8da merge fix 2016-04-19 11:51:06 +03:00
yuri cd01178151 fix activities service 2016-04-19 11:48:09 +03:00
yuri 3e8ed72246 mass update keep emails and phones 2016-04-19 11:32:56 +03:00
yuri c500520130 remove dashlet confirmation 2016-04-18 16:45:48 +03:00
yuri 0fc2eb6817 fix lang 2016-04-18 16:42:43 +03:00
yuri 4db2f49233 fix json 2016-04-15 16:30:45 +03:00
yuri 8215c44be9 fix address format 2016-04-15 15:35:10 +03:00
yuri dbee926a94 address formats 2016-04-15 15:28:54 +03:00
yuri d85e13cae0 timeline today by default 2016-04-14 16:42:31 +03:00
yuri 370eee8d9d fix timeline lang 2016-04-14 16:34:26 +03:00
yuri 98f317cb1c calendar scope list for create 2016-04-14 12:43:17 +03:00
yuri 3ec9613b0e timeline and calendar less 2016-04-14 12:02:37 +03:00
yuri 8e27d50d5c about.tpl 2016-04-13 17:17:53 +03:00
yuri fc50991317 fix deprecated ajax usage 2016-04-13 16:37:19 +03:00
yuri 52382bb958 fix deprecated moment usage 2016-04-13 16:22:41 +03:00
yuri 66fdd40659 timeline dashlet fix 2016-04-13 15:50:20 +03:00
yuri f0f402a8b3 calendar timeline dashlet 2016-04-13 12:40:45 +03:00
yuri 591cbf4484 opp dashlet sorting 2016-04-13 10:00:10 +03:00
yuri 44d813bf5b fix person name template 2016-04-13 09:36:02 +03:00
yuri fceff4ad7f calendar changes 2016-04-12 17:19:15 +03:00
yuri f47b41928e fix view field 2016-04-12 17:14:58 +03:00
yuri c559a97953 timeline dev 2016-04-11 17:16:14 +03:00
yuri d1d64c84b0 fix typo 2016-04-11 12:46:16 +03:00
yuri cb1f32ec3a Merge branch 'hotfix/4.0.5' 2016-04-11 11:36:13 +03:00
yuri e76116810f fix calendar weekStart 2016-04-11 11:36:01 +03:00
yuri 974b69eed0 timeline dev 2016-04-08 17:20:38 +03:00
yuri bb379c7a0e fix invitation 2016-04-08 10:30:00 +03:00
yuri 5664ede648 fix invitation 2016-04-08 10:28:31 +03:00
yuri 881a3db412 dev timeline 2016-04-08 10:26:53 +03:00
yuri b74f1cc5d6 Merge branch 'hotfix/4.0.5' 2016-04-07 12:51:38 +03:00
yuri 265ec60614 remove empty side panel box 2016-04-07 12:51:22 +03:00
yuri d79d716612 download file name escape 2016-04-07 12:51:09 +03:00
yuri aa61f322bc fix email attachment filename parsing 2016-04-07 12:50:42 +03:00
yuri c8d2f08c13 fix naming 2016-04-07 10:41:36 +03:00
yuri 920b6e2eba dev 2016-04-06 16:09:11 +03:00
yuri 6e7908de52 timeline dev 1 2016-04-05 16:22:13 +03:00
yuri 10de6aedbd Merge branch 'hotfix/4.0.5' 2016-04-05 11:23:46 +03:00
yuri ce0efed7b8 fix calendar 2016-04-05 11:23:32 +03:00
yuri 204b9aa49d dev 2016-04-05 10:55:02 +03:00
yuri ae1db7990e Merge branch 'hotfix/4.0.5' 2016-04-04 16:37:11 +03:00
yuri 18f13acfa9 assigner user field: dont display avatar in list view 2016-04-04 16:10:06 +03:00
yuri a15b247952 fix lang 2016-04-01 16:34:00 +03:00
yuri 2e457e1f6f fix entity manager link conflicts 2016-04-01 16:30:53 +03:00
yuri e0375a52f9 fix relationship manager ui 2016-04-01 16:30:38 +03:00
yuri 351a70015a fix email address and phone number duplicates 2016-04-01 12:27:52 +03:00
yuri 8faa001a56 use teams view 2016-04-01 11:34:48 +03:00
yuri 182fc8b65e Merge branch 'hotfix/4.0.5' 2016-04-01 11:16:34 +03:00
yuri 49b581dafd assigned user view 2016-04-01 11:16:25 +03:00
yuri 1319c54365 fix assignment permission no 2016-04-01 11:12:33 +03:00
yuri c9db2687f5 fetch only header for emails w/ exceeding size 2016-03-31 12:38:37 +03:00
yuri 21252a6eb7 Merge branch 'hotfix/4.0.5' 2016-03-31 11:27:21 +03:00
yuri 1054050542 fix list modal 2 2016-03-31 11:27:12 +03:00
yuri 90e2d7ef0b cleanup 2016-03-31 11:24:56 +03:00
yuri de9c1e7a20 merge 2016-03-31 11:24:30 +03:00
yuri 08b5f09c55 fix list modal 2016-03-31 11:21:46 +03:00
yuri 23a0ed86e0 cleanup 2016-03-31 10:34:50 +03:00
yuri 0ca7da454b fix notice 2016-03-31 10:33:16 +03:00
yuri ffc22673f7 Merge branch 'hotfix/4.0.5' 2016-03-30 14:59:08 +03:00
yuri 1faa75c303 add email to quick create list 2016-03-30 12:30:01 +03:00
yuri 8136eed152 orm improvements 2016-03-30 12:29:07 +03:00
yuri 13dc6f0d76 email quick create 2016-03-30 11:19:46 +03:00
yuri dd55141422 Merge branch 'hotfix/4.0.5' 2016-03-30 10:36:14 +03:00
yuri d3bcadce13 fix contact filter is empty account 2016-03-30 10:36:06 +03:00
yuri 99533a5416 dont show notification about note if no access to entity 2016-03-29 16:20:05 +03:00
yuri 4a288434bc lead - case 2016-03-29 16:00:40 +03:00
yuri 9218bce3e4 kb email addition 2016-03-29 15:36:50 +03:00
yuri b566413b78 Merge branch 'hotfix/4.0.5' 2016-03-29 15:30:46 +03:00
yuri f624b441e4 KB: send email 2016-03-29 15:30:01 +03:00
yuri ebe7834092 error message if restore password with empty smtp 2016-03-28 15:34:34 +03:00
yuri 9c8f54fd24 fix send test email button 2016-03-28 15:34:01 +03:00
yuri 4a20d74258 Merge branch 'hotfix/4.0.4' 2016-03-25 16:02:53 +02:00
yuri f47915d077 fix list 2016-03-25 15:20:19 +02:00
yuri eb2305712a user smtp info 2016-03-25 12:30:10 +02:00
yuri f75414a5d2 fix intaller 2016-03-25 12:30:02 +02:00
yuri a88cb05897 fix installer 2016-03-25 11:55:30 +02:00
yuri 2435ae67c9 v 2016-03-25 11:51:02 +02:00
yuri 4c49be5203 installer fix 2016-03-25 11:50:58 +02:00
yuri 2d8a1dad80 fix installer 2016-03-25 05:30:16 -04:00
yuri a15b009133 user filters 2016-03-25 05:11:11 -04:00
yuri 25d2033b7c user select default filter active 2016-03-25 05:08:08 -04:00
yuri 9abeb6aec8 Merge branch 'hotfix/4.0.4' 2016-03-25 05:01:28 -04:00
yuri c509eeae49 fix notices 2016-03-25 05:01:05 -04:00
yuri 2a4b0dbcb4 remove task dublicate index 2016-03-25 04:56:47 -04:00
yuri e45a863e8d fix invitation 2016-03-24 11:27:53 +02:00
yuri 2ca40a6b4e Merge branch 'hotfix/4.0.4' 2016-03-23 17:01:30 +02:00
yuri c4819e29e0 fix inv email date time 2016-03-23 17:01:00 +02:00
yuri 176fae228c Merge branch 'hotfix/4.0.4' 2016-03-23 16:57:33 +02:00
yuri c0242a18e3 fix invitation email and email template 2016-03-23 16:57:18 +02:00
yuri 7a953c9a47 use word-break for long field 2016-03-23 16:09:07 +02:00
yuri 27cc0d812e url field title 2016-03-23 16:06:45 +02:00
Yuri Kuznetsov 2a1df998eb Merge pull request #123 from ecm4u/master
allow "Ends With" and "Like (%)" on varchar field
2016-03-23 14:59:56 +02:00
yuri 78573b85b5 Merge branch 'hotfix/4.0.4' 2016-03-23 14:59:09 +02:00
yuri 7a433c1890 fix list remove 2016-03-23 13:03:44 +02:00
yuri de26d87400 list title 2016-03-23 12:02:03 +02:00
yuri 07da707503 Record controller fix sort 2016-03-23 11:40:24 +02:00
yuri b473b19b45 fix opportunity dashlet acl 2016-03-22 16:19:44 +02:00
yuri 6c291999f4 keep meeting duration if rescheduled 2016-03-22 13:07:14 +02:00
yuri 2e92d0d3a5 case email compose fix 2016-03-22 12:03:21 +02:00
yuri 2ac5695e45 case email compose fix 2016-03-22 12:02:44 +02:00
yuri 0af4ab54f9 calendar quick view 2016-03-22 11:50:46 +02:00
yuri 8da92966de Merge branch 'hotfix/4.0.4' 2016-03-22 10:41:47 +02:00
yuri bc3dfc7ff9 fix warnings 2016-03-18 16:05:41 +02:00
yuri e81d5707f2 fix warnings 2016-03-18 15:26:04 +02:00
yuri 4cb42f8762 Merge branch 'hotfix/4.0.4' 2016-03-18 14:47:51 +02:00
yuri 1d2e3aff89 fix job rep 2016-03-18 14:47:21 +02:00
yuri e36c8d6053 change year 2016-03-18 11:25:45 +02:00
yuri 4ea1d50caf fix detail getFieldViews 2016-03-18 11:19:14 +02:00
yuri a50361126f Merge branch 'hotfix/4.0.4' 2016-03-17 12:32:49 +02:00
yuri 865a8e2abc fix E_STRICT notices 2016-03-16 17:34:50 +02:00
yuri 037ef7ea78 Merge branch 'hotfix/4.0.4' 2016-03-16 12:49:17 +02:00
yuri 5783f4e708 fix autoincrement 2016-03-16 12:48:44 +02:00
yuri 9991dd350f fix autoincrement 2016-03-16 12:48:18 +02:00
yuri 557a48ec6a isDraggable 2016-03-15 16:01:43 +02:00
yuri d5fa18975e try catch for send password 2016-03-14 16:54:35 +02:00
yuri 23ee06e123 hide panels and buttons from portal user 2016-03-14 16:50:44 +02:00
yuri e47eab0ce7 fix select manager 2016-03-14 15:52:06 +02:00
yuri 17ae5b6b5a fix file permissions 2016-03-14 15:51:58 +02:00
yuri fa29bf3309 v 2016-03-14 11:43:24 +02:00
Heiko Robert e839e505c8 en localisation for new search filter 2016-03-11 15:50:47 +01:00
Heiko Robert 7dc9c3e6b6 added search filter "Ends With", "Like" for varchar fields 2016-03-11 15:46:15 +01:00
yuri 684585278d fix email import 2016-03-11 12:02:21 +02:00
yuri e303be8155 fix german salutations 2016-03-10 15:17:40 +02:00
yuri f1285f0615 fix default side panel tpl 2016-03-10 12:46:21 +02:00
yuri 3fd1974d86 fix build permissions 2016-03-09 15:57:13 +02:00
yuri d48716e65a account: text search by email address 2016-03-09 11:20:57 +02:00
yuri a0432051f4 fix lead service 2016-03-09 11:03:56 +02:00
yuri 076c3aa65b trim search 2016-03-09 10:44:32 +02:00
yuri 92abd16032 remove notification if record removed 2016-03-04 12:40:30 +02:00
yuri e6632066a6 aclPortal case: default status readOnly 2016-03-04 12:28:43 +02:00
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
1418 changed files with 29653 additions and 7528 deletions
+6 -7
View File
@@ -6,15 +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
/frontend/client/css/violet.css
/frontend/client/css/violet-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).
+15 -11
View File
@@ -34,7 +34,7 @@ module.exports = function (grunt) {
'client/lib/jquery.autocomplete.js',
'client/lib/bootstrap.min.js',
'client/lib/bootstrap-datepicker.js',
'client/lib/bull.min.js',
'client/lib/bull.js',
'client/src/namespace.js',
'client/src/exceptions.js',
'client/src/loader.js',
@@ -65,7 +65,7 @@ module.exports = function (grunt) {
yuicompress: true,
},
files: {
'frontend/client/css/espo.css': 'frontend/less/espo/main.less',
'client/css/espo.css': 'frontend/less/espo/main.less',
}
},
espoVertical: {
@@ -73,7 +73,7 @@ module.exports = function (grunt) {
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: {
@@ -81,7 +81,7 @@ module.exports = function (grunt) {
yuicompress: true,
},
files: {
'frontend/client/css/sakura.css': 'frontend/less/sakura/main.less',
'client/css/sakura.css': 'frontend/less/sakura/main.less',
}
},
sakuraVertical: {
@@ -89,7 +89,7 @@ module.exports = function (grunt) {
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: {
@@ -97,7 +97,7 @@ module.exports = function (grunt) {
yuicompress: true,
},
files: {
'frontend/client/css/violet.css': 'frontend/less/violet/main.less',
'client/css/violet.css': 'frontend/less/violet/main.less',
}
},
violetVertical: {
@@ -105,7 +105,7 @@ module.exports = function (grunt) {
yuicompress: true,
},
files: {
'frontend/client/css/violet-vertical.css': 'frontend/less/violet-vertical/main.less',
'client/css/violet-vertical.css': 'frontend/less/violet-vertical/main.less',
}
}
},
@@ -113,7 +113,7 @@ module.exports = function (grunt) {
minify: {
files: {
'build/tmp/client/css/espo.css': [
'frontend/client/css/espo.css',
'client/css/espo.css',
]
}
},
@@ -124,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/**',
@@ -151,7 +151,7 @@ module.exports = function (grunt) {
frontendLib: {
expand: true,
dot: true,
cwd: 'frontend/client/lib',
cwd: 'client/lib',
src: '**',
dest: 'build/tmp/client/lib/',
},
@@ -164,6 +164,7 @@ module.exports = function (grunt) {
'custom/**',
'data/.data',
'install/**',
'portal/**',
'vendor/**',
'html/**',
'bootstrap.php',
@@ -213,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 %>',
]
}
},
-4
View File
@@ -28,12 +28,8 @@ Never update composer dependencies if you are going to contribute code back.
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. Then open `data/config.php` file and add `isDeveloperMode => true`.
### How to build
You need to have nodejs and Grunt CLI installed.
@@ -29,7 +29,11 @@
require_once('../../../bootstrap.php');
$portalId = explode('/', $_SERVER['REQUEST_URI'])[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
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();
@@ -5,8 +5,8 @@
<rewrite>
<rules>
<rule name="rule 1G" stopProcessing="true">
<match url="^" />
<action type="Rewrite" url="index.php" appendQueryString="true" />
<match url="^" />
<action type="Rewrite" url="index.php" appendQueryString="true" />
</rule>
</rules>
</rewrite>
+4
View File
@@ -40,6 +40,10 @@ class Attachment extends \Espo\Core\Acl\Base
return true;
}
if ($entity->get('parentType') === 'Settings') {
return true;
}
$parent = null;
$hasParent = false;
if ($entity->get('parentId') && $entity->get('parentType')) {
@@ -40,6 +40,10 @@ class Attachment extends \Espo\Core\AclPortal\Base
return true;
}
if ($entity->get('parentType') === 'Settings') {
return true;
}
$parent = null;
$hasParent = false;
if ($entity->get('parentId') && $entity->get('parentType')) {
@@ -78,6 +78,9 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (!empty($data['sortDirection'])) {
$params['asc'] = $data['sortDirection'] === 'asc';
}
if (isset($data['textFilterFields']) && is_array($data['textFilterFields'])) {
$params['textFilterFields'] = $data['textFilterFields'];
}
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
+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();
}
@@ -37,9 +37,6 @@ class Notification extends \Espo\Core\Controllers\Record
public function actionList($params, $data, $request)
{
$scope = $params['scope'];
$id = $params['id'];
$userId = $this->getUser()->id;
$offset = intval($request->get('offset'));
@@ -132,6 +132,7 @@ class Preferences extends \Espo\Core\Controllers\Base
$entity->set('smtpEmailAddress', $user->get('emailAddress'));
$entity->set('name', $user->get('name'));
$entity->set('isPortalUser', $user->get('isPortalUser'));
$entity->clear('smtpPassword');
@@ -28,6 +28,7 @@
************************************************************************/
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
+34 -10
View File
@@ -97,8 +97,6 @@ class Table
throw new Error('User must be fetched before ACL check.');
}
$this->user->loadLinkMultipleField('teams');
if ($fileManager) {
$this->fileManager = $fileManager;
}
@@ -119,7 +117,7 @@ class Table
protected function initCacheFilePath()
{
$this->cacheFilePath = 'data/cache/application/acl/' . $this->user->id . '.php';
$this->cacheFilePath = 'data/cache/application/acl/' . $this->getUser()->id . '.php';
}
protected function getUser()
@@ -175,7 +173,7 @@ class Table
public function getLevel($scope, $action)
{
if (isset($this->data->table->$scope)) {
if (isset($this->table->$scope->$action)) {
if (isset($this->data->table->$scope->$action)) {
return $this->data->table->$scope->$action;
}
}
@@ -189,7 +187,10 @@ class Table
$valuePermissionLists->$permission = [];
}
if (!$this->user->isAdmin()) {
$aclTableList = [];
$fieldTableList = [];
if (!$this->getUser()->isAdmin()) {
$roleList = $this->getRoleList();
foreach ($roleList as $role) {
@@ -206,6 +207,7 @@ class Table
$this->applyDefault($aclTable, $fieldTable);
$this->applyDisabled($aclTable, $fieldTable);
$this->applyMandatory($aclTable, $fieldTable);
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
} else {
$aclTable = (object) [];
foreach ($this->getScopeList() as $scope) {
@@ -239,7 +241,7 @@ class Table
$this->fillFieldTableQuickAccess();
if (!$this->user->isAdmin()) {
if (!$this->getUser()->isAdmin()) {
foreach ($this->valuePermissionList as $permission) {
$this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get('app.'.$this->type.'.default.' . $permission, 'yes'));
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
@@ -263,11 +265,17 @@ class Table
$roleList = [];
$userRoleList = $this->getUser()->get('roles');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$teamList = $this->getUser()->get('teams');
if (!(is_array($teamList) || $teamList instanceof \Traversable)) {
throw new Error();
}
foreach ($teamList as $team) {
$teamRoleList = $team->get('roles');
foreach ($teamRoleList as $role) {
@@ -393,7 +401,7 @@ class Table
protected function applyDefault(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
@@ -458,7 +466,7 @@ class Table
protected function applyMandatory(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
@@ -484,6 +492,9 @@ class Table
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';
@@ -502,18 +513,31 @@ class Table
protected function applyDisabled(&$table, &$fieldTable)
{
if ($this->user->isAdmin()) {
if ($this->getUser()->isAdmin()) {
return;
}
foreach ($this->getScopeList() as $scope) {
if ($this->getMetadata()->get('scopes.' . $scope . '.disabled')) {
$aclTable->$scope = false;
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists)
{
if ($this->getUser()->get('isPortalUser')) {
foreach ($this->getScopeList() as $scope) {
$table->$scope = false;
unset($fieldTable->$scope);
}
foreach ($this->valuePermissionList as $permission) {
$valuePermissionLists->{$permission}[] = 'no';
}
}
}
private function mergeValueList(array $list, $defaultValue)
{
$result = null;
+5 -2
View File
@@ -98,7 +98,10 @@ class AclManager
protected function getTable(User $user)
{
$key = spl_object_hash($user);
$key = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
@@ -181,7 +184,7 @@ class AclManager
return $this->getImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity, $action)
public function checkInTeam(User $user, Entity $entity)
{
return $this->getImplementation($entity->getEntityType())->checkInTeam($user, $entity);
}
+12 -2
View File
@@ -76,11 +76,17 @@ class Table extends \Espo\Core\Acl\Table
$roleList = [];
$userRoleList = $this->getUser()->get('portalRoles');
if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
$portalRoleList = $this->getPortal()->get('portalRoles');
if (!(is_array($portalRoleList) || $portalRoleList instanceof \Traversable)) {
throw new Error();
}
foreach ($portalRoleList as $role) {
$roleList[] = $role;
}
@@ -115,11 +121,15 @@ class Table extends \Espo\Core\Acl\Table
{
foreach ($this->getScopeList() as $scope) {
$d = $this->getMetadata()->get('scopes.' . $scope);
if ($d['disabled'] || $d['portalDisabled']) {
$aclTable->$scope = false;
if (!empty($d['disabled']) || !empty($d['portalDisabled'])) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists)
{
}
}
+48 -7
View File
@@ -93,7 +93,7 @@ class Application
$this->getContainer()->get('clientManager')->display();
}
public function runEntryPoint($entryPoint)
public function runEntryPoint($entryPoint, $data = array(), $final = false)
{
if (empty($entryPoint)) {
throw new \Error();
@@ -102,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 = new \Espo\Core\Utils\Auth($this->container, $entryPointManager->checkNotStrictAuth($entryPoint));
$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();
@@ -289,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);
}
}
+3 -1
View File
@@ -200,7 +200,9 @@ class Container
$this->get('entityManager'),
$this->get('user'),
$this->get('acl'),
$this->get('metadata')
$this->get('aclManager'),
$this->get('metadata'),
$this->get('config')
);
}
+6 -6
View File
@@ -64,7 +64,7 @@ class Record extends Base
return $service;
}
public function actionRead($params)
public function actionRead($params, $data, $request)
{
$id = $params['id'];
$entity = $this->getRecordService()->getEntity($id);
@@ -128,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'];
}
@@ -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);
}
}
+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;
}
}
+2 -2
View File
@@ -90,7 +90,7 @@ class EntryPointManager
return $className::$notStrictAuth;
}
public function run($name)
public function run($name, $data = array())
{
$className = $this->getClassName($name);
if (!$className) {
@@ -98,7 +98,7 @@ class EntryPointManager
}
$entryPoint = new $className($this->container);
$entryPoint->run();
$entryPoint->run($data);
}
protected function getClassName($name)
@@ -106,7 +106,5 @@ abstract class Base
$this->container = $container;
}
abstract public function run();
}
+1 -1
View File
@@ -131,7 +131,7 @@ class HookManager
}
return $hook;
}
$GLOBALS['log']->error("Hook class '{$name}' does not exist.");
$GLOBALS['log']->error("Hook class '{$className}' does not exist.");
}
/**
+42 -8
View File
@@ -46,11 +46,19 @@ class Htmlizer
protected $config;
public function __construct(FileManager $fileManager, DateTime $dateTime, Number $number)
protected $acl;
public function __construct(FileManager $fileManager, DateTime $dateTime, Number $number, $acl = null)
{
$this->fileManager = $fileManager;
$this->dateTime = $dateTime;
$this->number = $number;
$this->acl = $acl;
}
protected function getAcl()
{
return $this->acl;
}
protected function formatNumber($value)
@@ -68,20 +76,25 @@ class Htmlizer
return $value;
}
protected function getDataFromEntity(Entity $entity)
protected function getDataFromEntity(Entity $entity, $notLinks = false)
{
$data = $entity->toArray();
$fieldDefs = $entity->getFields();
$fieldList = array_keys($fieldDefs);
$forbidenAttributeList = [];
if ($this->getAcl()) {
$forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read');
}
foreach ($fieldList as $field) {
$type = null;
if (!empty($fieldDefs[$field]['type'])) {
$type = $fieldDefs[$field]['type'];
}
if (in_array($field, $forbidenAttributeList)) continue;
$type = $entity->getAttributeType($field);
if ($type == Entity::DATETIME) {
if (!empty($data[$field])) {
$data[$field] = $this->dateTime->convertSystemDateTime($data[$field]);
@@ -116,6 +129,8 @@ class Htmlizer
$data[$field][$k] = $this->format($data[$field][$k]);
}
}
} else if ($type === Entity::PASSWORD) {
unset($data[$field]);
}
if (array_key_exists($field, $data)) {
@@ -123,6 +138,25 @@ class Htmlizer
}
}
if (!$notLinks) {
$relationDefs = $entity->getRelations();
foreach ($entity->getRelationList() as $relation) {
if (
!empty($relationDefs[$relation]['type'])
&&
($entity->getRelationType($relation) === 'belongsTo' || $entity->getRelationType($relation) === 'belongsToParent')
) {
$relatedEntity = $entity->get($relation);
if (!$relatedEntity) continue;
if ($this->getAcl()) {
if (!$this->getAcl()->check($relatedEntity, 'read')) continue;
}
$data[$relation] = $this->getDataFromEntity($relatedEntity, true);
}
}
}
return $data;
}
-2
View File
@@ -70,7 +70,5 @@ abstract class Base
$this->container = $container;
}
abstract public function run();
}
@@ -40,6 +40,7 @@ class EntityManager extends Base
'port' => $config->get('database.port'),
'dbname' => $config->get('database.dbname'),
'user' => $config->get('database.user'),
'charset' => $config->get('database.charset', 'utf8'),
'password' => $config->get('database.password'),
'metadata' => $this->getContainer()->get('metadata')->getOrmMetadata(),
'repositoryFactoryClassName' => '\\Espo\\Core\\ORM\\RepositoryFactory',
+82 -35
View File
@@ -71,7 +71,7 @@ class Importer
return $this->filtersMatcher;
}
public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [])
public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false)
{
try {
$email = $this->getEntityManager()->getEntity('Email');
@@ -166,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;
@@ -352,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)) {
@@ -395,7 +406,6 @@ class Importer
$contentId = null;
if ($contentDisposition) {
if ($contentDisposition === 'attachment') {
$fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition);
if ($fileName) {
@@ -454,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)
@@ -34,6 +34,7 @@ use Zend\Mail\Header\HeaderInterface;
use Zend\Mime;
use Zend\Mail\Storage\Exception;
use Zend\Mail\Storage\AbstractStorage;
use Zend\Stdlib\ErrorHandler;
class Message extends \Zend\Mail\Storage\Message
{
+3 -6
View File
@@ -94,9 +94,6 @@ class Entity extends \Espo\ORM\Entity
{
$columnsField = $field . 'Columns';
if (!$this->has($columnsField)) {
$this->loadLinkMultipleField($field, true);
}
if (!$this->has($columnsField)) {
return;
}
@@ -125,7 +122,7 @@ class Entity extends \Espo\ORM\Entity
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field, true);
$this->loadLinkMultipleField($field);
} else {
$this->set($idsField, []);
}
@@ -148,7 +145,7 @@ class Entity extends \Espo\ORM\Entity
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field, true);
$this->loadLinkMultipleField($field);
}
}
$valueList = $this->get($idsField);
@@ -166,7 +163,7 @@ class Entity extends \Espo\ORM\Entity
if (!$this->has($idsField)) {
if (!$this->isNew()) {
$this->loadLinkMultipleField($field, true);
$this->loadLinkMultipleField($field);
}
}
+141 -2
View File
@@ -37,6 +37,10 @@ class AclManager extends \Espo\Core\AclManager
{
protected $tableClassName = '\\Espo\\Core\\AclPortal\\Table';
private $mainManager = null;
private $portal = null;
public function getImplementation($scope)
{
if (empty($this->implementationHashMap[$scope])) {
@@ -70,16 +74,42 @@ class AclManager extends \Espo\Core\AclManager
return $this->implementationHashMap[$scope];
}
public function setMainManager($mainManager)
{
$this->mainManager = $mainManager;
}
protected function getMainManager()
{
return $this->mainManager;
}
public function setPortal($portal)
{
$this->portal = $portal;
}
protected function getPortal()
{
if ($this->portal) {
return $this->portal;
}
return $this->getContainer()->get('portal');
}
protected function getTable(User $user)
{
$key = spl_object_hash($user);
$key = $user->id;
if (empty($key)) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManager');
$portal = $this->getContainer()->get('portal');
$portal = $this->getPortal();
$this->tableHashMap[$key] = new $this->tableClassName($user, $portal, $config, $fileManager, $metadata, $fieldManager);
}
@@ -115,5 +145,114 @@ class AclManager extends \Espo\Core\AclManager
return $this->getImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
}
public function getMap(User $user)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getMap($user);
}
return parent::getMap($user);
}
public function getLevel(User $user, $scope, $action)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getLevel($user, $scope, $action);
}
return parent::getLevel($user, $scope, $action);
}
public function get(User $user, $permission)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->get($user, $permission);
}
return parent::get($user, $permission);
}
public function checkReadOnlyTeam(User $user, $permission)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkReadOnlyTeam($user, $permission);
}
return false;
}
public function checkReadOnlyOwn(User $user, $permission)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkReadOnlyOwn($user, $permission);
}
return false;
}
public function check(User $user, $subject, $action = null)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->check($user, $subject, $action);
}
return parent::check($user, $subject, $action);
}
public function checkEntity(User $user, Entity $entity, $action = 'read')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkEntity($user, $entity, $action);
}
return parent::checkEntity($user, $entity, $action);
}
public function checkIsOwner(User $user, Entity $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkIsOwner($user, $entity);
}
return parent::checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkInTeam($user, $entity);
}
return parent::checkInTeam($user, $entity);
}
public function checkScope(User $user, $scope, $action = null)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkScope($user, $scope, $action);
}
return parent::checkScope($user, $scope, $action);
}
public function checkUser(User $user, $permission, User $entity)
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkUser($user, $permission, $entity);
}
return parent::checkUser($user, $permission, $entity);
}
public function getScopeForbiddenAttributeList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
public function getScopeForbiddenFieldList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
protected function checkUserIsNotPortal($user)
{
return !$user->get('isPortalUser');
}
}
+2 -2
View File
@@ -45,6 +45,8 @@ class Application extends \Espo\Core\Application
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) {
@@ -58,8 +60,6 @@ class Application extends \Espo\Core\Application
$this->getContainer()->setPortal($portal);
$GLOBALS['log'] = $this->getContainer()->get('log');
$this->initAutoloads();
}
+16 -1
View File
@@ -38,12 +38,27 @@ class Container extends \Espo\Core\Container
return $className;
}
protected function getServiceMainClassName($name, $default)
{
$metadata = $this->get('metadata');
$className = $metadata->get('app.serviceContainer.classNames.' . $name, $default);
return $className;
}
protected function loadAclManager()
{
$className = $this->getServiceClassName('aclManager', '\\Espo\\Core\\Portal\\AclManager');
return new $className(
$mainClassName = $this->getServiceMainClassName('aclManager', '\\Espo\\Core\\AclManager');
$obj = new $className(
$this->get('container')
);
$objMain = new $mainClassName(
$this->get('container')
);
$obj->setMainManager($objMain);
return $obj;
}
protected function loadAcl()
@@ -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;
+33 -6
View File
@@ -33,6 +33,9 @@ use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Acl;
use \Espo\Core\AclManager;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\Config;
class Base
{
@@ -48,6 +51,8 @@ class Base
protected $metadata;
private $config;
private $seed = null;
private $userTimeZone = null;
@@ -56,13 +61,15 @@ class Base
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config)
{
$this->entityManager = $entityManager;
$this->user = $user;
$this->acl = $acl;
$this->aclManager = $aclManager;
$this->metadata = $metadata;
$this->config = $config;
}
protected function getEntityManager()
@@ -80,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;
@@ -277,7 +294,7 @@ class Base
$foreignEntity = $defs['entity'];
if (empty($foreignEntity)) {
continue;
return;
}
$pathName = lcfirst($query->sanitize($foreignEntity . 'Path'));
@@ -875,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;
@@ -906,6 +926,7 @@ class Base
$part[$item['field'] . '='] = null;
break;
case 'isNotNull':
case 'ever':
$part[$item['field'] . '!='] = null;
break;
case 'isTrue':
@@ -1068,7 +1089,7 @@ class Base
public function hasLeftJoin($leftJoin, &$result)
{
return in_array($leftJoin, $result['leftJoin']);
return in_array($leftJoin, $result['leftJoins']);
}
public function addJoin($join, &$result)
@@ -1162,15 +1183,21 @@ class Base
$d = array();
foreach ($fieldList as $field) {
$expression = $textFilter . '%';
if (
strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
&&
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
(
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
||
$this->getConfig()->get('textFilterUseContainsForVarchar')
)
) {
$d[$field . '*'] = '%' . $textFilter . '%';
$expression = '%' . $textFilter . '%';
} else {
$d[$field . '*'] = $textFilter . '%';
$expression = $textFilter . '%';
}
$d[$field . '*'] = $expression;
}
$result['whereClause'][] = array(
'OR' => $d
@@ -2,7 +2,8 @@
"fields": {
"name": {
"type": "varchar",
"required": true
"required": true,
"trim": true
},
"description": {
"type": "text"
@@ -28,10 +29,11 @@
"assignedUser": {
"type": "link",
"required": true,
"view": "views/fields/user"
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
@@ -2,7 +2,8 @@
"fields": {
"name": {
"type": "varchar",
"required": true
"required": true,
"trim": true
},
"order": {
"type": "int",
@@ -70,10 +70,11 @@
"assignedUser": {
"type": "link",
"required": false,
"view": "views/fields/user"
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple"
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
@@ -32,6 +32,39 @@ namespace Espo\Core\Templates\Services;
class Person extends \Espo\Services\Record
{
protected function getDuplicateWhereClause(Entity $entity, $data = array())
{
$data = array(
'OR' => array(
array(
'firstName' => $entity->get('firstName'),
'lastName' => $entity->get('lastName'),
)
)
);
if (
($entity->get('emailAddress') || $entity->get('emailAddressData'))
&&
($entity->isNew() || $entity->isFieldChanged('emailAddress') || $entity->isFieldChanged('emailAddressData'))
) {
if ($entity->get('emailAddress')) {
$list = [$entity->get('emailAddress')];
}
if ($entity->get('emailAddressData')) {
foreach ($entity->get('emailAddressData') as $row) {
if (!in_array($row->emailAddress, $list)) {
$list[] = $row->emailAddress;
}
}
}
foreach ($list as $emailAddress) {
$data['OR'][] = array(
'emailAddress' => $emailAddress
);
}
}
return $data;
}
}
@@ -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>');
}
}
+2
View File
@@ -163,6 +163,8 @@ class Auth
return false;
}
$user->set('portalId', $this->getPortal()->id);
} else {
$user->loadLinkMultipleField('teams');
}
$this->getEntityManager()->setUser($user);
+24 -2
View File
@@ -41,6 +41,8 @@ class ClientManager
protected $runScript = "app.start();";
protected $basePath = '';
public function __construct(Config $config, ThemeManager $themeManager)
{
$this->config = $config;
@@ -57,6 +59,24 @@ class ClientManager
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)) {
@@ -76,10 +96,12 @@ class ClientManager
foreach ($vars as $key => $value) {
$html = str_replace('{{'.$key.'}}', $value, $html);
}
$html = str_replace('{{cacheTimestamp}}', $this->getConfig()->get('cacheTimestamp', 0), $html);
$html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false' , $html);
$html = str_replace('{{applicationName}}', $this->getConfig()->get('applicationName', 'EspoCRM'), $html);
$html = str_replace('{{cacheTimestamp}}', $this->getCacheTimestamp(), $html);
$html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false', $html);
$html = str_replace('{{stylesheet}}', $this->getThemeManager()->getStylesheet(), $html);
$html = str_replace('{{runScript}}', $runScript , $html);
$html = str_replace('{{basePath}}', $this->basePath , $html);
echo $html;
exit;
+11 -1
View File
@@ -53,6 +53,13 @@ class Config
*/
protected $adminItems = array();
protected $associativeArrayAttributeList = [
'currencyRates',
'database',
'logger',
'defaultPermissions',
];
/**
* Contains content of config
@@ -124,6 +131,9 @@ class Config
}
foreach ($name as $key => $value) {
if (in_array($key, $this->associativeArrayAttributeList) && is_object($value)) {
$value = (array) $value;
}
$this->data[$key] = $value;
if (!$dontMarkDirty) {
$this->changedData[$key] = $value;
@@ -244,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 MAX(execute_time) ASC
";
$sth = $pdo->prepare($query);
$sth->execute();
$duplicateJobs = $sth->fetchAll(PDO::FETCH_ASSOC);
$duplicateJobList = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($duplicateJobs as $row) {
foreach ($duplicateJobList as $row) {
if (!empty($row['scheduled_job_id'])) {
/* no possibility to use limit in update or subqueries */
$query = "SELECT id FROM `job` WHERE scheduled_job_id = '" . $row['scheduled_job_id'] . "'
AND `status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000 ";
$query = "
SELECT id FROM `job`
WHERE
scheduled_job_id = '" . $row['scheduled_job_id'] . "' AND
`status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000
";
$sth = $pdo->prepare($query);
$sth->execute();
$jobIds = $sth->fetchAll(PDO::FETCH_COLUMN);
$update = "UPDATE job SET deleted = 1 WHERE
id IN ('". implode("', '", $jobIds)."') ";
$update = "
UPDATE job
SET deleted = 1
WHERE
id IN ('". implode("', '", $jobIds)."')
";
$sth = $pdo->prepare($update);
$sth->execute();
@@ -235,13 +286,16 @@ class Job
*
* @return void
*/
protected function markJobAttempts()
public function updateFailedJobAttempts()
{
$query = "SELECT * FROM job WHERE
`status` = '" . CronManager::FAILED . "'
AND deleted = 0
AND execute_time <= '".date('Y-m-d H:i:s')."'
AND attempts > 0";
$query = "
SELECT * FROM job
WHERE
`status` = '" . CronManager::FAILED . "' AND
deleted = 0 AND
execute_time <= '".date('Y-m-d H:i:s')."' AND
attempts > 0
";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -255,12 +309,14 @@ class Job
$attempts = $row['attempts'] - 1;
$failedAttempts = $row['failed_attempts'] + 1;
$update = "UPDATE job SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
$update = "
UPDATE job
SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
WHERE
id = '".$row['id']."'
";
$pdo->prepare($update)->execute();
}
@@ -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);
@@ -82,15 +82,11 @@ class Converter
*/
public function process()
{
$GLOBALS['log']->debug('Orm\Converter - Start: orm convertation');
$ormMeta = $this->getOrmConverter()->process();
//save database meta to a file espoMetadata.php
$result = $this->getMetadata()->setOrmMetadata($ormMeta);
$GLOBALS['log']->debug('Orm\Converter - End: orm convertation, result=['.$result.']');
return $result;
}
}
@@ -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',
@@ -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) ) {
+27 -4
View File
@@ -82,6 +82,8 @@ class EntityManager
throw new Error();
}
$name = trim($name);
$normalizedName = Util::normilizeClassName($name);
$contents = "<" . "?" . "php\n\n".
@@ -217,6 +219,15 @@ class EntityManager
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
if (isset($data['textFilterFields'])) {
$entityDefsData = array(
'collection' => array(
'textFilterFields' => $data['textFilterFields']
)
);
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
}
$this->getMetadata()->save();
$this->getLanguage()->save();
@@ -270,9 +281,9 @@ class EntityManager
$linkType = $params['linkType'];
$entity = $params['entity'];
$link = $params['link'];
$link = trim($params['link']);
$entityForeign = $params['entityForeign'];
$linkForeign = $params['linkForeign'];
$linkForeign = trim($params['linkForeign']);
$label = $params['label'];
$labelForeign = $params['labelForeign'];
@@ -320,9 +331,15 @@ class EntityManager
switch ($linkType) {
case 'oneToMany':
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.field.' . $linkForeign)) {
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign)) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Id')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.fields.' . $linkForeign . 'Name')) {
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(
@@ -361,9 +378,15 @@ class EntityManager
);
break;
case 'manyToOne':
if ($this->getMetadata()->get('entityDefs.' . $entity . '.field.' . $link)) {
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link)) {
throw new Conflict('Field ['.$entity.'::'.$link.'] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Id')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Id] already exists.');
}
if ($this->getMetadata()->get('entityDefs.' . $entity . '.fields.' . $link . 'Name')) {
throw new Conflict('Field ['.$entity.'::'.$link.'Name] already exists.');
}
$dataLeft = array(
'fields' => array(
$link => array(
+2 -1
View File
@@ -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;
@@ -99,7 +100,7 @@ class FieldManager
$this->setLabel($name, $fieldDefs['label'], $scope);
}
if (isset($fieldDefs['type']) && ($fieldDefs['type'] == 'enum' || $fieldDefs['type'] == 'phone')) {
if (isset($fieldDefs['type']) && $this->getMetadata()->get(['fields', $fieldDefs['type'], 'translatedOptions'])) {
if (isset($fieldDefs['translatedOptions'])) {
$this->setTranslatedOptions($name, $fieldDefs['translatedOptions'], $scope);
}
+2 -2
View File
@@ -249,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);
@@ -290,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) {
+2 -2
View File
@@ -187,7 +187,7 @@ class Metadata
/**
* Get Metadata
*
* @param string $key
* @param mixed string|array $key
* @param mixed $default
*
* @return array
@@ -308,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;
+29 -23
View File
@@ -394,33 +394,35 @@ class Util
$unsets = (array) $unsets;
}
foreach($unsets as $rootKey => $unsetItem){
foreach ($unsets as $rootKey => $unsetItem) {
$unsetItem = is_array($unsetItem) ? $unsetItem : (array) $unsetItem;
foreach($unsetItem as $unsetSett){
if (!empty($unsetSett)){
$keyItems = explode('.', $unsetSett);
$currVal = isset($content[$rootKey]) ? "\$content['{$rootKey}']" : "\$content";
foreach ($unsetItem as $unsetString) {
if (is_string($rootKey)) {
$unsetString = $rootKey . '.' . $unsetString;
}
$lastKey = array_pop($keyItems);
foreach($keyItems as $keyItem){
$currVal .= "['{$keyItem}']";
}
$keyСhain = explode('.', $unsetString);
$keyChainCount = count($keyСhain) - 1;
$unsetElem = $currVal . "['{$lastKey}']";
$elem = & $content;
for ($i = 0; $i <= $keyChainCount; $i++) {
$evalString = "
if (isset({$unsetElem}) || ( is_array({$currVal}) && array_key_exists('{$lastKey}', {$currVal}) )) {
unset({$unsetElem});
} ";
eval($evalString);
if (is_array($elem) && array_key_exists($keyСhain[$i], $elem)) {
if ($i == $keyChainCount) {
unset($elem[$keyСhain[$i]]);
if ($unsetParentEmptyArray && is_array($elem) && empty($elem)) {
unset($keyСhain[$i]);
$content = static::unsetInArray($content, implode('.', $keyСhain), false);
}
} else if (is_array($elem[$keyСhain[$i]])) {
$elem = & $elem[$keyСhain[$i]];
}
if ($unsetParentEmptyArray) {
$evalString = "
if (is_array({$currVal}) && empty({$currVal})) {
unset({$currVal});
} ";
eval($evalString);
}
}
}
@@ -449,7 +451,7 @@ class Util
* Return values of defined $key.
*
* @param array $array
* @param string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed array|string $key Ex. of key is "entityDefs", "entityDefs.User"
* @param mixed $default
* @return mixed
*/
@@ -459,7 +461,11 @@ class Util
return $array;
}
$keys = explode('.', $key);
if (is_array($key)) {
$keys = $key;
} else {
$keys = explode('.', $key);
}
$lastItem = $array;
foreach($keys as $keyName) {
+6 -1
View File
@@ -32,6 +32,7 @@ return array (
'driver' => 'pdo_mysql',
'host' => 'localhost',
'port' => '',
'charset' => 'utf8',
'dbname' => '',
'user' => '',
'password' => '',
@@ -63,9 +64,11 @@ return array (
'smtpPassword' => '',
'languageList' => [
'en_US',
'cs_CZ',
'de_DE',
'es_ES',
'fr_FR',
'id_ID',
'nl_NL',
'tr_TR',
'ro_RO',
@@ -92,7 +95,7 @@ return array (
'Opportunity',
),
"tabList" => ["Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign", "KnowledgeBaseArticle"],
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case"],
"quickCreateList" => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case", "Email"],
'calendarDefaultEntity' => 'Meeting',
'exportDisabled' => false,
'assignmentEmailNotifications' => false,
@@ -112,6 +115,8 @@ return array (
'authTokenLifetime' => 0,
'authTokenMaxIdleTime' => 120,
'userNameRegularExpression' => '[^a-z0-9\-@_\.\s]',
'addressFormat' => 1,
'displayListViewRecordCount' => true,
'dashboardLayout' => [
(object) [
'name' => 'My Espo',
+5
View File
@@ -51,6 +51,11 @@ class Email extends \Espo\Core\ORM\Entity
}
}
public function isManuallyArchived()
{
return $this->get('status') === 'Archived' && $this->get('createdById') !== 'system';
}
public function addAttachment(\Espo\Entities\Attachment $attachment)
{
if (!empty($this->id)) {
+12 -6
View File
@@ -36,11 +36,13 @@ use \Espo\Core\Exceptions\Error;
class Avatar extends Image
{
public static $authRequired = true;
public static $authRequired = false;
public static $notStrictAuth = true;
private $colorList = [
protected $systemColor = [212,114,155];
protected $colorList = [
[111,168,214],
[237,197,85],
[212,114,155],
@@ -73,7 +75,6 @@ class Avatar extends Image
$userId = $_GET['id'];
$user = $this->getEntityManager()->getEntity('User', $userId);
if (!$user) {
throw new NotFound();
@@ -106,9 +107,14 @@ class Avatar extends Image
header('Cache-Control: max-age=360000, must-revalidate');
header('Content-Type: image/png');
ob_clean();
flush();
echo $identicon->getImageData($userId, $width, $this->getColor($userId));
$hash = $userId;
$color = $this->getColor($userId);
if ($hash === 'system') {
$color = $this->systemColor;
}
$imgContent = $identicon->getImageData($hash, $width, $color);
echo $imgContent;
exit;
}
}
+4 -1
View File
@@ -73,6 +73,9 @@ class Download extends \Espo\Core\EntryPoints\Base
throw new NotFound();
}
$outputFileName = $attachment->get('name');
$outputFileName = str_replace("\"", "\\\"", $outputFileName);
$type = $attachment->get('type');
$disposition = 'attachment';
@@ -84,7 +87,7 @@ class Download extends \Espo\Core\EntryPoints\Base
if ($type) {
header('Content-Type: ' . $type);
}
header("Content-Disposition: " . $disposition . ";filename=\"" . $attachment->get('name') . "\"");
header("Content-Disposition: " . $disposition . ";filename=\"" . $outputFileName . "\"");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
+4 -1
View File
@@ -37,10 +37,12 @@ class Portal extends \Espo\Core\EntryPoints\Base
{
public static $authRequired = false;
public function run()
public function run($data = array())
{
if (!empty($_GET['id'])) {
$id = $_GET['id'];
} else if (!empty($data['id'])) {
$id = $data['id'];
} else {
$id = $this->getConfig()->get('defaultPortalId');
if (!$id) {
@@ -49,6 +51,7 @@ class Portal extends \Espo\Core\EntryPoints\Base
}
$application = new \Espo\Core\Portal\Application($id);
$application->setBasePath($this->getContainer()->get('clientManager')->getBasePath());
$application->runClient();
}
}
@@ -151,7 +151,10 @@ class Notifications extends \Espo\Core\Hooks\Base
$query = $this->getEntityManager()->getQuery();
$sql = "
DELETE FROM `notification`
WHERE related_id = ".$query->quote($entity->id)." AND related_type = ".$query->quote($entity->getEntityType()) ."
WHERE
(related_id = ".$query->quote($entity->id)." AND related_type = ".$query->quote($entity->getEntityType()) .")
OR
(related_parent_id = ".$query->quote($entity->id)." AND related_parent_type = ".$query->quote($entity->getEntityType()) .")
";
$this->getEntityManager()->getPDO()->query($sql);
}
+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.
************************************************************************/
namespace Espo\Modules\Crm\Acl;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Call extends Meeting
{
}
@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Modules\Crm\Acl;
use \Espo\Entities\User;
use \Espo\ORM\Entity;
class Meeting extends \Espo\Core\Acl\Base
{
public function checkEntityRead(User $user, Entity $entity, $data)
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
}
if (is_object($data)) {
if ($data->read === 'own' || $data->read === 'team') {
if ($entity->hasLinkMultipleId('users', $user->id)) {
return true;
}
}
}
return false;
}
}
@@ -45,11 +45,9 @@ class Invitations
protected $language;
protected $fileManager;
protected $ics;
public function __construct($entityManager, $smtpParams, $mailSender, $config, $dateTime, $language, $fileManager)
public function __construct($entityManager, $smtpParams, $mailSender, $config, $dateTime, $language)
{
$this->entityManager = $entityManager;
$this->smtpParams = $smtpParams;
@@ -57,7 +55,6 @@ class Invitations
$this->config = $config;
$this->dateTime = $dateTime;
$this->language = $language;
$this->fileManager = $fileManager;
}
protected function getEntityManager()
@@ -75,17 +72,28 @@ class Invitations
$key = '{'.$field.'}';
switch ($d['type']) {
case 'datetime':
$contents = str_replace($key, $this->dateTime->convertSystemDateTime($entity->get($field)), $contents);
$value = $entity->get($field);
if ($value) {
$value = $this->dateTime->convertSystemDateTime($value);
}
$contents = str_replace($key, $value, $contents);
break;
case 'date':
$contents = str_replace($key, $this->dateTime->convertSystemDate($entity->get($field)), $contents);
$value = $entity->get($field);
if ($value) {
$value = $this->dateTime->convertSystemDate($value);
}
$contents = str_replace($key, $value, $contents);
break;
case 'jsonArray':
break;
case 'jsonObject':
break;
default:
$contents = str_replace($key, $entity->get($field), $contents);
$value = $entity->get($field);
if (is_string($value) || $value === null || is_scalar($value) || is_callable([$value, '__toString'])) {
$contents = str_replace($key, $value, $contents);
}
}
}
@@ -108,7 +116,7 @@ class Invitations
if ($uid) {
$contents = str_replace('{acceptLink}', $siteUrl . '?entryPoint=eventConfirmation&action=accept&uid=' . $uid->get('name'), $contents);
$contents = str_replace('{declineLink}', $siteUrl . '?entryPoint=eventConfirmation&action=decline&uid=' . $uid->get('name'), $contents);
$contents = str_replace('{tentativeLink}', $siteUrl . '?entryPoint=eventConfirmation&action=tentativeLink&uid=' . $uid->get('name'), $contents);
$contents = str_replace('{tentativeLink}', $siteUrl . '?entryPoint=eventConfirmation&action=tentative&uid=' . $uid->get('name'), $contents);
}
return $contents;
}
@@ -136,13 +136,10 @@ class EmailReminder
$email->set('subject', $subject);
$email->set('body', $body);
$email->set('isHtml', true);
$this->getEntityManager()->saveEntity($email);
$emailSender = $this->mailSender;
$emailSender->send($email);
$this->getEntityManager()->removeEntity($email);
}
}
@@ -35,6 +35,9 @@ use \Espo\Core\Exceptions\Error,
class Activities extends \Espo\Core\Controllers\Base
{
protected $maxCalendarRange = 123;
protected $maxSizeLimit = 200;
public function actionListCalendarEvents($params, $data, $request)
{
@@ -49,18 +52,38 @@ class Activities extends \Espo\Core\Controllers\Base
throw new BadRequest();
}
$service = $this->getService('Activities');
$userId = $request->get('userId');
if (!$userId) {
$userId = $this->getUser()->id;
if (strtotime($to) - strtotime($from) > $this->maxCalendarRange * 24 * 3600) {
throw new Forbidden('Too long range.');
}
$service = $this->getService('Activities');
$scopeList = null;
if ($request->get('scopeList') !== null) {
$scopeList = explode(',', $request->get('scopeList'));
}
$userId = $request->get('userId');
$userIdList = $request->get('userIdList');
if ($userIdList) {
$userIdList = explode(',', $userIdList);
$resultList = [];
foreach ($userIdList as $userId) {
$userResultList = $service->getEvents($userId, $from, $to, $scopeList);
foreach ($userResultList as $item) {
$item['userId'] = $userId;
$resultList[] = $item;
}
}
return $resultList;
} else {
if (!$userId) {
$userId = $this->getUser()->id;
}
}
return $service->getEvents($userId, $from, $to, $scopeList);
}
@@ -76,6 +99,13 @@ class Activities extends \Espo\Core\Controllers\Base
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
if (empty($maxSize)) {
$maxSize = $this->maxSizeLimit;
}
if ($maxSize > $this->maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
}
return $service->getUpcomingActivities($userId, array(
'offset' => $offset,
'maxSize' => $maxSize
@@ -131,6 +161,13 @@ class Activities extends \Espo\Core\Controllers\Base
$sortBy = $request->get('sortBy');
$where = $request->get('where');
if (empty($maxSize)) {
$maxSize = $this->maxSizeLimit;
}
if ($maxSize > $this->maxSizeLimit) {
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
}
$scope = null;
if (is_array($where) && !empty($where[0]) && $where[0] !== 'false') {
$scope = $where[0];
@@ -31,5 +31,13 @@ namespace Espo\Modules\Crm\Controllers;
class KnowledgeBaseArticle extends \Espo\Core\Controllers\Record
{
public function postActionGetCopiedAttachments($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
$id = $data['id'];
return $this->getRecordService()->getCopiedAttachments($id);
}
}
@@ -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\Modules\Crm\Controllers;
@@ -37,52 +37,52 @@ class Opportunity extends \Espo\Core\Controllers\Record
public function actionReportByLeadSource($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own') {
if (!$level || $level == 'own' || $level == 'no') {
throw new Forbidden();
}
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
return $this->getService('Opportunity')->reportByLeadSource($dateFrom, $dateTo);
}
public function actionReportByStage($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own') {
if (!$level || $level == 'own' || $level == 'no') {
throw new Forbidden();
}
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
return $this->getService('Opportunity')->reportByStage($dateFrom, $dateTo);
}
public function actionReportSalesByMonth($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own') {
if (!$level || $level == 'own' || $level == 'no') {
throw new Forbidden();
}
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
return $this->getService('Opportunity')->reportSalesByMonth($dateFrom, $dateTo);
return $this->getService('Opportunity')->reportSalesByMonth($dateFrom, $dateTo);
}
public function actionReportSalesPipeline($params, $data, $request)
{
$level = $this->getAcl()->getLevel('Opportunity', 'read');
if (!$level || $level == 'own') {
if (!$level || $level == 'own' || $level == 'no') {
throw new Forbidden();
}
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
return $this->getService('Opportunity')->reportSalesPipeline($dateFrom, $dateTo);
}
}
@@ -81,6 +81,17 @@ class CampaignTrackOpened extends \Espo\Core\EntryPoints\Base
}
$campaignService = $this->getServiceFactory()->create('Campaign');
$campaignService->logOpened($campaignId, $queueItemId, $target, null, $queueItem->get('isTest'));
header('Content-Type: image/png');
$img = imagecreatetruecolor(1, 1);
imagesavealpha($img, true);
$color = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagefill($img, 0, 0, $color);
imagepng($img);
imagecolordeallocate($background);
imagedestroy( $tt_image );
}
}
@@ -29,20 +29,61 @@
namespace Espo\Modules\Crm\Jobs;
use \Espo\Core\Exceptions;
use \Espo\Core\Exceptions\Error;
class CheckEmailAccounts extends \Espo\Core\Jobs\Base
{
public function run()
public function run($data, $targetId)
{
if (!$targetId) {
throw new Error();
}
$service = $this->getServiceFactory()->create('EmailAccount');
$collection = $this->getEntityManager()->getRepository('EmailAccount')->where(array('status' => 'Active'))->find();
$entity = $this->getEntityManager()->getEntity('EmailAccount', $targetId);
if (!$entity) {
throw new Error("Job CheckEmailAccounts '".$targetId."': EmailAccount does not exist.");
};
if ($entity->get('status') !== 'Active') {
throw new Error("Job CheckEmailAccounts '".$targetId."': EmailAccount is not active.");
}
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {
throw new Error('Job CheckEmailAccounts '.$entity->id.': [' . $e->getCode() . '] ' .$e->getMessage());
}
return true;
}
public function prepare($data, $executeTime)
{
$collection = $this->getEntityManager()->getRepository('EmailAccount')->where(array(
'status' => 'Active'
))->find();
foreach ($collection as $entity) {
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {
$GLOBALS['log']->error('Job CheckEmailAccounts '.$entity->id.': [' . $e->getCode() . '] ' .$e->getMessage());
}
$running = $this->getEntityManager()->getRepository('Job')->where(array(
'scheduledJobId' => $data['id'],
'status' => ['Running', 'Pending'],
'targetType' => 'EmailAccount',
'targetId' => $entity->id
))->findOne();
if ($running) continue;
$job = $this->getEntityManager()->getEntity('Job');
$jobEntity = $this->getEntityManager()->getEntity('Job');
$jobEntity->set(array(
'name' => $data['name'],
'scheduledJobId' => $data['id'],
'executeTime' => $executeTime,
'method' => 'CheckEmailAccounts',
'targetType' => 'EmailAccount',
'targetId' => $entity->id
));
$this->getEntityManager()->saveEntity($jobEntity);
}
return true;
@@ -45,7 +45,7 @@ class SendEmailReminders extends \Espo\Core\Jobs\Base
$collection = $this->getEntityManager()->getRepository('Reminder')->where(array(
'type' => 'Email',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'startAt>' => $nowShifted
))->find();
if (!empty($collection)) {
@@ -33,7 +33,7 @@ use Espo\ORM\Entity;
class Account extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, array $options)
public function afterSave(Entity $entity, array $options = array())
{
parent::afterSave($entity, $options);
@@ -33,14 +33,14 @@ use Espo\ORM\Entity;
class CaseObj extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, array $options)
public function afterSave(Entity $entity, array $options = array())
{
$result = parent::afterSave($entity, $options);
$this->handleAfterSaveContacts($entity, $options);
return $result;
}
protected function handleAfterSaveContacts(Entity $entity, array $options)
protected function handleAfterSaveContacts(Entity $entity, array $options = array())
{
$contactIdChanged = $entity->has('contactId') && $entity->get('contactId') != $entity->getFetched('contactId');
@@ -47,7 +47,7 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
";
}
public function afterSave(Entity $entity, array $options)
public function afterSave(Entity $entity, array $options = array())
{
$result = parent::afterSave($entity, $options);
$this->handleAfterSaveAccounts($entity, $options);
@@ -59,7 +59,7 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
return $result;
}
protected function handleAfterSaveAccounts(Entity $entity, array $options)
protected function handleAfterSaveAccounts(Entity $entity, array $options = array())
{
$accountIdChanged = $entity->has('accountId') && $entity->get('accountId') != $entity->getFetched('accountId');
$titleChanged = $entity->has('title') && $entity->get('title') != $entity->getFetched('title');
@@ -33,7 +33,7 @@ use Espo\ORM\Entity;
class Lead extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, array $options)
public function afterSave(Entity $entity, array $options = array())
{
parent::afterSave($entity, $options);
@@ -33,7 +33,7 @@ use Espo\ORM\Entity;
class Meeting extends \Espo\Core\ORM\Repositories\RDB
{
protected function beforeSave(Entity $entity, array $options)
protected function beforeSave(Entity $entity, array $options = array())
{
parent::beforeSave($entity, $options);
@@ -83,7 +83,7 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
$usersColumns = new \StdClass();
}
if ($usersColumns instanceof \StdClass) {
if (!($usersColumns->$currentUserId instanceof \StdClass)) {
if (empty($usersColumns->$currentUserId) || !($usersColumns->$currentUserId instanceof \StdClass)) {
$usersColumns->$currentUserId = new \StdClass();
}
if (empty($usersColumns->$currentUserId->status)) {
@@ -93,21 +93,40 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
}
}
}
if (!$entity->isNew()) {
if ($entity->isFieldChanged('dateStart') && $entity->isFieldChanged('dateStart') && !$entity->isFieldChanged('dateEnd')) {
$dateEndPrevious = $entity->getFetched('dateEnd');
$dateStartPrevious = $entity->getFetched('dateStart');
if ($dateStartPrevious && $dateEndPrevious) {
$dtStart = new \DateTime($dateStartPrevious);
$dtEnd = new \DateTime($dateEndPrevious);
$dt = new \DateTime($entity->get('dateStart'));
if ($dtStart && $dtEnd && $dt) {
$duration = ($dtEnd->getTimestamp() - $dtStart->getTimestamp());
$dt->modify('+' . $duration . ' seconds');
$dateEnd = $dt->format('Y-m-d H:i:s');
$entity->set('dateEnd', $dateEnd);
}
}
}
}
}
public function getEntityReminders(Entity $entity)
public function getEntityReminderList(Entity $entity)
{
$pdo = $this->getEntityManager()->getPDO();
$reminders = array();
$reminderList = [];
$sql = "
SELECT id, `seconds`, `type`
SELECT DISTINCT `seconds`, `type`
FROM `reminder`
WHERE
`entity_type` = ".$pdo->quote($entity->getEntityType())." AND
`entity_id` = ".$pdo->quote($entity->id)." AND
`deleted` = 0
ORDER BY `seconds` ASC
ORDER BY `seconds` ASC
";
$sth = $pdo->prepare($sql);
@@ -118,19 +137,20 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
$o = new \StdClass();
$o->seconds = intval($row['seconds']);
$o->type = $row['type'];
$reminders[] = $o;
$reminderList[] = $o;
}
return $reminders;
return $reminderList;
}
protected function afterSave(Entity $entity, array $options)
protected function afterSave(Entity $entity, array $options = array())
{
parent::afterSave($entity, $options);
if (
$entity->isNew() ||
$entity->isFieldChanged('assignedUserId') ||
$entity->isFieldChanged('usersIds') ||
$entity->isFieldChanged('dateStart') ||
$entity->has('reminders')
) {
@@ -139,9 +159,9 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
$reminderTypeList = $this->getMetadata()->get('entityDefs.Reminder.fields.type.options');
if (!$entity->has('reminders')) {
$reminders = $this->getEntityReminders($entity);
$reminderList = $this->getEntityReminderList($entity);
} else {
$reminders = $entity->get('reminders');
$reminderList = $entity->get('reminders');
}
if (!$entity->isNew()) {
@@ -155,32 +175,28 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
$pdo->query($sql);
}
if (empty($reminders) || !is_array($reminders)) return;
if (empty($reminderList) || !is_array($reminderList)) return;
$entityType = $entity->getEntityName();
$dateStart = $entity->get('dateStart');
$assignedUserId = $entity->get('assignedUserId');
if (!$dateStart || !$assignedUserId) {
if (!$dateStart) {
$e = $this->get($entity->id);
if ($e) {
$dateStart = $e->get('dateStart');
$assignedUserId = $e->get('assignedUserId');
}
}
if (!$dateStart || !$assignedUserId) {
return;
}
$userIdList = $entity->getLinkMultipleIdList('users');
if (!$dateStart) return;
if (empty($userIdList)) return;
$dateStartObj = new \DateTime($dateStart);
if (!$dateStartObj) {
return;
}
if (!$dateStartObj) return;
foreach ($reminders as $item) {
foreach ($reminderList as $item) {
$remindAt = clone $dateStartObj;
$seconds = intval($item->seconds);
$type = $item->type;
@@ -189,25 +205,26 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB
$remindAt->sub(new \DateInterval('PT' . $seconds . 'S'));
$id = uniqid();
$sql = "
INSERT
INTO `reminder`
(id, entity_id, entity_type, `type`, user_id, remind_at, start_at, `seconds`)
VALUES (
".$pdo->quote($id).",
".$pdo->quote($entity->id).",
".$pdo->quote($entityType).",
".$pdo->quote($type).",
".$pdo->quote($assignedUserId).",
".$pdo->quote($remindAt->format('Y-m-d H:i:s')).",
".$pdo->quote($dateStart).",
".$pdo->quote($seconds)."
)
";
$pdo->query($sql);
foreach ($userIdList as $userId) {
$id = uniqid(true);
$sql = "
INSERT
INTO `reminder`
(id, entity_id, entity_type, `type`, user_id, remind_at, start_at, `seconds`)
VALUES (
".$pdo->quote($id).",
".$pdo->quote($entity->id).",
".$pdo->quote($entityType).",
".$pdo->quote($type).",
".$pdo->quote($userId).",
".$pdo->quote($remindAt->format('Y-m-d H:i:s')).",
".$pdo->quote($dateStart).",
".$pdo->quote($seconds)."
)
";
$pdo->query($sql);
}
}
}
}
@@ -33,6 +33,16 @@ use Espo\ORM\Entity;
class Opportunity extends \Espo\Core\ORM\Repositories\RDB
{
public function beforeSave(Entity $entity, array $options = array())
{
parent::beforeSave($entity, $options);
if ($entity->isNew()) {
if (!$entity->has('probability') && $entity->get('stage')) {
$probability = $this->getMetadata()->get('entityDefs.Opportunity.probabilityMap.' . $entity->get('stage'), 0);
$entity->set('probability', $probability);
}
}
}
}
@@ -64,7 +64,7 @@ class Task extends \Espo\Core\ORM\Repositories\RDB
return null;
}
protected function beforeSave(Entity $entity, array $options)
protected function beforeSave(Entity $entity, array $options = array())
{
parent::beforeSave($entity, $options);
@@ -0,0 +1,73 @@
{
"fields": {
"name": "Název",
"emailAddress": "Email",
"website": "Web",
"phoneNumber": "Telefon",
"billingAddress": "Fakturační adresa",
"shippingAddress": "Doručovací adresa",
"description": "Popis",
"sicCode": "IČ",
"industry": "Průmysl",
"type": "Typ",
"contactRole": "Pozice",
"campaign": "Kampaň"
},
"links": {
"contacts": "Kontakty",
"opportunities": "Případy",
"cases": "Události",
"documents": "Dokumenty",
"meetingsPrimary": "Schůzky (rozšířené)",
"callsPrimary": "Volání (rozšířené)",
"tasksPrimary": "Úkoly (rozšířené)",
"emailsPrimary": "Emaily (rozšířené)",
"targetLists": "Cílové seznamy",
"campaignLogRecords": "Log kampaně",
"campaign": "Kampaň"
},
"options": {
"type": {
"Customer": "Zákazník",
"Investor": "Investor",
"Partner": "Partner",
"Reseller": "Prodejce"
},
"industry": {
"Agriculture": "Zeměděltví",
"Advertising": "Reklama",
"Apparel & Accessories": "Oblečení a doplňky",
"Automotive": "Automotive",
"Banking": "Bankovnictví",
"Biotechnology": "Biotechnologie",
"Chemical": "Chemie",
"Computer": "Počítače",
"Education": "Vzdělání",
"Electronics": "Elektronika",
"Entertainment & Leisure": "Zábava a volný čas",
"Finance": "Finance",
"Food & Beverage": "Stravování a nápoje",
"Grocery": "Potraviny",
"Insurance": "Pojištění",
"Legal": "Právo",
"Publishing": "Vydavatelství",
"Real Estate": "Nemovitosti",
"Service": "Služby",
"Sports": "Sport",
"Software": "Software",
"Technology": "Technologie",
"Telecommunications": "Telekomunikace",
"Television": "TV",
"Transportation": "Doprava",
"Venture Capital": "Rizikový kapitál"
}
},
"labels": {
"Create Account": "Vytvořit organizaci",
"Copy Billing": "Zkopírovat fakturační údaje"
},
"presetFilters": {
"customers": "Zákazníci",
"partners": "Partneři"
}
}
@@ -0,0 +1,6 @@
{
"layouts": {
"detailConvert": "Konvertovat stopu",
"listForAccount": "Seznam (pro organizace)"
}
}
@@ -0,0 +1,13 @@
{
"modes": {
"month": "Měsíc",
"week": "Týden",
"day": "Den",
"agendaWeek": "Týden",
"agendaDay": "Den"
},
"labels": {
"Today": "Dnes",
"Create": "Vytvořit"
}
}
@@ -0,0 +1,51 @@
{
"fields": {
"name": "Název",
"parent": "Rodič",
"status": "Status",
"dateStart": "Datum zahájení",
"dateEnd": "Datum ukončení",
"direction": "Směr",
"duration": "Trvání",
"description": "Popis",
"users": "Uživatelé",
"contacts": "Kontakty",
"leads": "Stopy",
"reminders": "Připomenutí",
"account": "Organizace"
},
"links": {
},
"options": {
"status": {
"Planned": "Plánovaný",
"Held": "Uskutečněný",
"Not Held": "Neuskutečněný"
},
"direction": {
"Outbound": "Odchozí",
"Inbound": "Příchozí"
},
"acceptanceStatus": {
"None": "None",
"Accepted": "Přijatý",
"Declined": "Odmítnutý",
"Tentative": "Předběžný"
}
},
"massActions": {
"setHeld": "Nastavit jako uskutečněný",
"setNotHeld": "Nastavit jako neuskutečněný"
},
"labels": {
"Create Call": "Vytvořit volání",
"Set Held": "Nastavit jako uskutečněný",
"Set Not Held": "Nastavit jako neuskutečněný",
"Send Invitations": "Poslat pozvánky"
},
"presetFilters": {
"planned": "Plánovaný",
"held": "Uskutečněný",
"todays": "Dnešní"
}
}
@@ -0,0 +1,55 @@
{
"fields": {
"name": "Název",
"description": "Popis",
"status": "Status",
"type": "Typ",
"startDate": "Datum zahájení",
"endDate": "Datum ukončení",
"targetLists": "Cílové seznamy",
"sentCount": "Poslané",
"openedCount": "Otevřené",
"clickedCount": "Kliknuté",
"optedOutCount": "Odhlášené",
"bouncedCount": "Odmítnuté",
"hardBouncedCount": "Tvrdě odmítnuté",
"softBouncedCount": "Měkce odmítnuté",
"leadCreatedCount": "Vytvořených stop",
"revenue": "Příjem",
"revenueConverted": "příjem (konvertováno)"
},
"links": {
"targetLists": "Cílové seznamy",
"accounts": "Organizace",
"contacts": "Kontakty",
"leads": "Stopy",
"opportunities": "Případy",
"campaignLogRecords": "Log"
},
"options": {
"type": {
"Email": "Email",
"Web": "Web",
"Television": "TV",
"Radio": "Rádio",
"Newsletter": "Newsletter",
"Mail": "Email"
},
"status": {
"Planning": "Plánované",
"Active": "Aktivní",
"Inactive": "Neaktivní",
"Complete": "Kompletní"
}
},
"labels": {
"Create Campaign": "Vytvořit kampaň",
"Target Lists": "Cílové seznamy",
"Statistics": "Statistiky",
"hard": "těžký",
"soft": "měkký"
},
"presetFilters": {
"active": "Aktivní"
}
}
@@ -0,0 +1,24 @@
{
"fields": {
"action": "Akce",
"actionDate": "Datum",
"data": "Data",
"campaign": "Kampaň",
"parent": "Cíl",
"object": "Objekt",
"application": "Aplikace"
},
"options": {
"action": {
"Sent": "Poslané",
"Opened": "Otevřené",
"Opted Out": "Odhlášené",
"Bounced": "Odmítnuté",
"Clicked": "Kliknuté",
"Lead Created": "Vytvořeno stop"
}
},
"labels": {
"All": "Vše"
}
}
@@ -0,0 +1,42 @@
{
"fields": {
"name": "Název",
"number": "Číslo",
"status": "Status",
"account": "Organizace",
"contact": "Kontakt",
"priority": "Priorita",
"type": "Typ",
"description": "Popis"
},
"links": {
},
"options": {
"status": {
"New": "Nový",
"Assigned": "Přiřazený",
"Pending": "Čekající",
"Closed": "Uzavřený",
"Rejected": "Odmítnutý",
"Duplicate": "Duplikovaný"
},
"priority" : {
"Low": "Malá",
"Normal": "Normální",
"High": "Vysoká",
"Urgent": "Urgentní"
},
"type": {
"Question": "Otázka",
"Incident": "Incident",
"Problem": "Problém"
}
},
"labels": {
"Create Case": "Vytvořit událost"
},
"presetFilters": {
"open": "Otevřený",
"closed": "Zavřený"
}
}
@@ -0,0 +1,37 @@
{
"fields": {
"name": "Název",
"emailAddress": "Email",
"title": "Pozice",
"account": "Organizace",
"accounts": "Organizace",
"phoneNumber": "Telefon",
"accountType": "Typ organizace",
"doNotCall": "Nevolat",
"address": "Adresa",
"opportunityRole": "Pozice vzhledem k případu",
"accountRole": "Pozice",
"description": "Popis",
"campaign": "Kampaň",
"targetLists": "Cílové seznamy",
"targetList": "Cílový seznam"
},
"links": {
"opportunities": "Případy",
"cases": "Události",
"targetLists": "Cílové seznamy",
"campaignLogRecords": "Log kampaně",
"campaign": "Kampaň"
},
"labels": {
"Create Contact": "Vytvořit kontakt"
},
"options": {
"opportunityRole": {
"": "--",
"Decision Maker": "Rozhoduje",
"Evaluator": "Hodnotí",
"Influencer": "Má vliv"
}
}
}
@@ -0,0 +1,42 @@
{
"labels": {
"Create Document": "Vytvořit dokument",
"Details": "Detaily"
},
"fields": {
"name": "Název",
"status": "Status",
"file": "Soubor",
"type": "Typ",
"source": "Zdroj",
"publishDate": "Datum publikace",
"expirationDate": "Datum expirace",
"description": "Popis",
"accounts": "Organizace",
"folder": "Složka"
},
"links": {
"accounts": "Organizace",
"opportunities": "Případy",
"folder": "Složka"
},
"options": {
"status": {
"Active": "Aktivní",
"Draft": "Draft",
"Expired": "Expirovaný",
"Canceled": "Zrušený"
},
"type": {
"": "-",
"Contract": "Kontrakt",
"NDA": "NDA",
"EULA": "EULA",
"License Agreement": "Licenční smlouva"
}
},
"presetFilters": {
"active": "Aktivní",
"draft": "Draft"
}
}
@@ -0,0 +1,10 @@
{
"labels": {
"Create DocumentFolder": "Vytvořit složku",
"Manage Categories": "Spravovat složky",
"documents": "Dokumenty"
},
"links": {
"documents": "Dokumenty"
}
}
@@ -0,0 +1,8 @@
{
"labels": {
"Create Lead": "Vytvořit stopu",
"Create Contact": "Vytvořit kontakt",
"Create Task": "Vytvořit úkol",
"Create Case": "Vytvořit událost"
}
}
@@ -0,0 +1,100 @@
{
"scopeNames": {
"Account": "Organizace",
"Contact": "Kontakt",
"Lead": "Stopa",
"Target": "Cíl",
"Opportunity": "Případ",
"Meeting": "Schůzka",
"Calendar": "Kalendář",
"Call": "Volání",
"Task": "Úkol",
"Case": "Událost",
"Document": "Dokument",
"DocumentFolder": "Složka dokumentů",
"Campaign": "Kampaň",
"TargetList": "Cílový seznam"
},
"scopeNamesPlural": {
"Account": "Organizace",
"Contact": "Kontakty",
"Lead": "Stopy",
"Target": "Cíle",
"Opportunity": "Případy",
"Meeting": "Schůzky",
"Calendar": "Kalendáře",
"Call": "Volání",
"Task": "Úkoly",
"Case": "Události",
"Document": "Dokumenty",
"DocumentFolder": "Složky dokumentů",
"Campaign": "Kampaně",
"TargetList": "Cílové seznamy"
},
"dashlets": {
"Leads": "Moje stopy",
"Opportunities": "Moje případy",
"Tasks": "Moje úkoly",
"Cases": "Moje události",
"Calendar": "Kalendář",
"Calls": "Moje volání",
"Meetings": "Moje schůzky",
"OpportunitiesByStage": "Případy podle stavu",
"OpportunitiesByLeadSource": "Případy podle zdroje",
"SalesByMonth": "Prodeje za měsíc",
"SalesPipeline": "Prodejní pipeline"
},
"labels": {
"Create InboundEmail": "Vytvořit příchodí email",
"Activities": "Aktivity",
"History": "Historie",
"Attendees": "Účastníci",
"Schedule Meeting": "Plánovaná schůzka",
"Schedule Call": "Plánované volání",
"Compose Email": "Sestavit email",
"Log Meeting": "Zapsat schůzku",
"Log Call": "Zapsat volání",
"Archive Email": "Archivovat email",
"Create Task": "Vytvořit úkol",
"Tasks": "Úkoly"
},
"fields": {
"billingAddressCity": "Město",
"billingAddressCountry": "Země",
"billingAddressPostalCode": "PSČ",
"billingAddressState": "Kraj",
"billingAddressStreet": "Ulice",
"addressCity": "Město",
"addressStreet": "Ulice",
"addressCountry": "Země",
"addressState": "Kraj",
"addressPostalCode": "PSČ",
"shippingAddressCity": "Město (doručovací)",
"shippingAddressStreet": "Ulice (doručovací)",
"shippingAddressCountry": "Země (doručovací)",
"shippingAddressState": "Kraj (doručovací)",
"shippingAddressPostalCode": "PSČ (doručovací)"
},
"links": {
"contacts": "Kontakty",
"opportunities": "Případy",
"leads": "Stopy",
"meetings": "Schůzky",
"calls": "Volání",
"tasks": "Úkoly",
"emails": "Emaily",
"accounts": "Organizace",
"cases": "Události",
"documents": "Dokumenty",
"account": "Organizace",
"opportunity": "Případ",
"contact": "Kontakt",
"parent": "Rodič"
},
"options": {
"reminderTypes": {
"Popup": "Vyskakovací",
"Email": "Email"
}
}
}
@@ -0,0 +1,58 @@
{
"labels": {
"Converted To": "Konvertováno do",
"Create Lead": "Vytvořit stopu",
"Convert": "Konvertovat"
},
"fields": {
"name": "Název",
"emailAddress": "Email",
"title": "Pozice",
"website": "Web",
"phoneNumber": "Telefon",
"accountName": "Název organizace",
"doNotCall": "Nevolat",
"address": "Adresa",
"status": "Status",
"source": "Zdroj",
"opportunityAmount": "Částka případu",
"opportunityAmountConverted": "Částka případu (konvertováno)",
"description": "Popis",
"createdAccount": "Organizace",
"createdContact": "Kontakt",
"createdOpportunity": "Případ",
"campaign": "Kampaň",
"targetLists": "Cílové seznamy",
"targetList": "Cílový seznam"
},
"links": {
"targetLists": "Cílové seznamy",
"campaignLogRecords": "Log kampaně",
"campaign": "Kampaň"
},
"options": {
"status": {
"New": "Nový",
"Assigned": "Přiřazený",
"In Process": "V procesu",
"Converted": "Konvertovaný",
"Recycled": "Recyklovaný",
"Dead": "Mrtvý"
},
"source": {
"": "-",
"Call": "Volání",
"Email": "Email",
"Existing Customer": "Existující zákazník",
"Partner": "Partner",
"Public Relations": "Veřejné vztahy",
"Web Site": "Web",
"Campaign": "Kampaň",
"Other": "Ostatní"
}
},
"presetFilters": {
"active": "Aktivní",
"converted": "Konvertovaný"
}
}
@@ -0,0 +1,48 @@
{
"fields": {
"name": "Název",
"parent": "Rodič",
"status": "Status",
"dateStart": "Datum zahájení",
"dateEnd": "Datum ukončení",
"duration": "Trvání",
"description": "Popis",
"users": "Uživatelé",
"contacts": "Kontakty",
"leads": "Stopy",
"reminders": "Připomenutí",
"account": "Organizace"
},
"links": {
},
"options": {
"status": {
"Planned": "Planovaný",
"Held": "Uskutečněný",
"Not Held": "Neuskutečněný"
},
"acceptanceStatus": {
"None": "-",
"Accepted": "Přijatý",
"Declined": "Odmítnutý",
"Tentative": "Předběžný"
}
},
"massActions": {
"setHeld": "Nastavit jako uskutečněný",
"setNotHeld": "Nastavit jako neuskutečněný"
},
"labels": {
"Create Meeting": "Vytvořit schůzku",
"Set Held": "Nastavit jako uskutečněný",
"Set Not Held": "Nastavit jako neuskutečněný",
"Send Invitations": "Odeslat pozvánky",
"on time": "na čas",
"before": "před"
},
"presetFilters": {
"planned": "Plánovaný",
"held": "Uskutečněný",
"todays": "Dnešní"
}
}

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