mirror of https://github.com/MISP/MISP
677 lines
35 KiB
PHP
677 lines
35 KiB
PHP
<?php
|
|
$URL_ADD = '/analystData/add/';
|
|
$URL_EDIT = '/analystData/edit/';
|
|
$URL_DELETE = '/analystData/delete/';
|
|
|
|
$seed = isset($seed) ? $seed : mt_rand();
|
|
$injectInPage = !empty($container_id) ? true : false;
|
|
|
|
$notes = !empty($notes) ? $notes : [];
|
|
$opinions = !empty($opinions) ? $opinions : [];
|
|
$relationshipsOutbound = !empty($relationships_outbound) ? $relationships_outbound : [];
|
|
$relationshipsInbound = !empty($relationships_inbound) ? $relationships_inbound : [];
|
|
|
|
$related_objects = [
|
|
'Attribute' => [],
|
|
'Event' => [],
|
|
'Object' => [],
|
|
'Organisation' => [],
|
|
'GalaxyCluster' => [],
|
|
'Galaxy' => [],
|
|
'Note' => [],
|
|
'Opinion' => [],
|
|
'SharingGroup' => [],
|
|
];
|
|
foreach ($relationshipsOutbound as $relationship) {
|
|
if (!empty($relationship['related_object'][$relationship['related_object_type']])) {
|
|
$related_objects[$relationship['related_object_type']][$relationship['related_object_uuid']] = $relationship['related_object'][$relationship['related_object_type']];
|
|
}
|
|
}
|
|
foreach ($relationshipsInbound as $relationship) {
|
|
if (!empty($relationship['related_object'][$relationship['object_type']])) {
|
|
$related_objects[$relationship['object_type']][$relationship['object_uuid']] = $relationship['related_object'][$relationship['object_type']];
|
|
}
|
|
}
|
|
|
|
$notesOpinions = array_merge($notes, $opinions);
|
|
$notesOpinionsRelationships = array_merge($notesOpinions, $relationshipsOutbound, $relationshipsInbound);
|
|
?>
|
|
|
|
<script>
|
|
|
|
if (!window.shortDist) {
|
|
var shortDist = <?= json_encode($shortDist) ?>;
|
|
}
|
|
|
|
var container_id = false
|
|
<?php if (isset($container_id)): ?>
|
|
container_id = '<?= h($container_id) ?>'
|
|
<?php endif; ?>
|
|
|
|
function adjustPopoverPosition() {
|
|
var $popover = $('.popover:last');
|
|
$popover.css('top', Math.max($popover.position().top, 50) + 'px')
|
|
}
|
|
|
|
function openNotes<?= $seed ?>(clicked) {
|
|
var notes = <?= json_encode($notesOpinions) ?>;
|
|
var relationships = <?= json_encode($relationshipsOutbound) ?>;
|
|
var relationships_inbound = <?= json_encode($relationshipsInbound) ?>;
|
|
var relationship_related_object = <?= json_encode($related_objects) ?>;
|
|
var renderedNotes = renderAllNotesWithForm<?= $seed ?>(notes, relationships, relationships_inbound, relationship_related_object)
|
|
openPopover(clicked, renderedNotes, undefined, undefined, function() {
|
|
adjustPopoverPosition()
|
|
$(clicked).removeClass('have-a-popover') // avoid closing the popover if a confirm popover (like the delete one) is called
|
|
})
|
|
}
|
|
|
|
function renderNotes(notes, relationship_related_object, emptyMessage='<?= __('Empty') ?>', isInbound=false) {
|
|
var renderedNotesArray = []
|
|
if (notes.length == 0) {
|
|
var emptyHtml = '<span style="text-align: center; color: #777;">' + emptyMessage + '</span>'
|
|
renderedNotesArray.push(emptyHtml)
|
|
} else {
|
|
notes.forEach(function(note) {
|
|
var noteHtml = renderNote(note, relationship_related_object, isInbound)
|
|
|
|
if (note.Opinion && note.Opinion.length > 0) { // The notes has more notes attached
|
|
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.Opinion, relationship_related_object), })
|
|
}
|
|
if (note.Note && note.Note.length > 0) { // The notes has more notes attached
|
|
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.Note, relationship_related_object), })
|
|
}
|
|
if (note._max_depth_reached) {
|
|
noteHtml += replyNoteTemplate({notes_html: maxDepthReachedTemplate({note: note}), })
|
|
}
|
|
|
|
renderedNotesArray.push(noteHtml)
|
|
});
|
|
}
|
|
return renderedNotesArray.join('')
|
|
}
|
|
|
|
function renderNote(note, relationship_related_object, isInbound=false) {
|
|
note.modified_relative = note.modified ? moment(note.modified).fromNow() : note.modified
|
|
note.created_relative = note.created ? moment(note.created).fromNow() : note.created
|
|
note.modified = note.modified ? (new Date(note.modified)).toLocaleString() : note.modified
|
|
note.created = note.created ? (new Date(note.created)).toLocaleString() : note.created
|
|
note.distribution_text = note.distribution != 4 ? shortDist[note.distribution] : note.SharingGroup.name
|
|
note.distribution_color = note.distribution == 0 ? '#ff0000' : (note.distribution == 4 ? '#0088cc' : '#000')
|
|
note.authors = Array.isArray(note.authors) ? note.authors.join(', ') : note.authors;
|
|
|
|
if (note.note_type == 0) { // analyst note
|
|
note.content = analystTemplate(note)
|
|
} else if (note.note_type == 1) { // opinion
|
|
note.opinion_color = note.opinion == 50 ? '#333' : ( note.opinion > 50 ? '#468847' : '#b94a48');
|
|
note.opinion_text = (note.opinion >= 81) ? '<?= __("Strongly Agree") ?>' : ((note.opinion >= 61) ? '<?= __("Agree") ?>' : ((note.opinion >= 41) ? '<?= __("Neutral") ?>' : ((note.opinion >= 21) ? '<?= __("Disagree") ?>' : '<?= __("Strongly Disagree") ?>')))
|
|
note.content = opinionTemplate(note)
|
|
} else if (note.note_type == 2) {
|
|
note.content = renderRelationshipEntryFromType(note, relationship_related_object, isInbound)
|
|
}
|
|
if (isInbound) {
|
|
note._canEdit = false;
|
|
}
|
|
var noteHtml = baseNoteTemplate(note)
|
|
return noteHtml
|
|
}
|
|
|
|
|
|
function getURLFromRelationship(note) {
|
|
if (note.related_object_type == 'Event') {
|
|
return baseurl + '/events/view/' + note.related_object_uuid
|
|
} else if (note.related_object_type == 'Attribute') {
|
|
return note?.attribute?.event_id ? baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid : '#'
|
|
} else if (note.related_object_type == 'Object') {
|
|
return note?.object?.event_id ? baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid : '#'
|
|
}
|
|
return '#'
|
|
}
|
|
|
|
function renderRelationshipEntryFromType(note, relationship_related_object, isInbound=false) {
|
|
if (isInbound) { // reverse related_object_* with object_* to preserve the same code
|
|
var tmp_uuid = note.object_uuid
|
|
var tmp_type = note.object_type
|
|
note.object_uuid = note.related_object_uuid
|
|
note.related_object_uuid = tmp_uuid
|
|
note.object_type = note.related_object_type
|
|
note.related_object_type = tmp_type
|
|
}
|
|
var contentHtml = ''
|
|
var template = doT.template('\
|
|
<span style="border: 1px solid #ddd !important; border-radius: 3px; padding: 0.25rem;"> \
|
|
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_type}}</span> \
|
|
:: \
|
|
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_uuid}}</span> \
|
|
</span> \
|
|
')
|
|
var templateEvent = doT.template('\
|
|
<span class="misp-element-wrapper attribute" title="<?= __('Event') ?>"> \
|
|
<span class="bold"> \
|
|
<span class="attr-type"><span><i class="<?= $this->FontAwesome->getClass('envelope') ?>"></i></span></span> \
|
|
<span class=""><span class="attr-value"> \
|
|
<span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.urlEvent}}" target="_blank">{{!it.content}}</a></span> \
|
|
</span></span> \
|
|
</span> \
|
|
</span> \
|
|
')
|
|
if (note.related_object_type == 'Event' && relationship_related_object.Event[note.related_object_uuid]) {
|
|
note.event = relationship_related_object.Event[note.related_object_uuid]
|
|
template = doT.template(templateEvent({content: '{{!it.event.info}}', urlEvent: '{{!it.url}}'}))
|
|
} else if (note.related_object_type == 'Attribute' && relationship_related_object.Attribute[note.related_object_uuid]) {
|
|
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
|
|
note.attribute = relationship_related_object.Attribute[note.related_object_uuid]
|
|
if (note.attribute.object_relation !== undefined && note.attribute.object_relation !== null) {
|
|
template = doT.template('\
|
|
' + event + ' \
|
|
<b>↦</b> \
|
|
<span class="misp-element-wrapper object"> \
|
|
<span class="bold"> \
|
|
<span class="obj-type"> \
|
|
<span class="object-name" title="<?= __('Object') ?>">{{!it.attribute.Object.name}}</span> \
|
|
↦ <span class="object-attribute-type" title="<?= __('Object Relation') ?>">{{!it.attribute.object_relation}}</span> \
|
|
</span> \
|
|
<span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span> \
|
|
</span> \
|
|
')
|
|
} else if (relationship_related_object.Attribute[note.related_object_uuid]) {
|
|
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
|
|
template = doT.template('\
|
|
' + event + ' \
|
|
<b>↦</b> \
|
|
<span class="misp-element-wrapper attribute"> \
|
|
<span class="bold"> \
|
|
<span class="attr-type"><span title="<?= __('Attribute') ?>">{{!it.attribute.type}}</span></span> \
|
|
<span class="blue"><span class="attr-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span></span> \
|
|
</span> \
|
|
</span> \
|
|
')
|
|
}
|
|
} else if (note.related_object_type == 'Object') {
|
|
var event = templateEvent({content: '{{!it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.object.event_id}}'})
|
|
note.object = relationship_related_object.Object[note.related_object_uuid]
|
|
template = doT.template('\
|
|
' + event + ' \
|
|
<b>↦</b> \
|
|
<span class="misp-element-wrapper object"> \
|
|
<span class="bold"> \
|
|
<span class="obj-type"> \
|
|
<i class="<?= $this->FontAwesome->getClass('cubes') ?>" title="<?= __('Object') ?>" style="margin: 0 0 0 0.25rem;"></i> \
|
|
<span>{{!it.object.name}}</span> \
|
|
</span> \
|
|
<span class="blue"><span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.object.id}}</a></span></span></span> \
|
|
</span> \
|
|
</span> \
|
|
')
|
|
}
|
|
note.url = getURLFromRelationship(note)
|
|
contentHtml = template(note)
|
|
var full = ''
|
|
if (isInbound) {
|
|
full = relationshipInboundDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
|
|
} else {
|
|
full = relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
|
|
}
|
|
return full
|
|
}
|
|
|
|
var noteFilteringTemplate = '\
|
|
<div class="btn-group notes-filtering-container" style="margin-bottom: 0.5rem"> \
|
|
<btn class="btn btn-small btn-primary" href="#" onclick="filterNotes(this, \'all\')"><?= __('All notes') ?></btn> \
|
|
<btn class="btn btn-small btn-inverse" href="#" onclick="filterNotes(this, \'org\')"><?= __('Organisation notes') ?></btn> \
|
|
<btn class="btn btn-small btn-inverse" href="#" onclick="filterNotes(this, \'notorg\')"><?= __('Non-Org notes') ?></btn> \
|
|
</div> \
|
|
'
|
|
|
|
var baseNoteTemplate = doT.template('\
|
|
<div id="{{!it.note_type_name}}-{{!it.id}}" \
|
|
class="analyst-note" \
|
|
style="display: flex; flex-direction: row; align-items: center; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 1px 5px -2px rgb(0 0 0 / 0.5); border-radius: 0.25rem; padding: 0.25rem; margin-bottom: 0.0rem; background-color: #fff; transition: ease-out opacity 0.5s;" \
|
|
data-org-uuid="{{!it.orgc_uuid}}" \
|
|
> \
|
|
<div style="flex-grow: 1;"> \
|
|
<div style="display: flex; flex-direction: column;"> \
|
|
<div style="display: flex; min-width: 250px; gap: 0.5rem;"> \
|
|
<img src="<?= $baseurl ?>/img/orgs/{{!it.Orgc.id}}.png" width="20" height="20" class="orgImg" style="width: 20px; height: 20px;" onerror="this.remove()" alt="Organisation logo"></object> \
|
|
<span style="margin-left: 0rem; margin-right: 0.5rem;"> \
|
|
<span>{{!it.Orgc.name}}</span> \
|
|
<i class="<?= $this->FontAwesome->getClass('angle-right') ?>" style="color: #999; margin: 0 0.25rem;"></i> \
|
|
<b>{{!it.authors}}</b> \
|
|
</span> \
|
|
<span style="display: inline-block; font-weight: lighter; color: #999">{{!it.modified_relative}} • {{!it.modified}}</span> \
|
|
<span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{!it.distribution_color}}"> \
|
|
{{? it.distribution == 4 }} \
|
|
<a href="<?= $baseurl ?>/sharingGroups/view/{{!it.SharingGroup.id}}" target="_blank">{{!it.distribution_text}}</a> \
|
|
{{??}} \
|
|
{{!it.distribution_text}} \
|
|
{{?}} \
|
|
</span> \
|
|
<span class="action-button-container" style="margin-left: auto; display: flex; gap: 0.2rem;"> \
|
|
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
|
|
<span role="button" onclick="addOpinion(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add an opinion to this note') ?>"><i class="<?= $this->FontAwesome->getClass('gavel') ?> useCursorPointer"></i></span> \
|
|
{{?}} \
|
|
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
|
|
<span role="button" onclick="addNote(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add a note to this ') ?>{{!it.note_type_name}}"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
|
|
{{?}} \
|
|
{{? it._canEdit }} \
|
|
<span role="button" onclick="editNote(this, {{!it.id}}, \'{{!it.note_type_name}}\')" title="<?= __('Edit this note') ?>"><i class="<?= $this->FontAwesome->getClass('edit') ?> useCursorPointer"></i></span> \
|
|
{{?}} \
|
|
{{? it._canEdit }} \
|
|
<span role="button" onclick="deleteNote(this, {{!it.id}})" title="<?= __('Delete this note') ?>" href="<?= $baseurl . $URL_DELETE ?>{{!it.note_type_name}}/{{!it.id}}"><i class="<?= $this->FontAwesome->getClass('trash') ?> useCursorPointer"></i></span> \
|
|
{{?}} \
|
|
</span> \
|
|
</div> \
|
|
<div style="">{{=it.content}}</div> \
|
|
</div> \
|
|
</div> \
|
|
</div> \
|
|
')
|
|
var analystTemplate = doT.template('\
|
|
<div style="max-width: 40vw; margin-top: 0.5rem; font-size:"> \
|
|
{{!it.note}} \
|
|
</div> \
|
|
')
|
|
var opinionGradient = '\
|
|
<div class="opinion-gradient-container" style="width: 10rem; height: 6px;">\
|
|
<span class="opinion-gradient-dot"></span> \
|
|
<div class="opinion-gradient opinion-gradient-negative"></div> \
|
|
<div class="opinion-gradient opinion-gradient-positive"></div> \
|
|
</div> \
|
|
'
|
|
var opinionTemplate = doT.template('\
|
|
<div style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> {{!it.opinion}} /100"> \
|
|
' + opinionGradient + ' \
|
|
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;"> \
|
|
<b style="margin-left: 0.5rem; color: {{!it.opinion_color}}">{{!it.opinion_text}}</b> \
|
|
<b style="margin-left: 0.25rem; color: {{!it.opinion_color}}">{{!it.opinion}}</b> \
|
|
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span> \
|
|
</span> \
|
|
</div> \
|
|
{{? it.comment }} \
|
|
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
|
|
{{!it.comment}} \
|
|
</div> \
|
|
{{?}} \
|
|
')
|
|
var relationshipDefaultEntryTemplate = doT.template('\
|
|
<div style="max-width: 40vw; margin: 0.5rem 0 0.5rem 0.25rem;"> \
|
|
<div style="display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;"> \
|
|
<i class="far fa-dot-circle" style="font-size: 1.25em; color: #555; margin-right: 0.25em;"></i> \
|
|
<i class="<?= $this->FontAwesome->getClass('minus') ?>" style="font-size: 1.5em; color: #555"></i> \
|
|
<span style="text-wrap: nowrap; padding: 0 0.25rem; border: 2px solid #555; border-radius: 0.25rem; max-width: 20rem; overflow-x: hidden; text-overflow: ellipsis;"> \
|
|
{{? it.relationship_type }} \
|
|
{{!it.relationship_type}} \
|
|
{{??}} \
|
|
<i style="font-weight: lighter; color: #999;"> - empty -</i> \
|
|
{{?}} \
|
|
</span> \
|
|
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
|
|
<div style="margin-left: 0.25rem;">{{=it.content}}</div> \
|
|
</div> \
|
|
{{? it.comment }} \
|
|
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
|
|
{{!it.comment}} \
|
|
</div> \
|
|
{{?}} \
|
|
</div> \
|
|
')
|
|
var relationshipInboundDefaultEntryTemplate = doT.template('\
|
|
<div style="max-width: 40vw; margin: 0.5rem 0 0.5rem 0.25rem;"> \
|
|
<div style="display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;"> \
|
|
<div style="margin-right: 0.25rem;">{{=it.content}}</div> \
|
|
<i class="<?= $this->FontAwesome->getClass('minus') ?>" style="font-size: 1.5em; color: #555"></i> \
|
|
<span style="text-wrap: nowrap; padding: 0 0.25rem; border: 2px solid #555; border-radius: 0.25rem; max-width: 20rem; overflow-x: hidden; text-overflow: ellipsis;"> \
|
|
{{? it.relationship_type }} \
|
|
{{!it.relationship_type}} \
|
|
{{??}} \
|
|
<i style="font-weight: lighter; color: #999;"> - empty -</i> \
|
|
{{?}} \
|
|
</span> \
|
|
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
|
|
<i class="far fa-dot-circle" style="font-size: 1.25em; color: #555; margin-left: 0.25em;"></i> \
|
|
</div> \
|
|
{{? it.comment }} \
|
|
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
|
|
{{!it.comment}} \
|
|
</div> \
|
|
{{?}} \
|
|
</div> \
|
|
')
|
|
var replyNoteTemplate = doT.template('\
|
|
<span class="reply-to-note-collapse-button reply-to-group" onclick="$(this).toggleClass(\'collapsed\').next().toggle()" title="<?= __('Toggle annotation for this note') ?>" \
|
|
style="width: 12px; height: 12px; border-radius: 50%; border: 1px solid #0035dc20; background: #ccccccdd; box-sizing: border-box; line-height: 12px; padding: 0 1px; cursor: pointer; margin: calc(-0.5rem - 6px) 0 calc(-0.5rem - 6px) -1px; z-index: 2;" \
|
|
> \
|
|
<i class="<?= $this->FontAwesome->getClass('angle-up') ?>" style="line-height: 8px;"></i> \
|
|
</span> \
|
|
<div class="reply-to-note reply-to-group" style="position: relative; display: flex; flex-direction: column; gap: 0.5rem; margin-left: 3px; border-left: 4px solid #ccccccaa; background: #0035dc10; padding: 0.5rem; border-radius: 5px; border-top-left-radius: 0;"> \
|
|
{{=it.notes_html}} \
|
|
</div> \
|
|
')
|
|
|
|
var maxDepthReachedTemplate = doT.template('\
|
|
<div class="max-depth-container"> \
|
|
<div> \
|
|
<span style="font-weight: lighter; color: #999;"> \
|
|
- Max depth reached, there is at least one entry remaining - \
|
|
<a href="<?= $baseurl ?>/analystData/view/{{!it.note.note_type_name}}/{{!it.note.id}}" target="_blank"> \
|
|
<i class="<?= $this->FontAwesome->getClass('search') ?>"></i> \
|
|
<?= __('View entry') ?> \
|
|
</a> \
|
|
</span> \
|
|
</div> \
|
|
<div> \
|
|
<span> \
|
|
<a onclick="fetchMoreNotes(this, \'{{!it.note.note_type_name}}\', \'{{!it.note.uuid}}\')" target="_blank" class="useCursorPointer"> \
|
|
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> \
|
|
<?= __('Load more notes') ?> \
|
|
</a> \
|
|
</span> \
|
|
</div> \
|
|
</div> \
|
|
')
|
|
|
|
function filterNotes(clicked, filter) {
|
|
$(clicked).closest('.notes-filtering-container').find('.btn').addClass('btn-inverse').removeClass('btn-primary')
|
|
$(clicked).removeClass('btn-inverse').addClass('btn-primary')
|
|
var $container = $(clicked).parent().parent().find('.all-notes')
|
|
var $addButtonContainer = $('#add-button-container');
|
|
if (filter == 'notorg') {
|
|
$addButtonContainer.hide()
|
|
} else {
|
|
$addButtonContainer.show()
|
|
}
|
|
$container.find('.analyst-note').show()
|
|
$container.find('.reply-to-group').show()
|
|
$container.find('.analyst-note').filter(function() {
|
|
var $note = $(this)
|
|
// WEIRD. reply-to-group is not showing up!
|
|
if (filter == 'all') {
|
|
return false
|
|
} else if (filter == 'org') {
|
|
var shouldHide = $note.data('org-uuid') != '<?= $me['Organisation']['uuid'] ?>'
|
|
if (shouldHide && $note.next().hasClass('reply-to-group')) { // Also hide reply to button and container
|
|
$note.next().hide().next().hide()
|
|
}
|
|
return shouldHide
|
|
} else if (filter == 'notorg') {
|
|
var shouldHide = $note.data('org-uuid') == '<?= $me['Organisation']['uuid'] ?>'
|
|
if (shouldHide && $note.next().hasClass('reply-to-group')) { // Also hide reply to button and container
|
|
$note.next().hide().next().hide()
|
|
|
|
}
|
|
return shouldHide
|
|
}
|
|
}).hide()
|
|
}
|
|
|
|
function fetchMoreNotes(clicked, noteType, uuid) {
|
|
var depth = 3
|
|
var $maxDepthContainer = $(clicked).closest('.max-depth-container')
|
|
var url = '<?= $baseurl ?>/analystData/getChildren/' + noteType + '/' + uuid + '/' + depth + '.json'
|
|
$.ajax({
|
|
beforeSend: function () {
|
|
$maxDepthContainer.css('filter', 'blur(2px)')
|
|
},
|
|
cache: false,
|
|
success:function (data, textStatus) {
|
|
var notesOpinions = [].concat(data.Note ?? [], data.Opinion ?? [])
|
|
var renderedAdditionalNotes = renderNotes(notesOpinions, [])
|
|
$maxDepthContainer[0].outerHTML = renderedAdditionalNotes
|
|
},
|
|
error:function(xhr) {
|
|
showMessage('fail', 'Could not fetch additional analyst data.');
|
|
},
|
|
complete: function() {
|
|
$maxDepthContainer.css('filter', 'unset')
|
|
},
|
|
url: url
|
|
});
|
|
|
|
}
|
|
|
|
var nodeContainerTemplate<?= $seed ?> = doT.template('\
|
|
<div> \
|
|
<ul class="nav nav-tabs" style="margin-bottom: 10px;"> \
|
|
<li class="active"><a href="#notes-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('sticky-note') ?>"></i> <?= __('Notes & Opinions') ?> <span class="label label-secondary"><?= $allCounts['notesOpinions'] ?></span></a></li> \
|
|
<li><a href="#relationships-outbound-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('arrow-up') ?>"></i> <?= __('Outbound Relationships') ?> <span class="label label-secondary"><?= $allCounts['relationships_outbound'] ?></span></a></li> \
|
|
<li><a href="#relationships-inbound-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('arrow-down') ?>"></i> <?= __('Inbound Relationships') ?> <span class="label label-secondary"><?= $allCounts['relationships_inbound'] ?></span></a></li> \
|
|
</ul> \
|
|
<div class="tab-content" style="padding: 0.25rem; max-width: 1200px; min-width: 400px;"> \
|
|
<div id="notes-<?= $seed ?>" class="tab-pane active"> \
|
|
' + noteFilteringTemplate + ' \
|
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;" class="all-notes">{{=it.content_notes}}</div>\
|
|
</div> \
|
|
<div id="relationships-outbound-<?= $seed ?>" class="tab-pane"> \
|
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships_outbound}}</div>\
|
|
</div> \
|
|
<div id="relationships-inbound-<?= $seed ?>" class="tab-pane"> \
|
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships_inbound}}</div>\
|
|
</div> \
|
|
</div> \
|
|
</div> \
|
|
')
|
|
|
|
var addNoteButton = '<button class="btn btn-small btn-block btn-primary" type="button" onclick="createNewNote(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
|
|
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> <?= __('Add a note') ?> \
|
|
</button>'
|
|
var addOpinionButton = '<button class="btn btn-small btn-block btn-primary" style="margin-top: 2px;" type="button" onclick="createNewOpinion(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
|
|
<i class="<?= $this->FontAwesome->getClass('gavel') ?>"></i> <?= __('Add an opinion') ?> \
|
|
</button>'
|
|
var addRelationshipButton = '<button class="btn btn-small btn-block btn-primary" type="button" onclick="createNewRelationship(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
|
|
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> <?= __('Add a relationship') ?> \
|
|
</button>'
|
|
|
|
function renderAllNotesWithForm<?= $seed ?>(notes, relationships, relationships_inbound, relationship_related_object) {
|
|
var buttonContainer = '<div id="add-button-container" style="margin-top: 0.5rem;">' + addNoteButton + addOpinionButton + '</div>'
|
|
var renderedNotes = nodeContainerTemplate<?= $seed ?>({
|
|
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2}), relationship_related_object, '<?= __('No notes for this UUID.') ?>') + buttonContainer,
|
|
content_relationships_outbound: renderNotes(relationships, relationship_related_object, '<?= __('No relationship from this UUID') ?>') + addRelationshipButton,
|
|
content_relationships_inbound: renderNotes(relationships_inbound, relationship_related_object, '<?= __('No element are referencing this UUID') ?>', true),
|
|
})
|
|
return renderedNotes
|
|
}
|
|
|
|
function createNewNote(clicked, object_type, object_uuid) {
|
|
note_type = 'Note';
|
|
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
|
|
}
|
|
|
|
function createNewOpinion(clicked, object_type, object_uuid) {
|
|
note_type = 'Opinion';
|
|
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
|
|
}
|
|
|
|
function createNewRelationship(clicked, object_type, object_uuid) {
|
|
note_type = 'Relationship';
|
|
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
|
|
}
|
|
|
|
function addNote(clicked, note_uuid, object_type) {
|
|
note_type = 'Note';
|
|
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + note_uuid + '/' + object_type)
|
|
}
|
|
|
|
function addOpinion(clicked, note_uuid, object_type) {
|
|
note_type = 'Opinion';
|
|
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + note_uuid + '/' + object_type)
|
|
}
|
|
|
|
function editNote(clicked, note_id, note_type) {
|
|
openGenericModal(baseurl + '<?= $URL_EDIT ?>' + note_type + '/' + note_id)
|
|
}
|
|
|
|
function deleteNote(clicked, note_id) {
|
|
var deletionSuccessCallback = function(data) {
|
|
$(clicked).closest('.analyst-note').remove()
|
|
}
|
|
popoverConfirm(clicked, '<?= __('Confirm deletion of this note') ?>', undefined, deletionSuccessCallback)
|
|
}
|
|
|
|
function replaceNoteInUI(data) {
|
|
var noteType = Object.keys(data)[0]
|
|
var noteHTMLID = '#' + data[noteType].note_type_name + '-' + data[noteType].id
|
|
var $noteToReplace = $(noteHTMLID)
|
|
if ($noteToReplace.length == 1) {
|
|
var compiledUpdatedNote = renderNote(data[noteType])
|
|
$noteToReplace[0].outerHTML = compiledUpdatedNote
|
|
$(noteHTMLID).css({'opacity': 0})
|
|
setTimeout(() => {
|
|
$(noteHTMLID).css({'opacity': 1})
|
|
}, 750);
|
|
}
|
|
}
|
|
|
|
<?php if(!empty($injectInPage)): ?>
|
|
$(document).ready(function() {
|
|
var notes = <?= json_encode($notesOpinions) ?>;
|
|
var relationships = <?= json_encode($relationshipsOutbound) ?>;
|
|
var relationships_inbound = <?= json_encode($relationshipsInbound) ?>;
|
|
var relationship_related_object = <?= json_encode($related_objects) ?>;
|
|
var renderedNotes = renderAllNotesWithForm<?= $seed ?>(notes, relationships, relationships_inbound, relationship_related_object)
|
|
if (container_id) {
|
|
$('#' + container_id).html(renderedNotes)
|
|
}
|
|
})
|
|
<?php endif; ?>
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
.action-button-container > span {
|
|
visibility: hidden;
|
|
}
|
|
.analyst-note:hover .action-button-container > span {
|
|
visibility: visible;
|
|
}
|
|
|
|
.reply-to-note-collapse-button.collapsed {
|
|
margin-bottom: -0.25rem !important;
|
|
}
|
|
|
|
.v-bar-text-opinion::before {
|
|
content: '';
|
|
margin-right: 5px;
|
|
margin-left: 2px;
|
|
border-left: 1px solid;
|
|
border-bottom: 1px solid;
|
|
height: 1.3rem;
|
|
width: 5px;
|
|
display: inline-block;
|
|
float: left;
|
|
margin-top: -12px;
|
|
border-color: #969696;
|
|
}
|
|
|
|
.reply-to-note-collapse-button.collapsed > i {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
<?php
|
|
if(!function_exists("genStyleForOpinionNotes")) {
|
|
function genStyleForOpinionNotes($notes) {
|
|
foreach ($notes as $note) {
|
|
genStyleForOpinionNote($note);
|
|
if (!empty($note['Note'])) {
|
|
genStyleForOpinionNotes($note['Note']);
|
|
}
|
|
if (!empty($note['Opinion'])) {
|
|
genStyleForOpinionNotes($note['Opinion']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!function_exists("genStyleForOpinionNote")) {
|
|
function genStyleForOpinionNote($note) {
|
|
if ($note['note_type'] != 1) { // opinion
|
|
return;
|
|
}
|
|
$opinion_color_scale_100 = ['rgb(164, 0, 0)', 'rgb(166, 15, 0)', 'rgb(169, 25, 0)', 'rgb(171, 33, 0)', 'rgb(173, 40, 0)', 'rgb(175, 46, 0)', 'rgb(177, 52, 0)', 'rgb(179, 57, 0)', 'rgb(181, 63, 0)', 'rgb(183, 68, 0)', 'rgb(186, 72, 0)', 'rgb(188, 77, 0)', 'rgb(190, 82, 0)', 'rgb(191, 86, 0)', 'rgb(193, 90, 0)', 'rgb(195, 95, 0)', 'rgb(197, 98, 0)', 'rgb(198, 102, 0)', 'rgb(200, 106, 0)', 'rgb(201, 110, 0)', 'rgb(203, 114, 0)', 'rgb(204, 118, 0)', 'rgb(206, 121, 0)', 'rgb(208, 125, 0)', 'rgb(209, 128, 0)', 'rgb(210, 132, 0)', 'rgb(212, 135, 0)', 'rgb(213, 139, 0)', 'rgb(214, 143, 0)', 'rgb(216, 146, 0)', 'rgb(217, 149, 0)', 'rgb(218, 153, 0)', 'rgb(219, 156, 0)', 'rgb(220, 160, 0)', 'rgb(222, 163, 0)', 'rgb(223, 166, 0)', 'rgb(224, 169, 0)', 'rgb(225, 173, 0)', 'rgb(226, 176, 0)', 'rgb(227, 179, 0)', 'rgb(228, 182, 0)', 'rgb(229, 186, 0)', 'rgb(230, 189, 0)', 'rgb(231, 192, 0)', 'rgb(232, 195, 0)', 'rgb(233, 198, 0)', 'rgb(234, 201, 0)', 'rgb(235, 204, 0)', 'rgb(236, 207, 0)', 'rgb(237, 210, 0)', 'rgb(237, 212, 0)', 'rgb(234, 211, 0)', 'rgb(231, 210, 0)', 'rgb(229, 209, 1)', 'rgb(226, 208, 1)', 'rgb(223, 207, 1)', 'rgb(220, 206, 1)', 'rgb(218, 204, 1)', 'rgb(215, 203, 2)', 'rgb(212, 202, 2)', 'rgb(209, 201, 2)', 'rgb(206, 200, 2)', 'rgb(204, 199, 2)', 'rgb(201, 198, 3)', 'rgb(198, 197, 3)', 'rgb(195, 196, 3)', 'rgb(192, 195, 3)', 'rgb(189, 194, 3)', 'rgb(186, 193, 3)', 'rgb(183, 192, 4)', 'rgb(180, 190, 4)', 'rgb(177, 189, 4)', 'rgb(174, 188, 4)', 'rgb(171, 187, 4)', 'rgb(168, 186, 4)', 'rgb(165, 185, 4)', 'rgb(162, 183, 4)', 'rgb(159, 182, 4)', 'rgb(156, 181, 4)', 'rgb(153, 180, 4)', 'rgb(149, 179, 5)', 'rgb(146, 178, 5)', 'rgb(143, 177, 5)', 'rgb(139, 175, 5)', 'rgb(136, 174, 5)', 'rgb(133, 173, 5)', 'rgb(130, 172, 5)', 'rgb(126, 170, 5)', 'rgb(123, 169, 5)', 'rgb(119, 168, 5)', 'rgb(115, 167, 5)', 'rgb(112, 165, 6)', 'rgb(108, 164, 6)', 'rgb(104, 163, 6)', 'rgb(100, 162, 6)', 'rgb(96, 160, 6)', 'rgb(92, 159, 6)', 'rgb(88, 157, 6)', 'rgb(84, 156, 6)', 'rgb(80, 155, 6)', 'rgb(78, 154, 6)'];
|
|
$opinion = min(100, max(0, intval($note['opinion'])));
|
|
?>
|
|
|
|
#Opinion-<?= $note['id'] ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
|
|
opacity: 0;
|
|
}
|
|
#Opinion-<?= $note['id'] ?> .opinion-gradient-dot {
|
|
left: calc(<?= $opinion ?>% - 6px);
|
|
background-color: <?= $opinion == 50 ? '#555' : $opinion_color_scale_100[$opinion] ?>;
|
|
}
|
|
<?php if ($opinion >= 50): ?>
|
|
#Opinion-<?= $note['id'] ?> .opinion-gradient-positive {
|
|
-webkit-mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
|
|
mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
|
|
}
|
|
<?php else: ?>
|
|
#Opinion-<?= $note['id'] ?> .opinion-gradient-negative {
|
|
-webkit-mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
|
|
mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
|
|
}
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
}
|
|
}
|
|
|
|
genStyleForOpinionNotes($notesOpinionsRelationships)
|
|
?>
|
|
|
|
</style>
|
|
|
|
<style>
|
|
span.misp-element-wrapper {
|
|
margin: 3px 3px;
|
|
border: 1px solid #ddd !important;
|
|
border-radius: 3px;
|
|
white-space: nowrap;
|
|
display: inline-block;
|
|
padding: 0;
|
|
}
|
|
.misp-element-wrapper.attribute .attr-type {
|
|
background-color: #f5f5f5 !important;
|
|
border-right: 1px solid #ddd !important;
|
|
display: inline-block;
|
|
}
|
|
.misp-element-wrapper.attribute .attr-type > span {
|
|
margin: 2px 3px;
|
|
}
|
|
.misp-element-wrapper.attribute .attr-value {
|
|
display: inline-table;
|
|
margin: 0px 3px;
|
|
}
|
|
.misp-element-wrapper.attribute .attr-value > span {
|
|
max-width: 300px;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
display: table-cell;
|
|
}
|
|
span.misp-element-wrapper.object {
|
|
border: 1px solid #3465a4 !important;
|
|
}
|
|
.misp-element-wrapper.object .obj-type {
|
|
display: inline-block;
|
|
background-color: #3465a4 !important;
|
|
color: #ffffff !important;
|
|
}
|
|
.misp-element-wrapper.object .obj-type .object-attribute-type {
|
|
margin-left: 0;
|
|
background-color: #f5f5f5;
|
|
color: black;
|
|
padding: 1px 3px;
|
|
border-radius: 7px;
|
|
}
|
|
.misp-element-wrapper.object .obj-type > span {
|
|
margin: 2px 3px;
|
|
}
|
|
.misp-element-wrapper.object .obj-value {
|
|
display: inline-table;
|
|
margin: 0px 3px;
|
|
}
|
|
.misp-element-wrapper.object .obj-value > span {
|
|
max-width: 300px;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
display: table-cell;
|
|
}
|
|
</style>
|