Merge branch 'feature/analyst-data' into notes

notes
Sami Mokaddem 2024-01-29 10:06:25 +01:00
commit 0c53d96d5d
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
25 changed files with 1071 additions and 293 deletions

View File

@ -23,7 +23,7 @@ class AnalystDataController extends AppController
'Relationship'
];
public $modelSelection = 'Note';
// public $modelSelection = 'Note';
private function _setViewElements()
{
@ -65,7 +65,8 @@ class AnalystDataController extends AppController
{
$this->__typeSelector($type);
$this->set('id', $id);
$params = [];
$params = [
];
$this->CRUD->edit($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
@ -115,10 +116,18 @@ class AnalystDataController extends AppController
$this->_setViewElements();
}
public function getRelatedElement($type, $uuid)
{
$this->__typeSelector('Relationship');
$data = $this->AnalystData->getRelatedElement($this->Auth->user(), $type, $uuid);
return $this->RestResponse->viewData($data, 'json');
}
private function __typeSelector($type) {
foreach ($this->__valid_types as $vt) {
if ($type === $vt) {
$this->modelSelection = $vt;
$this->loadModel($vt);
$this->AnalystData = $this->{$vt};
$this->modelClass = $vt;
$this->{$vt}->current_user = $this->Auth->user();

View File

@ -25,6 +25,7 @@ class CRUDComponent extends Component
}
$options['filters'][] = 'quickFilter';
}
$this->Controller->{$this->Controller->modelClass}->includeAnalystData = true;
$params = $this->Controller->IndexFilter->harvestParameters(empty($options['filters']) ? [] : $options['filters']);
$query = [];
$query = $this->setFilters($params, $query);

View File

@ -1233,6 +1233,8 @@ class EventsController extends AppController
}
}
$this->Event->Attribute->includeAnalystData = true;
if (isset($filters['focus'])) {
$this->set('focus', $filters['focus']);
}

View File

@ -69,6 +69,16 @@ class JSONConverterTool
}
}
if (isset($event['Event']['Note'])) {
$event['Event']['Note'] = self::__cleanAnalystData($event['Event']['Note']);
}
if (isset($event['Event']['Opinion'])) {
$event['Event']['Opinion'] = self::__cleanAnalystData($event['Event']['Opinion']);
}
if (isset($event['Event']['Relationship'])) {
$event['Event']['Relationship'] = self::__cleanAnalystData($event['Event']['Relationship']);
}
// cleanup the array from things we do not want to expose
$tempSightings = array();
if (!empty($event['Sighting'])) {
@ -209,6 +219,17 @@ class JSONConverterTool
return $objects;
}
private function __cleanAnalystData($data)
{
foreach ($data as $k => $entry) {
if (empty($entry['SharingGroup'])) {
unset($data[$k]['SharingGroup']);
}
}
$data = array_values($data);
return $data;
}
public static function arrayPrinter($array, $root = true)
{
if (is_array($array)) {

View File

@ -23,8 +23,45 @@ class AnalystData extends AppModel
'SharingGroup'
];
const NOTE = 0,
OPINION = 1,
RELATIONSHIP = 2;
public $current_user = null;
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->bindModel([
'belongsTo' => [
'Organisation' => [
'className' => 'Organisation',
'foreignKey' => false,
'conditions' => [
sprintf('%s.orgc_uuid = Organisation.uuid', $this->alias)
],
]
]
]);
}
public function afterFind($results, $primary = false)
{
parent::afterFind($results, $primary);
foreach ($results as $i => $v) {
$results[$i][$this->alias]['note_type'] = $this->current_type_id;
$results[$i][$this->alias]['note_type_name'] = $this->current_type;
if (!isset($v['Organisation'])) {
$this->Organisation = ClassRegistry::init('Organisation');
$results[$i][$this->alias]['Organisation'] = $this->Organisation->find('first', ['condition' => ['uuid' => $v[$this->alias]['orgc_uuid']]])['Organisation'];
} else {
$results[$i][$this->alias]['Organisation'] = $v['Organisation'];
}
unset($results[$i]['Organisation']);
$results[$i][$this->alias] = $this->fetchChildNotesAndOpinions($results[$i][$this->alias]);
}
return $results;
}
public function beforeValidate($options = array())
{
@ -39,7 +76,6 @@ class AnalystData extends AppModel
$this->data[$this->current_type]['org_uuid'] = $this->current_user['Organisation']['uuid'];
$this->data[$this->current_type]['authors'] = $this->current_user['email'];
}
debug($this->data);
return true;
}
@ -57,4 +93,44 @@ class AnalystData extends AppModel
}
throw new NotFoundException(__('Invalid UUID'));
}
public function fetchChildNotesAndOpinions(array $analystData): array
{
$this->Note = ClassRegistry::init('Note');
$this->Opinion = ClassRegistry::init('Opinion');
$paramsNote = [
'recursive' => -1,
'contain' => ['Organisation'],
'conditions' => [
'object_type' => $this->current_type,
'object_uuid' => $analystData['uuid'],
]
];
$paramsOpinion = [
'recursive' => -1,
'contain' => ['Organisation'],
'conditions' => [
'object_type' => $this->current_type,
'object_uuid' => $analystData['uuid'],
]
];
// recursively fetch and include nested notes and opinions
$childNotes = array_map(function ($item) {
$expandedNotes = $this->fetchChildNotesAndOpinions($item[$this->Note->current_type]);
return $expandedNotes;
}, $this->Note->find('all', $paramsNote));
$childOpinions = array_map(function ($item) {
$expandedNotes = $this->fetchChildNotesAndOpinions($item[$this->Opinion->current_type]);
return $expandedNotes;
}, $this->Opinion->find('all', $paramsOpinion));
if (!empty($childNotes)) {
$analystData[$this->Note->current_type] = $childNotes;
}
if (!empty($childOpinions)) {
$analystData[$this->Opinion->current_type] = $childOpinions;
}
return $analystData;
}
}

View File

@ -2070,6 +2070,7 @@ class AppModel extends Model
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
`distribution` tinyint(4) NOT NULL,
`sharing_group_id` int(10) unsigned,
`relationship_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci,
`related_object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`related_object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
PRIMARY KEY (`id`),
@ -2080,6 +2081,7 @@ class AppModel extends Model
KEY `orgc_uuid` (`orgc_uuid`),
KEY `distribution` (`distribution`),
KEY `sharing_group_id` (`sharing_group_id`),
KEY `relationship_type` (`relationship_type`),
KEY `related_object_uuid` (`related_object_uuid`),
KEY `related_object_type` (`related_object_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";

View File

@ -37,7 +37,7 @@ class Attribute extends AppModel
'Containable',
'Regexp' => array('fields' => array('value')),
'LightPaginator',
'AnalystDataParent'
'AnalystDataParent',
);
public $displayField = 'value';

View File

@ -38,7 +38,8 @@ class AnalystDataBehavior extends ModelBehavior
}
return $Model->find('all', [
'recursive' => -1,
'conditions' => $conditions
'conditions' => $conditions,
'contain' => ['Organisation'],
]);
}

View File

@ -11,7 +11,7 @@ class AnalystDataParentBehavior extends ModelBehavior
public function attachAnalystData(Model $model, array $object, array $types = ['Note', 'Opinion', 'Relationship'])
public function attachAnalystData(Model $Model, array $object, array $types = ['Note', 'Opinion', 'Relationship'])
{
// No uuid, nothing to attach
if (empty($object['uuid'])) {
@ -42,7 +42,7 @@ class AnalystDataParentBehavior extends ModelBehavior
if (!empty($model->includeAnalystData)) {
foreach ($results as $k => $item) {
if (isset($item[$model->alias])) {
$results[$k] = array_merge($results[$k], $this->attachAnalystData($item[$model->alias]));
$results[$k] = array_merge($results[$k], $this->attachAnalystData($model, $item[$model->alias]));
}
}
}

View File

@ -2232,6 +2232,10 @@ class Event extends AppModel
unset($attribute['EventTag']);
}
}
$allAnalystData = $this->Attribute->attachAnalystData($attribute); // afterFind from AnalystDataParentBehavior is not called. Probably because we're in an association
foreach ($allAnalystData as $type => $analystData) {
$attribute[$type] = $analystData;
}
// If a shadowattribute can be linked to an attribute, link it to it
// This is to differentiate between proposals that were made to an attribute for modification and between proposals for new attributes
$attribute['ShadowAttribute'] = $shadowAttributeByOldId[$attribute['id']] ?? [];

View File

@ -570,11 +570,18 @@ class MispObject extends AppModel
if (isset($options['fields'])) {
$params['fields'] = $options['fields'];
}
$contain = [];
if (isset($options['contain'])) {
$contain = $options['contain'];
}
if (empty($contain['Event'])) {
$contain = ['Event' => ['distribution', 'id', 'user_id', 'orgc_id', 'org_id']];
}
$results = $this->find('all', array(
'conditions' => $params['conditions'],
'recursive' => -1,
'fields' => $params['fields'],
'contain' => array('Event' => array('distribution', 'id', 'user_id', 'orgc_id', 'org_id')),
'contain' => $contain,
'sort' => false
));
return $results;

View File

@ -12,6 +12,7 @@ class Note extends AnalystData
);
public $current_type = 'Note';
public $current_type_id = 0;
public $validate = array(
);

View File

@ -1,5 +1,6 @@
<?php
App::uses('AppModel', 'Model');
App::uses('AnalystData', 'Model');
class Opinion extends AnalystData
{
@ -11,6 +12,7 @@ class Opinion extends AnalystData
);
public $current_type = 'Opinion';
public $current_type_id = 1;
public $validate = array(
);

View File

@ -1,5 +1,6 @@
<?php
App::uses('AppModel', 'Model');
App::uses('AnalystData', 'Model');
class Relationship extends AnalystData
{
@ -11,7 +12,114 @@ class Relationship extends AnalystData
);
public $current_type = 'Relationship';
public $current_type_id = 2;
public $validate = array(
);
/** @var object|null */
protected $Event;
/** @var object|null */
protected $Attribute;
/** @var object|null */
protected $Object;
/** @var object|null */
protected $Note;
/** @var object|null */
protected $Opinion;
/** @var object|null */
protected $Relationship;
/** @var object|null */
protected $User;
/** @var array|null */
private $__currentUser;
public function afterFind($results, $primary = false)
{
$results = parent::afterFind($results, $primary);
if (empty($this->__currentUser)) {
$user_id = Configure::read('CurrentUserId');
$this->User = ClassRegistry::init('User');
if ($user_id) {
$this->__currentUser = $this->User->getAuthUser($user_id);
}
}
foreach ($results as $i => $v) {
$results[$i][$this->alias]['related_object'] = $this->getRelatedElement($this->__currentUser, $v[$this->alias]['related_object_type'], $v[$this->alias]['related_object_uuid']);
}
return $results;
}
public function getRelatedElement(array $user, $type, $uuid): array
{
$data = [];
if ($type == 'Event') {
$this->Event = ClassRegistry::init('Event');
$params = [
];
$data = $this->Event->fetchSimpleEvent($user, $uuid, $params);
} else if ($type == 'Attribute') {
$this->Attribute = ClassRegistry::init('Attribute');
$params = [
'conditions' => [
['Attribute.uuid' => $uuid],
],
'contain' => ['Event' => 'Orgc', 'Object',]
];
$data = $this->Attribute->fetchAttributeSimple($user, $params);
$data = $this->rearrangeData($data, 'Attribute');
} else if ($type == 'Object') {
$this->Object = ClassRegistry::init('MispObject');
$params = [
'conditions' => [
['Object.uuid' => $uuid],
],
'contain' => ['Event' => 'Orgc',]
];
$data = $this->Object->fetchObjectSimple($user, $params);
if (!empty($data)) {
$data = $data[0];
}
$data = $this->rearrangeData($data, 'Object');
} else if ($type == 'Note') {
$this->Note = ClassRegistry::init('Note');
$params = [
];
$data = $this->Note->fetchNote();
} else if ($type == 'Opinion') {
$this->Opinion = ClassRegistry::init('Opinion');
$params = [
];
$data = $this->Opinion->fetchOpinion();
} else if ($type == 'Relationship') {
$this->Relationship = ClassRegistry::init('Relationship');
$params = [
];
$data = $this->Relationship->fetchRelationship();
}
return $data;
}
private function rearrangeData(array $data, $objectType): array
{
$models = ['Event', 'Attribute', 'Object', 'Organisation', ];
if (!empty($data)) {
foreach ($models as $model) {
if ($model == $objectType) {
continue;
}
if (isset($data[$model])) {
$data[$objectType][$model] = $data[$model];
unset($data[$model]);
}
}
}
$data[$objectType]['Organisation'] = $data[$objectType]['Event']['Orgc'];
$data[$objectType]['orgc_uuid'] = $data[$objectType]['Event']['Orgc']['uuid'];
unset($data[$objectType]['Event']['Orgc']);
return $data;
}
}

View File

@ -48,9 +48,41 @@ if ($modelSelection === 'Note') {
]
);
} else if ($modelSelection === 'Opinion') {
$fields = array_merge($fields,
[
[
'field' => 'opinion',
'class' => '',
'type' => 'opinion'
],
[
'field' => 'comment',
'type' => 'textarea',
'class' => 'input span6'
]
]
);
} else if ($modelSelection === 'Relationship') {
$fields = array_merge($fields,
[
[
'field' => 'relationship_type',
'class' => 'span4',
],
[
'field' => 'related_object_type',
'class' => 'span2',
'options' => $dropdownData['valid_targets'],
'type' => 'dropdown',
'stayInLine' => 1
],
[
'field' => 'related_object_uuid',
'class' => 'span4',
],
sprintf('<div><label>%s:</label><div id="related-object-container">%s</div></div>', __('Related Object'), __('- No UUID provided -'))
]
);
}
echo $this->element('genericElements/Form/genericForm', [
'data' => [
@ -60,7 +92,7 @@ echo $this->element('genericElements/Form/genericForm', [
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
'ajaxSubmit' => 'submitGenericFormInPlace(analystDataSubmitSuccess, true);'
]
]
]);
@ -68,3 +100,85 @@ echo $this->element('genericElements/Form/genericForm', [
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
?>
<script>
function analystDataSubmitSuccess(data) {
<?php if ($edit): ?>
replaceNoteInUI(data)
<?php else: ?>
addNoteInUI(data)
<?php endif; ?>
}
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 relatedObjects = {}
if (noteType == 'Relationship') {
var relationship = data[noteType]
relatedObjects[relationship['object_type']] = {}
relatedObjects[relationship['object_type']][relationship['related_object_uuid']] = relationship['related_object'][relationship['object_type']]
}
var compiledUpdatedNote = renderNote(data[noteType], relatedObjects)
$noteToReplace[0].outerHTML = compiledUpdatedNote
$(noteHTMLID).css({'opacity': 0})
setTimeout(() => {
$(noteHTMLID).css({'opacity': 1})
}, 750);
}
}
function addNoteInUI(data) {
location.reload()
}
function displayRelatedObject(data) {
if (Object.keys(data).length == 0) {
$('#related-object-container').html('<span class="text-muted"><?= __('Could not fetch remote object or fetching not supported yet.') ?></span>')
} else {
var parsed = syntaxHighlightJson(data)
$('#related-object-container').html(parsed)
}
}
function fetchAndDisplayRelatedObject(type, uuid) {
var url = baseurl + '/analystData/getRelatedElement/' + type + '/' + uuid
$.ajax({
type: "get",
url: url,
headers: { Accept: "application/json" },
success: function (data) {
displayRelatedObject(data)
},
error: function (data, textStatus, errorThrown) {
showMessage('fail', textStatus + ": " + errorThrown);
}
});
}
$(document).ready(function() {
$('#RelationshipRelatedObjectType').change(function(e) {
if ($('#RelationshipRelatedObjectUuid').val().length == 36) {
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
}
})
$('#RelationshipRelatedObjectUuid').on('input', function(e) {
if ($('#RelationshipRelatedObjectUuid').val().length == 36) {
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
}
})
})
</script>
<style>
#related-object-container {
box-shadow: 0 0 5px 0px #22222266;
padding: 0.5rem;
max-height: 400px;
overflow: auto;
margin-bottom: 1rem;
}
</style>

View File

@ -9,6 +9,11 @@
'name' => __('UUID'),
'data_path' => $modelSelection . '.uuid'
],
[
'name' => __('Parent Object Type'),
'sort' => $modelSelection . '.object_type',
'data_path' => $modelSelection . '.object_type'
],
[
'name' => __('Target Object'),
'sort' => $modelSelection . '.object_type',
@ -16,7 +21,7 @@
],
[
'name' => __('Creator org'),
'data_path' => $modelSelection . '.orgc_id'
'data_path' => $modelSelection . '.orgc_uuid'
],
[
'name' => __('Created'),
@ -51,9 +56,35 @@
]
);
} else if ($modelSelection === 'Opinion') {
$fields = array_merge($fields,
[
[
'name' => __('Comment'),
'data_path' => $modelSelection . '.comment'
],
]
);
} else if ($modelSelection === 'Relationship') {
$fields = array_merge($fields,
[
[
'name' => __('Related Object Type'),
'sort' => $modelSelection . '.related_object_type',
'data_path' => $modelSelection . '.related_object_type'
],
[
'name' => __('Related Object UUID'),
'sort' => $modelSelection . '.related_object_uuid',
'data_path' => $modelSelection . '.related_object_uuid'
],
[
'name' => __('Relationship_type'),
'sort' => $modelSelection . '.relationship_type',
'data_path' => $modelSelection . '.relationship_type'
],
]
);
}
echo $this->element('genericElements/IndexTable/scaffold', [
@ -63,6 +94,27 @@
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
[
'active' => $modelSelection === 'Note',
'url' => sprintf('%s/analyst_data/index/Note', $baseurl),
'text' => __('Note'),
],
[
'active' => $modelSelection === 'Opinion',
'class' => 'defaultContext',
'url' => sprintf('%s/analyst_data/index/Opinion', $baseurl),
'text' => __('Opinion'),
],
[
'active' => $modelSelection === 'Relationship',
'url' => sprintf('%s/analyst_data/index/Relationship', $baseurl),
'text' => __('Relationship'),
],
]
],
[
'type' => 'search',
'button' => __('Filter'),
@ -91,7 +143,7 @@
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/analystData/edit/' . $modelSelection . '/[onclick_params_data_path]\');',
'openGenericModal(\'%s/analystData/delete/' . $modelSelection . '/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => $modelSelection . '.id',

View File

@ -70,14 +70,34 @@
<td class="short context hidden"><?= $objectId ?></td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php echo $this->element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short timestamp <?= $isNew ? 'bold red' : '' ?>" <?= $isNew ? 'title="' . __('Element or modification to an existing element has not been published yet.') . '"' : '' ?>><?= $this->Time->date($object['timestamp']) . ($isNew ? '*' : '') ?></td>
<td class="short context">
<?php echo $this->element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if (!empty($extended)):

View File

@ -32,14 +32,34 @@ $objectId = intval($object['id']);
<td class="short context hidden"><?= $objectId ?></td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php echo $this->element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short timestamp <?= $isNew ? 'bold red' : '' ?>" <?= $isNew ? 'title="' . __('Element or modification to an existing element has not been published yet.') . '"' : '' ?>><?= $this->Time->date($object['timestamp']) . ($isNew ? '*' : '') ?></td>
<td class="short context">
<?php echo $this->element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'object']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if ($extended):

View File

@ -47,7 +47,16 @@
</td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php echo $this->element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
@ -59,7 +68,18 @@
?>
</td>
<td class="short context">
<?php echo $this->element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'proposals']); ?>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if ($extended):

View File

@ -1,13 +1,11 @@
<?php
$seed = mt_rand();
$URL_ADD = '/analyst-notes/add/';
$URL_EDIT = '/analyst-notes/edit/';
$URL_DELETE = '/analyst-notes/delete/';
$URL_ADD = '/analystData/add/';
$URL_EDIT = '/analystData/edit/';
$URL_DELETE = '/analystData/delete/';
$object_uuid = 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a'; // e.g. $event['Event']['uuid']
$object_type = 'event';
$notes = [
$notes2 = [
[
'analyst_note' => 'This is a note',
'note_type' => 0,
@ -185,7 +183,7 @@ $notes = [
],
];
$related_objects = [
$related_objects2 = [
'Event' => [
'f80a0db2-24bd-4148-929e-7c803ade7ca1' => [
'uuid' => 'f80a0db2-24bd-4148-929e-7c803ade7ca1',
@ -238,48 +236,327 @@ $related_objects = [
],
];
$notes = $analyst_data['notes'] ?? [];
$opinions = $analyst_data['opinions'] ?? [];
$relationships = $analyst_data['relationships'] ?? [];
$related_objects = [
'Attribute' => [],
'Event' => [],
'Object' => [],
'Organisation' => [],
'GalaxyCluster' => [],
'Galaxy' => [],
'Note' => [],
'Opinion' => [],
'SharingGroup' => [],
];
foreach ($relationships 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']];
}
}
$notesOpinions = array_merge($notes, $opinions);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
if(!function_exists("countNotes")) {
function countNotes($notes) {
$notesTotalCount = count($notes);
function countNotes($notesOpinions) {
$notesTotalCount = count($notesOpinions);
$notesCount = 0;
$relationsCount = 0;
foreach ($notes as $note) {
if ($note['note_type'] == 2) { // relationship
foreach ($notesOpinions as $notesOpinion) {
if ($notesOpinion['note_type'] == 2) { // relationship
$relationsCount += 1;
} else {
$notesCount += 1;
}
if (!empty($note['notes'])) {
$nestedCounts = countNotes($note['notes']);
if (!empty($notesOpinion['Note'])) {
$nestedCounts = countNotes($notesOpinion['Note']);
$notesTotalCount += $nestedCounts['total'];
$notesCount += $nestedCounts['notes'];
$notesCount += $nestedCounts['notesOpinions'];
$relationsCount += $nestedCounts['relations'];
}
if (!empty($notesOpinion['Opinion'])) {
$nestedCounts = countNotes($notesOpinion['Opinion']);
$notesTotalCount += $nestedCounts['total'];
$notesCount += $nestedCounts['notesOpinions'];
$relationsCount += $nestedCounts['relations'];
}
}
return ['total' => $notesTotalCount, 'notes' => $notesCount, 'relations' => $relationsCount];
return ['total' => $notesTotalCount, 'notesOpinions' => $notesCount, 'relations' => $relationsCount];
}
}
$counts = countNotes($notes);
$notesCount = $counts['notes'];
$relationshipsCount = $counts['relations'];
$counts = countNotes($notesOpinions);
$notesOpinionCount = $counts['notesOpinions'];
$relationshipsCount = count($relationships);
?>
<?php if (empty($notes)): ?>
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer" onclick="openNotes(this)" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?php if (empty($notesOpinions) && empty($relationshipsCount)): ?>
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer node-opener-<?= $seed ?>" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?php else: ?>
<span class="label label-info useCursorPointer" onclick="openNotes(this)" >
<span class="label label-info useCursorPointer node-opener-<?= $seed ?>">
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?= $notesCount; ?>
<?= $notesOpinionCount; ?>
<i class="<?= $this->FontAwesome->getClass('project-diagram') ?> useCursorPointer" title="<?= __('Relationships for this UUID') ?>"></i>
<?= $relationshipsCount; ?>
</span>
<?php endif; ?>
<script>
var notes = <?= json_encode($notes) ?>;
function renderNote(note, relationship_related_object) {
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 ? '#0000ff' : '#000')
note.authors = Array.isArray(note.authors) ? note.authors.join(', ') : note.authors;
note._permissions = {
can_edit: true,
can_delete: true,
can_add: true,
}
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)
}
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 baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid
} else if (note.related_object_type == 'Object') {
return baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid
}
return '#'
}
function renderRelationshipEntryFromType(note, relationship_related_object) {
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)
return relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
}
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.Organisation.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.Organisation.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> \
</i><span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{=it.distribution_color}}">{{=it.distribution_text}}</span> \
<span class="action-button-container" style="margin-left: auto; display: flex; gap: 0.2rem;"> \
{{? it._permissions.can_add }} \
<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> \
{{?}} \
{{? it._permissions.can_add }} \
<span role="button" onclick="addNote(this, \'{{=it.uuid}}\', \'{{=it.note_type_name}}\')" title="<?= __('Add a note to this note') ?>"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._permissions.can_edit }} \
<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._permissions.can_delete }} \
<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="<?= $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;">{{=it.relationship_type}}</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<div style="margin-left: 0.5rem;">{{=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 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> \
')
function toggleNotes(clicked) {
var $container = $('.note-container-<?= $seed ?>')
$container.toggle()
}
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')
$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 adjustPopoverPosition() {
var $popover = $('.popover:last');
$popover.css('top', Math.max($popover.position().top, 50) + 'px')
}
var shortDist = <?= json_encode($shortDist) ?>;
(function() {
var notes = <?= json_encode($notesOpinions) ?>;
var relationships = <?= json_encode($relationships) ?>;
var relationship_related_object = <?= json_encode($related_objects) ?>;
var shortDist = <?= json_encode($shortDist) ?>;
var renderedNotes = null
var renderedNotes<?= $seed ?> = null
var nodeContainerTemplate = doT.template('\
<div> \
@ -287,9 +564,10 @@ $relationshipsCount = $counts['relations'];
<li class="active"><a href="#notes-<?= $seed ?>" data-toggle="tab"><?= __('Notes & Opinions') ?></a></li> \
<li><a href="#relationships-<?= $seed ?>" data-toggle="tab"><?= __('Relationships') ?></a></li> \
</ul> \
<div class="tab-content" style="padding: 0.25rem;"> \
<div class="tab-content" style="padding: 0.25rem; max-width: 992px; min-width: 400px;"> \
<div id="notes-<?= $seed ?>" class="tab-pane active"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_notes}}</div>\
' + noteFilteringTemplate + ' \
<div style="display: flex; flex-direction: column; gap: 0.5rem;" class="all-notes">{{=it.content_notes}}</div>\
</div> \
<div id="relationships-<?= $seed ?>" class="tab-pane"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships}}</div>\
@ -297,129 +575,28 @@ $relationshipsCount = $counts['relations'];
</div> \
</div> \
')
var baseNoteTemplate = doT.template('\
<div id="note-{{=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" \
> \
<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.Organisation.id}}.png" width="20" height="20" class="orgImg" onerror="this.remove()" alt="Organisation logo"></object> \
<span style="margin-left: 0rem; margin-right: 0.5rem;"> \
<span>{{=it.Organisation.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> \
</i><span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{=it.distribution_color}}">{{=it.distribution_text}}</span> \
<span class="action-button-container" style="margin-left: auto; display: flex; gap: 0.2rem;"> \
{{? it._permissions.can_add }} \
<span role="button" onclick="addOpinion(this, \'{{=it.uuid}}\')" title="<?= __('Add an opinion to this note') ?>"><i class="<?= $this->FontAwesome->getClass('gavel') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._permissions.can_add }} \
<span role="button" onclick="addNote(this, \'{{=it.uuid}}\')" title="<?= __('Add a note to this note') ?>"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._permissions.can_edit }} \
<span role="button" onclick="editNote(this, {{=it.id}})" title="<?= __('Edit this note') ?>"><i class="<?= $this->FontAwesome->getClass('edit') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._permissions.can_delete }} \
<span role="button" onclick="deleteNote(this, {{=it.id}})" title="<?= __('Delete this note') ?>" href="<?= $baseurl . $URL_DELETE ?>{{=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.analyst_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="<?= $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;">{{=it.relationship_type}}</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<div style="margin-left: 0.5rem;">{{=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 replyNoteTemplate = doT.template('\
<span class="reply-to-note-collapse-button" 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" 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 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 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 toggleNotes(clicked) {
var $container = $('.note-container-<?= $seed ?>')
$container.toggle()
}
function openNotes(clicked) {
openPopover(clicked, renderedNotes, undefined, undefined, function() {
openPopover(clicked, renderedNotes<?= $seed ?>, undefined, undefined, function() {
adjustPopoverPosition()
$(clicked).removeClass('have-a-popover') // avoid closing the popover if a confirm popover (like the delete one) is called
})
}
function adjustPopoverPosition() {
var $popover = $('.popover');
$popover.css('top', Math.max($popover.position().top, 50) + 'px')
}
function renderNotes(notes) {
function renderNotes(notes, relationship_related_object) {
var renderedNotesArray = []
if (notes.length == 0) {
var emptyHtml = '<span style="text-align: center; color: #777;"><?= __('No notes for this UUID.') ?></span>'
renderedNotesArray.push(emptyHtml)
} else {
notes.forEach(function(note) {
var noteHtml = renderNote(note)
var noteHtml = renderNote(note, relationship_related_object)
if (note.notes) { // The notes has more notes attached
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.notes)})
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)})
}
renderedNotesArray.push(noteHtml)
@ -428,135 +605,63 @@ $relationshipsCount = $counts['relations'];
return renderedNotesArray.join('')
}
function renderNote(note) {
note.modified_relative = note.modified.date ? moment(note.modified.date).fromNow() : note.modified
note.created_relative = note.created.date ? moment(note.created.date).fromNow() : note.created
note.modified = note.modified.date ? (new Date(note.modified.date)).toLocaleString() : note.modified
note.created = note.created.date ? (new Date(note.created.date)).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 ? '#0000ff' : '#000')
note.authors = Array.isArray(note.authors) ? note.authors.join(', ') : note.authors;
note._permissions = {
can_edit: true,
can_delete: true,
can_add: true,
}
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)
}
var noteHtml = baseNoteTemplate(note)
return noteHtml
}
function renderAllNotesWithForm() {
renderedNotes = nodeContainerTemplate({
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2})) + addNoteButton,
content_relationships: renderNotes(notes.filter(function(note) { return note.note_type == 2})) + addRelationshipButton,
function renderAllNotesWithForm(relationship_related_object) {
var buttonContainer = '<div style="margin-top: 0.5rem">' + addNoteButton + addOpinionButton + '</div>'
renderedNotes<?= $seed ?> = nodeContainerTemplate({
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2}), relationship_related_object) + buttonContainer,
content_relationships: renderNotes(relationships, relationship_related_object) + addRelationshipButton,
})
}
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 baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid
} else if (note.related_object_type == 'Object') {
return baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid
}
return '#'
function registerListeners() {
$('.node-opener-<?= $seed ?>').click(function() {
openNotes(this)
})
}
function renderRelationshipEntryFromType(note) {
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> \
')
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('\
<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.url}}">{{=it.event.info}}</a></span> \
</span></span> \
</span> \
</span> \
')
} else if (note.related_object_type == 'Attribute' && relationship_related_object.Attribute[note.related_object_uuid]) {
note.attribute = relationship_related_object.Attribute[note.related_object_uuid]
if (note.attribute.object_relation !== undefined && note.attribute.object_relation !== null) {
template = doT.template('\
<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}}">{{=it.attribute.value}}</a></span></span> \
</span> \
')
} else if (relationship_related_object.Attribute[note.related_object_uuid]) {
template = doT.template('\
<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}}">{{=it.attribute.value}}</a></span></span></span> \
</span> \
</span> \
')
}
} else if (note.related_object_type == 'Object') {
note.object = relationship_related_object.Object[note.related_object_uuid]
template = doT.template('\
<i class="<?= $this->FontAwesome->getClass('cubes') ?>" title="<?= __('Object') ?>"></i> \
<span class="misp-element-wrapper object"> \
<span class="bold"> \
<span class="obj-type"><span>{{=it.object.type}}</span></span> \
<span class="blue"><span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{=it.url}}">{{=it.object.value}}</a></span></span></span> \
</span> \
</span> \
')
}
note.url = getURLFromRelationship(note)
contentHtml = template(note)
return relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
}
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>'
$(document).ready(function() {
renderAllNotesWithForm(relationship_related_object)
registerListeners()
})
})()
function createNewNote(clicked, object_type, object_uuid) {
note_type = 0;
openGenericModal(baseurl + '<?= $URL_ADD ?>' + object_type + '/' + object_uuid + '/' + note_type)
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 = 2;
openGenericModal(baseurl + '<?= $URL_ADD ?>' + object_type + '/' + object_uuid + '/' + note_type)
note_type = 'Relationship';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
}
function addNote(clicked, note_uuid) {
object_type = 'note';
note_type = 0;
openGenericModal(baseurl + '<?= $URL_ADD ?>' + object_type + '/' + note_uuid + '/' + note_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';
note_type = 1;
openGenericModal(baseurl + '<?= $URL_ADD ?>' + object_type + '/' + note_uuid + '/' + note_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) {
openGenericModal(baseurl + '<?= $URL_EDIT ?>' + note_id)
function editNote(clicked, note_id, note_type) {
openGenericModal(baseurl + '<?= $URL_EDIT ?>' + note_type + '/' + note_id)
}
function deleteNote(clicked, note_id) {
@ -566,14 +671,20 @@ $relationshipsCount = $counts['relations'];
popoverConfirm(clicked, '<?= __('Confirm deletion of this note') ?>', undefined, deletionSuccessCallback)
}
function registerListeners() {
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);
}
}
$(document).ready(function() {
renderAllNotesWithForm()
registerListeners()
})
</script>
<style>
@ -644,8 +755,11 @@ if(!function_exists("genStyleForOpinionNotes")) {
function genStyleForOpinionNotes($notes) {
foreach ($notes as $note) {
genStyleForOpinionNote($note);
if (!empty($note['notes'])) {
genStyleForOpinionNotes($note['notes']);
if (!empty($note['Note'])) {
genStyleForOpinionNotes($note['Note']);
}
if (!empty($note['Opinion'])) {
genStyleForOpinionNotes($note['Opinion']);
}
}
}
@ -659,20 +773,20 @@ if(!function_exists("genStyleForOpinionNote")) {
$opinion = min(100, max(0, intval($note['opinion'])));
?>
#note-<?= $note['id'] ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
#Opinion-<?= $note['id'] ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
opacity: 0;
}
#note-<?= $note['id'] ?> .opinion-gradient-dot {
#Opinion-<?= $note['id'] ?> .opinion-gradient-dot {
left: calc(<?= $opinion ?>% - 6px);
background-color: <?= $opinion == 50 ? '#555' : $opinion_color_scale_100[$opinion] ?>;
}
<?php if ($opinion >= 50): ?>
#note-<?= $note['id'] ?> .opinion-gradient-positive {
#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: ?>
#note-<?= $note['id'] ?> .opinion-gradient-negative {
#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%);
}
@ -682,7 +796,7 @@ if(!function_exists("genStyleForOpinionNote")) {
}
}
genStyleForOpinionNotes($notes)
genStyleForOpinionNotes($notesOpinionsRelationships)
?>
</style>

View File

@ -0,0 +1,176 @@
<?php
$seed = mt_rand();
$params['type'] = 'number';
$params['min'] = 0;
$params['max'] = 100;
$params['class'] .= ' opinion-' . $seed;
echo $this->Form->input($fieldData['field'], $params);
?>
<script>
var opinionGradient<?= $seed ?> = '\
<div class="opinion-gradient-container" style="width: 10rem; height: 6px; position: relative;">\
<div class="opinion-gradient opinion-gradient-negative"></div> \
<div class="opinion-gradient opinion-gradient-positive"></div> \
<input type="range" min="0" max="100" value="50" class="slider" id="opinion-slider">\
</div> \
'
var opinionTemplate<?= $seed ?> = '\
<div class="main-container" style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> 50 /100"> \
' + opinionGradient<?= $seed ?> + ' \
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;"> \
<b class="opinion-text" style="margin-left: 0.5rem; color: #333"></b> \
<b class="opinion-value" style="margin-left: 0.25rem; color: #333"></b> \
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span> \
</span> \
</div> \
'
var opinionColorScale = ['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)'];
$(document).ready(function() {
initOpinionSlider()
})
function getOpinionColor(opinion) {
return opinion == 50 ? '#333' : ( opinion > 50 ? '#468847' : '#b94a48');
}
function getOpinionText(opinion) {
return (opinion >= 81) ? '<?= __("Strongly Agree") ?>' : ((opinion >= 61) ? '<?= __("Agree") ?>' : ((opinion >= 41) ? '<?= __("Neutral") ?>' : ((opinion >= 21) ? '<?= __("Disagree") ?>' : '<?= __("Strongly Disagree") ?>')))
}
function setOpinionLevel(opinion) {
opinion = Number.parseInt(opinion)
var $formContainer = $('.opinion-<?= $seed ?>').parent()
var $mainContainer = $formContainer.find('.main-container')
var $gradientContainer = $formContainer.find('.opinion-gradient-container')
var $opinionSlider = $gradientContainer.find('#opinion-slider')
var backgroundColor = getOpinionColor(opinion)
var backgroundColorDot = opinion == 50 ? '#555' : opinionColorScale[opinion]
$mainContainer.attr('title', '<?= __('Opinion:') ?> ' + opinion + ' /100')
$mainContainer.find('.opinion-text')
.css('color', backgroundColor)
.text(getOpinionText(opinion))
$mainContainer.find('.opinion-value')
.css('color', backgroundColor)
.text(opinion)
if (opinion >= 50) {
var opinionMask = Math.abs(-50 + opinion)*2
$gradientContainer.find('.opinion-gradient-negative').css({
'opacity': 0,
'-webkit-mask-image': 'unset',
'mask-image': 'unset',
})
$gradientContainer.find('.opinion-gradient-positive').css({
'opacity': 1,
'-webkit-mask-image': 'linear-gradient(90deg, black 0 ' + opinionMask + '%, transparent ' + opinionMask + '% 100%)',
'mask-image': 'linear-gradient(90deg, black 0 ' + opinionMask + '%, transparent ' + opinionMask + '% 100%)',
})
} else {
var opinionMask = 100-(Math.abs(-50 + opinion)*2)
$gradientContainer.find('.opinion-gradient-negative').css({
'opacity': 1,
'-webkit-mask-image': 'linear-gradient(90deg, transparent 0 ' + opinionMask + '%, black ' + opinionMask + '% 100%)',
'mask-image': 'linear-gradient(90deg, transparent 0 ' + opinionMask + '%, black ' + opinionMask + '% 100%)',
})
$gradientContainer.find('.opinion-gradient-positive').css({
'opacity': 0,
'-webkit-mask-image': 'unset',
'mask-image': 'unset'
})
}
$opinionSlider.val(opinion)
$opinionSlider[0].style.setProperty('--color', backgroundColorDot);
$('input#OpinionOpinion').val(opinion)
}
function genSlider() {
var $div = $('<div style="display: inline-block;"></div>')
var $opinionTemplate = $(opinionTemplate<?= $seed ?>)
var $div = $div.append($opinionTemplate)
return $div
}
function initOpinionSlider() {
var $input = $('.opinion-<?= $seed ?>')
$input.css({
'width': '2.5rem',
'margin': '0 0.5rem 0 0',
})
$input.parent().append(genSlider())
var currentOpinionValue = !Number.isNaN(Number.parseInt($input.val())) ? Number.parseInt($input.val()) : 50
setOpinionLevel(currentOpinionValue)
$('.opinion-<?= $seed ?>').parent().find('#opinion-slider')
.on('input', function(e) {
setOpinionLevel(this.value)
})
$input.on('input', function(e) {
setOpinionLevel(this.value)
})
}
</script>
<style>
.opinion-gradient-container {
display: flex;
position: relative;
background: #ccc;
border-radius: 3px;
}
.opinion-gradient {
display: inline-block;
position: relative;
height: 100%;
width: 50%;
}
.opinion-gradient-positive {
border-radius: 0 3px 3px 0;
background-image: linear-gradient(90deg, rgb(237, 212, 0), rgb(236, 211, 0), rgb(234, 211, 0), rgb(233, 210, 0), rgb(231, 210, 0), rgb(230, 209, 1), rgb(229, 209, 1), rgb(227, 208, 1), rgb(226, 208, 1), rgb(224, 207, 1), rgb(223, 207, 1), rgb(222, 206, 1), rgb(220, 206, 1), rgb(219, 205, 1), rgb(218, 204, 1), rgb(216, 204, 2), rgb(215, 203, 2), rgb(213, 203, 2), rgb(212, 202, 2), rgb(211, 202, 2), rgb(209, 201, 2), rgb(208, 201, 2), rgb(206, 200, 2), rgb(205, 200, 2), rgb(204, 199, 2), rgb(202, 199, 2), rgb(201, 198, 3), rgb(199, 197, 3), rgb(198, 197, 3), rgb(197, 196, 3), rgb(195, 196, 3), rgb(194, 195, 3), rgb(192, 195, 3), rgb(191, 194, 3), rgb(189, 194, 3), rgb(188, 193, 3), rgb(186, 193, 3), rgb(185, 192, 4), rgb(183, 192, 4), rgb(182, 191, 4), rgb(180, 190, 4), rgb(179, 190, 4), rgb(177, 189, 4), rgb(175, 189, 4), rgb(174, 188, 4), rgb(173, 188, 4), rgb(171, 187, 4), rgb(170, 186, 4), rgb(168, 186, 4), rgb(167, 185, 4), rgb(165, 185, 4), rgb(164, 184, 4), rgb(162, 183, 4), rgb(161, 183, 4), rgb(159, 182, 4), rgb(158, 182, 4), rgb(156, 181, 4), rgb(154, 180, 4), rgb(153, 180, 4), rgb(151, 179, 4), rgb(149, 179, 5), rgb(148, 178, 5), rgb(146, 178, 5), rgb(144, 177, 5), rgb(143, 177, 5), rgb(141, 176, 5), rgb(139, 175, 5), rgb(138, 175, 5), rgb(136, 174, 5), rgb(134, 173, 5), rgb(133, 173, 5), rgb(131, 172, 5), rgb(130, 172, 5), rgb(128, 171, 5), rgb(126, 170, 5), rgb(125, 170, 5), rgb(123, 169, 5), rgb(121, 168, 5), rgb(119, 168, 5), rgb(117, 167, 5), rgb(115, 167, 5), rgb(113, 166, 6), rgb(112, 165, 6), rgb(110, 165, 6), rgb(108, 164, 6), rgb(106, 163, 6), rgb(104, 163, 6), rgb(102, 162, 6), rgb(100, 162, 6), rgb(98, 161, 6), rgb(96, 160, 6), rgb(94, 159, 6), rgb(92, 159, 6), rgb(90, 158, 6), rgb(88, 157, 6), rgb(86, 157, 6), rgb(84, 156, 6), rgb(82, 155, 6), rgb(80, 155, 6),rgb(78, 154, 6))
}
.opinion-gradient-negative {
border-radius: 3px 0 0 3px;
background-image: linear-gradient(90deg, rgb(164, 0, 0), rgb(165, 8, 0), rgb(166, 15, 0), rgb(167, 21, 0), rgb(169, 25, 0), rgb(170, 30, 0), rgb(171, 33, 0), rgb(172, 37, 0), rgb(173, 40, 0), rgb(174, 43, 0), rgb(175, 46, 0), rgb(176, 49, 0), rgb(177, 52, 0), rgb(178, 55, 0), rgb(179, 57, 0), rgb(180, 60, 0), rgb(181, 63, 0), rgb(182, 65, 0), rgb(183, 68, 0), rgb(184, 70, 0), rgb(186, 72, 0), rgb(187, 75, 0), rgb(188, 77, 0), rgb(189, 80, 0), rgb(190, 82, 0), rgb(190, 84, 0), rgb(191, 86, 0), rgb(192, 88, 0), rgb(193, 90, 0), rgb(194, 92, 0), rgb(195, 95, 0), rgb(196, 96, 0), rgb(197, 98, 0), rgb(197, 100, 0), rgb(198, 102, 0), rgb(199, 104, 0), rgb(200, 106, 0), rgb(201, 108, 0), rgb(201, 110, 0), rgb(202, 112, 0), rgb(203, 114, 0), rgb(204, 116, 0), rgb(204, 118, 0), rgb(205, 119, 0), rgb(206, 121, 0), rgb(207, 123, 0), rgb(208, 125, 0), rgb(208, 127, 0), rgb(209, 128, 0), rgb(210, 130, 0), rgb(210, 132, 0), rgb(211, 134, 0), rgb(212, 135, 0), rgb(212, 137, 0), rgb(213, 139, 0), rgb(214, 141, 0), rgb(214, 143, 0), rgb(215, 144, 0), rgb(216, 146, 0), rgb(216, 148, 0), rgb(217, 149, 0), rgb(217, 151, 0), rgb(218, 153, 0), rgb(219, 154, 0), rgb(219, 156, 0), rgb(220, 158, 0), rgb(220, 160, 0), rgb(221, 161, 0), rgb(222, 163, 0), rgb(222, 164, 0), rgb(223, 166, 0), rgb(223, 168, 0), rgb(224, 169, 0), rgb(225, 171, 0), rgb(225, 173, 0), rgb(226, 174, 0), rgb(226, 176, 0), rgb(227, 178, 0), rgb(227, 179, 0), rgb(227, 181, 0), rgb(228, 182, 0), rgb(228, 184, 0), rgb(229, 186, 0), rgb(229, 187, 0), rgb(230, 189, 0), rgb(230, 190, 0), rgb(231, 192, 0), rgb(231, 193, 0), rgb(232, 195, 0), rgb(232, 196, 0), rgb(233, 198, 0), rgb(233, 200, 0), rgb(234, 201, 0), rgb(234, 203, 0), rgb(235, 204, 0), rgb(235, 206, 0), rgb(236, 207, 0), rgb(236, 209, 0), rgb(237, 210, 0), rgb(237, 212, 0));
}
input#opinion-slider {
position: absolute;
width: 160px;
-webkit-appearance: none;
appearance: none;
height: 6px;
background: #ffffff00;
outline: none;
opacity: 0.8;
-webkit-transition: .2s;
transition: opacity .2s;
}
#opinion-slider:hover {
opacity: 1;
}
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
#opinion-slider::-webkit-slider-thumb {
border-radius: 50%;
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
box-shadow: 0 0 2px 0px #00000066;
background-color: var(--color, white);
cursor: pointer;
}
#opinion-slider::-moz-range-thumb {
border-radius: 50%;
width: 12px;
height: 12px;
box-shadow: 0 0 2px 0px #00000066;
background-color: var(--color, white);
cursor: pointer;
}
</style>

View File

@ -5,7 +5,18 @@
h($uuid)
);
$analyst_data = !empty($analyst_data) ? $analyst_data : [];
$object_uuid = !empty($object_uuid) ? $object_uuid : null;
$object_type = !empty($object_type) ? $object_type : null;
echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $analyst_data, 'object_uuid' => $object_uuid, 'object_type' => $object_type]);
if (!empty($field['object_type'])) {
$field['notes_path'] = !empty($field['notes_path']) ? $field['notes_path'] : 'Note';
$field['opinions_path'] = !empty($field['opinions_path']) ? $field['opinions_path'] : 'Opinion';
$field['relationships_path'] = !empty($field['relationships_path']) ? $field['relationships_path'] : 'Relationship';
$notes = !empty($field['notes']) ? $field['notes'] : Hash::extract($data, $field['notes_path']);
$opinions = !empty($field['opinions']) ? $field['opinions'] : Hash::extract($data, $field['opinions_path']);
$relationships = !empty($field['relationships']) ? $field['relationships'] : Hash::extract($data, $field['relationships_path']);
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $uuid,
'object_type' => $field['object_type']
]);
} else {
debug('Provide object type to access notes for that object');
}

View File

@ -2,7 +2,15 @@
$uuidHalfWidth = 3;
$shortUUID = sprintf('%s...%s', substr($uuid, 0, $uuidHalfWidth), substr($uuid, 36-$uuidHalfWidth, $uuidHalfWidth));
$notes = !empty($notes) ? $notes : [];
$object_type = !empty($object_type) ? $object_type : null;
echo sprintf('<span title="%s">%s</span>', $uuid, $shortUUID);
echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $notes, 'object_uuid' => $uuid, 'object_type' => $object_type]);
if (!empty($object_type)) {
$notes = !empty($notes) ? $notes : [];
$opinions = !empty($opinions) ? $opinions : [];
$relationships = !empty($relationships) ? $relationships : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships,],
'object_uuid' => $uuid,
'object_type' => $object_type
]);
}

View File

@ -18,6 +18,9 @@
'path' => 'Event.uuid',
'class' => '',
'type' => 'uuid',
'object_type' => 'Event',
'notes_path' => 'Note',
'opinions_path' => 'Opinion',
'action_buttons' => [
[
'url' => $baseurl . '/events/add/extends:' . h($event['Event']['uuid']),

View File

@ -1935,7 +1935,9 @@ function popoverConfirm(clicked, message, placement, callback) {
var href = $clicked.attr("href");
// Load form to get new token
fetchFormDataAjax(href, function (form) {
var $form = $(form);
var $formContainer = $(form);
var $form = $formContainer.is('form') ? $formContainer : $formContainer.find('form');
$clicked.popover('destroy');
xhr({
data: $form.serialize(),
success: function (data) {
@ -5568,9 +5570,13 @@ function loadClusterRelations(clusterId) {
}
}
function submitGenericFormInPlace(callback) {
function submitGenericFormInPlace(callback, forceApi=false) {
var $genericForm = $('.genericForm');
$.ajax({
ajaxOptions = {}
if (forceApi) {
ajaxOptions['headers'] = { Accept: "application/json" }
}
$.ajax(Object.assign({}, {
type: "POST",
url: $genericForm.attr('action'),
data: $genericForm.serialize(), // serializes the form's elements.
@ -5588,7 +5594,7 @@ function submitGenericFormInPlace(callback) {
$('#genericModal').modal();
},
error: xhrFailCallback,
});
}, ajaxOptions));
}
function openIdSelection(clicked, scope, action) {