diff --git a/application/Espo/Hooks/Note/Notifications.php b/application/Espo/Hooks/Note/Notifications.php index 76f3f0f43c..ec81a055d3 100644 --- a/application/Espo/Hooks/Note/Notifications.php +++ b/application/Espo/Hooks/Note/Notifications.php @@ -60,13 +60,26 @@ class Notifications extends \Espo\Core\Hooks\Base return $mentionedUserList; } - protected function getSubscriberIdList($parentType, $parentId) + protected function getSubscriberIdList($parentType, $parentId, $isInternal = false) { $pdo = $this->getEntityManager()->getPDO(); - $sql = " - SELECT user_id AS userId - FROM subscription - WHERE entity_id = " . $pdo->quote($parentId) . " AND entity_type = " . $pdo->quote($parentType); + + if (!$isInternal) { + $sql = " + SELECT user_id AS userId + FROM subscription + WHERE entity_id = " . $pdo->quote($parentId) . " AND entity_type = " . $pdo->quote($parentType) . " + "; + } else { + $sql = " + SELECT subscription.user_id AS userId + FROM subscription + JOIN user ON user.id = subscription.user_id + WHERE + entity_id = " . $pdo->quote($parentId) . " AND entity_type = " . $pdo->quote($parentType) . " AND + user.is_portal_user = 0 + "; + } $sth = $pdo->prepare($sql); $sth->execute(); $userIdList = []; @@ -89,9 +102,9 @@ class Notifications extends \Espo\Core\Hooks\Base $userIdList = []; if ($parentType && $parentId) { - $userIdList = array_merge($userIdList, $this->getSubscriberIdList($parentType, $parentId)); + $userIdList = array_merge($userIdList, $this->getSubscriberIdList($parentType, $parentId, $entity->get('isInternal'))); if ($superParentType && $superParentId) { - $userIdList = array_merge($userIdList, $this->getSubscriberIdList($superParentType, $superParentId)); + $userIdList = array_merge($userIdList, $this->getSubscriberIdList($superParentType, $superParentId, $entity->get('isInternal'))); } } else { $targetType = $entity->get('targetType'); diff --git a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json index 23d047d1ed..9abaae85f4 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json +++ b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json @@ -48,5 +48,6 @@ "boolFilterList": ["onlyMy"], "selectDefaultFilters": { "filter": "open" - } + }, + "allowInternalNotes": true } diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 1dbdca626b..85c9e8a27d 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -224,7 +224,8 @@ "writeMessageToSelf": "Write a message to self", "typeAndPressEnter": "Type & press enter", "checkForNewNotifications": "Check for new notifications", - "checkForNewNotes": "Check for new Stream entries" + "checkForNewNotes": "Check for new Stream entries", + "internalPost": "Post will be seen only by internal users" }, "boolFilters": { "onlyMy": "Only My", diff --git a/application/Espo/Resources/metadata/app/aclPortal.json b/application/Espo/Resources/metadata/app/aclPortal.json index 82312340c5..de969bcc44 100644 --- a/application/Espo/Resources/metadata/app/aclPortal.json +++ b/application/Espo/Resources/metadata/app/aclPortal.json @@ -63,6 +63,10 @@ }, "Attachment": { "parent": false + }, + "Note": { + "isInternal": false, + "isGlobal": false } } }, diff --git a/application/Espo/Resources/metadata/entityDefs/Note.json b/application/Espo/Resources/metadata/entityDefs/Note.json index 4d15836439..9545ad4ddb 100644 --- a/application/Espo/Resources/metadata/entityDefs/Note.json +++ b/application/Espo/Resources/metadata/entityDefs/Note.json @@ -50,6 +50,9 @@ "type": "jsonArray", "notStorable": true }, + "isInternal": { + "type": "bool" + }, "isToSelf": { "type": "bool" }, diff --git a/application/Espo/Services/Stream.php b/application/Espo/Services/Stream.php index 10e1849f93..177f0c9355 100644 --- a/application/Espo/Services/Stream.php +++ b/application/Espo/Services/Stream.php @@ -300,7 +300,7 @@ class Stream extends \Espo\Core\Services\Base $selectParamsList = []; - $selectParamsList[] = array( + $selectParamsSubscription = array( 'select' => $select, 'leftJoins' => ['createdBy'], 'customJoin' => " @@ -318,7 +318,15 @@ class Stream extends \Espo\Core\Services\Base 'order' => 'DESC' ); - $selectParamsList[] = array( + if ($user->get('isPortalUser')) { + $selectParamsSubscription['whereClause'][] = array( + 'isInternal' => false + ); + } + + $selectParamsList[] = $selectParamsSubscription; + + $selectParamsSubscriptionSuper = array( 'select' => $select, 'leftJoins' => ['createdBy'], 'customJoin' => " @@ -342,6 +350,14 @@ class Stream extends \Espo\Core\Services\Base 'order' => 'DESC' ); + if ($user->get('isPortalUser')) { + $selectParamsSubscriptionSuper['whereClause'][] = array( + 'isInternal' => false + ); + } + + $selectParamsList[] = $selectParamsSubscriptionSuper; + $selectParamsList[] = array( 'select' => $select, 'leftJoins' => ['createdBy'], @@ -594,6 +610,12 @@ class Stream extends \Espo\Core\Services\Base } } + if ($this->getUser()->get('isPortalUser')) { + $where[] = array( + 'isInternal' => false + ); + } + $collection = $this->getEntityManager()->getRepository('Note')->find(array( 'whereClause' => $where, 'offset' => $params['offset'], diff --git a/client/res/templates/stream/panel.tpl b/client/res/templates/stream/panel.tpl index 03de03b4e4..8facc345c3 100644 --- a/client/res/templates/stream/panel.tpl +++ b/client/res/templates/stream/panel.tpl @@ -3,6 +3,12 @@
+ + {{#if allowInternalNotes}} + + + + {{/if}}
{{{attachments}}} diff --git a/client/src/views/stream/panel.js b/client/src/views/stream/panel.js index 4d1b73576c..bac2e7d2bd 100644 --- a/client/src/views/stream/panel.js +++ b/client/src/views/stream/panel.js @@ -43,6 +43,18 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text 'click button.post': function () { this.post(); }, + 'click .action[data-action="switchInternalMode"]': function (e) { + this.isInternalNoteMode = !this.isInternalNoteMode; + + var $a = $(e.currentTarget); + + if (this.isInternalNoteMode) { + $a.addClass('enabled'); + } else { + $a.removeClass('enabled'); + } + + }, 'keypress textarea.note': function (e) { if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) { this.post(); @@ -62,6 +74,7 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text var data = Dep.prototype.data.call(this); data.postDisabled = this.postDisabled; data.placeholderText = this.placeholderText; + data.allowInternalNotes = this.allowInternalNotes; return data; }, @@ -119,6 +132,13 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text this.placeholderText = this.translate('writeYourCommentHere', 'messages'); + this.allowInternalNotes = false; + if (!this.getUser().get('isPortalUser')) { + this.allowInternalNotes = this.getMetadata().get(['clientDefs', this.scope, 'allowInternalNotes']); + } + + this.isInternalNoteMode = false; + this.wait(true); this.getModelFactory().create('Note', function (model) { this.seed = model; @@ -249,7 +269,7 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text return; } - model.once('sync', function () { + this.listenToOnce(model, 'sync', function () { this.notify('Posted', 'success'); this.collection.fetchNew(); @@ -259,12 +279,12 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text }, this); model.set('post', message); - model.set('attachmentsIds', _.clone(this.seed.get('attachmentsIds'))); + model.set('attachmentsIds', Espo.Utils.clone(this.seed.get('attachmentsIds'))); model.set('type', 'Post'); + model.set('isInternal', this.isInternalNoteMode); this.prepareNoteForPost(model); - this.notify('Posting...'); model.save(null, { error: function () { diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 59a7358514..c1a77f7e05 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -1019,6 +1019,18 @@ h5 { margin-bottom: 9px; } +.post-container .internal-mode-switcher { + color: @gray-light; + margin-left: 10px; + display: inline-block; + position: relative; + top: 2px; +} + +.post-container .internal-mode-switcher.enabled { + color: @link-color; +} + @media screen and (min-width: @screen-sm-min) { #global-search-panel { margin-top: 5px;