mirror of https://github.com/MISP/MISP
Merge branch 'develop' of github.com:MISP/MISP into develop
commit
e29924b55d
|
@ -85,6 +85,7 @@ class ServerShell extends AppShell
|
|||
|
||||
$userId = $this->args[0];
|
||||
$user = $this->getUser($userId);
|
||||
Configure::write('CurrentUserId', $userId);
|
||||
|
||||
if (!empty($this->args[1])) {
|
||||
$technique = $this->args[1];
|
||||
|
@ -129,6 +130,7 @@ class ServerShell extends AppShell
|
|||
$user = $this->getUser($userId);
|
||||
$serverId = $this->args[1];
|
||||
$server = $this->getServer($serverId);
|
||||
Configure::write('CurrentUserId', $userId);
|
||||
if (!empty($this->args[2])) {
|
||||
$technique = $this->args[2];
|
||||
} else {
|
||||
|
@ -146,7 +148,7 @@ class ServerShell extends AppShell
|
|||
try {
|
||||
$result = $this->Server->pull($user, $technique, $server, $jobId, $force);
|
||||
if (is_array($result)) {
|
||||
$message = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4]);
|
||||
$message = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled, %s analyst data pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4], $result[5]);
|
||||
$this->Job->saveStatus($jobId, true, $message);
|
||||
} else {
|
||||
$message = __('ERROR: %s', $result);
|
||||
|
@ -169,6 +171,7 @@ class ServerShell extends AppShell
|
|||
$user = $this->getUser($userId);
|
||||
$serverId = $this->args[1];
|
||||
$server = $this->getServer($serverId);
|
||||
Configure::write('CurrentUserId', $userId);
|
||||
$technique = empty($this->args[2]) ? 'full' : $this->args[2];
|
||||
if (!empty($this->args[3])) {
|
||||
$jobId = $this->args[3];
|
||||
|
@ -203,6 +206,7 @@ class ServerShell extends AppShell
|
|||
$userId = $this->args[0];
|
||||
$user = $this->getUser($userId);
|
||||
|
||||
Configure::write('CurrentUserId', $userId);
|
||||
$technique = isset($this->args[1]) ? $this->args[1] : 'full';
|
||||
|
||||
$servers = $this->Server->find('list', array(
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
class AnalystDataBlocklistsController extends AppController
|
||||
{
|
||||
public $components = array('Session', 'RequestHandler', 'BlockList');
|
||||
|
||||
public $paginate = array(
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 entries <- no we won't, this is the max a user van view/page.
|
||||
'order' => array(
|
||||
'AnalystDataBlocklist.created' => 'DESC'
|
||||
),
|
||||
);
|
||||
|
||||
public function index()
|
||||
{
|
||||
$passedArgsArray = array();
|
||||
$passedArgs = $this->passedArgs;
|
||||
$params = array();
|
||||
$validParams = array('analyst_data_uuid', 'comment', 'analyst_data_info', 'analyst_data_orgc');
|
||||
foreach ($validParams as $validParam) {
|
||||
if (!empty($this->params['named'][$validParam])) {
|
||||
$params[$validParam] = $this->params['named'][$validParam];
|
||||
}
|
||||
}
|
||||
if (!empty($this->params['named']['searchall'])) {
|
||||
$params['AND']['OR'] = array(
|
||||
'analyst_data_uuid' => $this->params['named']['searchall'],
|
||||
'comment' => $this->params['named']['searchall'],
|
||||
'analyst_data_info' => $this->params['named']['searchall'],
|
||||
'analyst_data_orgc' => $this->params['named']['searchall']
|
||||
);
|
||||
}
|
||||
$this->set('passedArgs', json_encode($passedArgs));
|
||||
$this->set('passedArgsArray', $passedArgsArray);
|
||||
return $this->BlockList->index($this->_isRest(), $params);
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$this->set('action', 'add');
|
||||
return $this->BlockList->add($this->_isRest());
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$this->set('action', 'edit');
|
||||
return $this->BlockList->edit($this->_isRest(), $id);
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
if (Validation::uuid($id)) {
|
||||
$entry = $this->AnalystDataBlocklist->find('first', array(
|
||||
'conditions' => array('analyst_data_uuid' => $id)
|
||||
));
|
||||
if (empty($entry)) {
|
||||
throw new NotFoundException(__('Invalid blocklist entry'));
|
||||
}
|
||||
$id = $entry['AnalystDataBlocklist']['id'];
|
||||
}
|
||||
return $this->BlockList->delete($this->_isRest(), $id);
|
||||
}
|
||||
|
||||
public function massDelete()
|
||||
{
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
if (!isset($this->request->data['AnalystDataBlocklist'])) {
|
||||
$this->request->data = array('AnalystDataBlocklist' => $this->request->data);
|
||||
}
|
||||
$ids = $this->request->data['AnalystDataBlocklist']['ids'];
|
||||
$analyst_data_ids = json_decode($ids, true);
|
||||
if (empty($analyst_data_ids)) {
|
||||
throw new NotFoundException(__('Invalid Analyst Data IDs.'));
|
||||
}
|
||||
$result = $this->AnalystDataBlocklist->deleteAll(array('AnalystDataBlocklist.id' => $analyst_data_ids));
|
||||
if ($result) {
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveSuccessResponse('AnalystDataBlocklist', 'Deleted', $ids, $this->response->type());
|
||||
} else {
|
||||
$this->Flash->success('Blocklist entry removed');
|
||||
$this->redirect(array('controller' => 'AnalystDataBlocklist', 'action' => 'index'));
|
||||
}
|
||||
} else {
|
||||
$error = __('Failed to delete Analyst Data from AnalystDataBlocklist. Error: ') . PHP_EOL . h($result);
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveFailResponse('AnalystDataBlocklist', 'Deleted', false, $error, $this->response->type());
|
||||
} else {
|
||||
$this->Flash->error($error);
|
||||
$this->redirect(array('controller' => 'AnalystDataBlocklists', 'action' => 'index'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$ids = json_decode($this->request->query('ids'), true);
|
||||
if (empty($ids)) {
|
||||
throw new NotFoundException(__('Invalid analyst data IDs.'));
|
||||
|
||||
}
|
||||
$this->set('analyst_data_ids', $ids);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,327 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
class AnalystDataController extends AppController
|
||||
{
|
||||
|
||||
public $components = ['Session', 'RequestHandler'];
|
||||
|
||||
public $paginate = [
|
||||
'limit' => 60,
|
||||
'order' => []
|
||||
];
|
||||
|
||||
public $uses = [
|
||||
'Opinion',
|
||||
'Note',
|
||||
'Relationship'
|
||||
];
|
||||
|
||||
private $__valid_types = [
|
||||
'Opinion',
|
||||
'Note',
|
||||
'Relationship'
|
||||
];
|
||||
|
||||
// public $modelSelection = 'Note';
|
||||
|
||||
private function _setViewElements()
|
||||
{
|
||||
$dropdownData = [];
|
||||
$this->loadModel('Event');
|
||||
$dropdownData['distributionLevels'] = $this->Event->distributionLevels;
|
||||
$this->set('initialDistribution', Configure::read('MISP.default_event_distribution'));
|
||||
$dropdownData['sgs'] = $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
|
||||
$dropdownData['valid_targets'] = array_combine($this->AnalystData->valid_targets, $this->AnalystData->valid_targets);
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('modelSelection', $this->modelSelection);
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
App::uses('LanguageRFC5646Tool', 'Tools');
|
||||
$this->set('languageRFC5646', ['' => __('- No language -'), LanguageRFC5646Tool::getLanguages()]);
|
||||
}
|
||||
|
||||
public function add($type = 'Note', $object_uuid = null, $object_type = null)
|
||||
{
|
||||
$this->__typeSelector($type);
|
||||
if (!empty($object_uuid)) {
|
||||
$this->request->data[$this->modelSelection]['object_uuid'] = $object_uuid;
|
||||
}
|
||||
if (!empty($object_type)) {
|
||||
$this->request->data[$this->modelSelection]['object_type'] = $object_type;
|
||||
}
|
||||
|
||||
if (empty($this->request->data[$this->modelSelection]['object_type']) && !empty($this->request->data[$this->modelSelection]['object_uuid'])) {
|
||||
$this->request->data[$this->modelSelection]['object_type'] = $this->AnalystData->deduceType($object_uuid);
|
||||
}
|
||||
$params = [];
|
||||
$this->CRUD->add($params);
|
||||
if ($this->restResponsePayload) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->_setViewElements();
|
||||
if ($type == 'Relationship') {
|
||||
$this->set('existingRelations', $this->AnalystData->getExistingRelationships());
|
||||
}
|
||||
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'add_' . strtolower($type)));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function edit($type = 'Note', $id)
|
||||
{
|
||||
if ($type === 'all' && Validation::uuid($id)) {
|
||||
$this->loadModel('AnalystData');
|
||||
$type = $this->AnalystData->deduceType($id);
|
||||
}
|
||||
$this->__typeSelector($type);
|
||||
if (!is_numeric($id) && Validation::uuid($id)) {
|
||||
$id = $this->AnalystData->getIDFromUUID($type, $id);
|
||||
}
|
||||
|
||||
$this->set('id', $id);
|
||||
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
|
||||
$params = [
|
||||
'conditions' => $conditions,
|
||||
'afterFind' => function(array $analystData): array {
|
||||
$canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection);
|
||||
if (!$canEdit) {
|
||||
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
|
||||
}
|
||||
return $analystData;
|
||||
},
|
||||
'beforeSave' => function(array $analystData): array {
|
||||
$analystData[$this->modelSelection]['modified'] = date ('Y-m-d H:i:s');
|
||||
return $analystData;
|
||||
}
|
||||
];
|
||||
$this->CRUD->edit($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->_setViewElements();
|
||||
if ($type == 'Relationship') {
|
||||
$this->set('existingRelations', $this->AnalystData->getExistingRelationships());
|
||||
}
|
||||
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'edit'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function delete($type = 'Note', $id, $hard=true)
|
||||
{
|
||||
if ($type === 'all' && Validation::uuid($id)) {
|
||||
$this->loadModel('AnalystData');
|
||||
$type = $this->AnalystData->deduceType($id);
|
||||
}
|
||||
$this->__typeSelector($type);
|
||||
if (!is_numeric($id) && Validation::uuid($id)) {
|
||||
$id = $this->AnalystData->getIDFromUUID($type, $id);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'afterFind' => function(array $analystData) {
|
||||
$canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection);
|
||||
if (!$canEdit) {
|
||||
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
|
||||
}
|
||||
return $analystData;
|
||||
},
|
||||
'afterDelete' => function($deletedAnalystData) use ($hard) {
|
||||
if (empty($hard)) {
|
||||
return;
|
||||
}
|
||||
$type = $this->AnalystData->deduceAnalystDataType($deletedAnalystData);
|
||||
$info = '- Unsupported analyst type -';
|
||||
if ($type === 'Note') {
|
||||
$info = $deletedAnalystData[$type]['note'];
|
||||
} else if ($type === 'Opinion') {
|
||||
$info = sprintf('%s/100 :: %s', $deletedAnalystData[$type]['opinion'], $deletedAnalystData[$type]['comment']);
|
||||
} else if ($type === 'Relationship') {
|
||||
$info = sprintf('-- %s --> %s :: %s', $deletedAnalystData[$type]['relationship_type'] ?? '[undefined]', $deletedAnalystData[$type]['related_object_type'], $deletedAnalystData[$type]['related_object_uuid']);
|
||||
}
|
||||
$this->AnalystDataBlocklist = ClassRegistry::init('AnalystDataBlocklist');
|
||||
$this->AnalystDataBlocklist->create();
|
||||
if (!empty($deletedAnalystData[$type]['orgc_uuid'])) {
|
||||
if (!empty($deletedAnalystData[$type]['Orgc'])) {
|
||||
$orgc = $deletedAnalystData[$type];
|
||||
} else {
|
||||
$orgc = $this->Orgc->find('first', array(
|
||||
'conditions' => ['Orgc.uuid' => $deletedAnalystData[$type]['orgc_uuid']],
|
||||
'recursive' => -1,
|
||||
'fields' => ['Orgc.name'],
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$orgc = ['Orgc' => ['name' => 'MISP']];
|
||||
}
|
||||
$this->AnalystDataBlocklist->save(['analyst_data_uuid' => $deletedAnalystData[$type]['uuid'], 'analyst_data_info' => $info, 'analyst_data_orgc' => $orgc['Orgc']['name']]);
|
||||
}
|
||||
];
|
||||
$this->CRUD->delete($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function view($type = 'Note', $id)
|
||||
{
|
||||
if ($type === 'all' && Validation::uuid($id)) {
|
||||
$this->loadModel('AnalystData');
|
||||
$type = $this->AnalystData->getAnalystDataTypeFromUUID($id);
|
||||
}
|
||||
$this->__typeSelector($type);
|
||||
if (!is_numeric($id) && Validation::uuid($id)) {
|
||||
$id = $this->AnalystData->getIDFromUUID($type, $id);
|
||||
}
|
||||
|
||||
if (!$this->IndexFilter->isRest()) {
|
||||
$this->AnalystData->fetchRecursive = true;
|
||||
}
|
||||
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
|
||||
$this->CRUD->view($id, [
|
||||
'conditions' => $conditions,
|
||||
'contain' => ['Org', 'Orgc'],
|
||||
'afterFind' => function(array $analystData) {
|
||||
if (!$this->request->is('ajax')) {
|
||||
unset($analystData[$this->modelSelection]['_canEdit']);
|
||||
}
|
||||
return $analystData;
|
||||
}
|
||||
]);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->loadModel('Event');
|
||||
$this->_setViewElements();
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
$this->set('shortDist', $this->Event->shortDist);
|
||||
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'view'));
|
||||
$this->render('view');
|
||||
}
|
||||
|
||||
public function index($type = 'Note')
|
||||
{
|
||||
$this->__typeSelector($type);
|
||||
|
||||
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
|
||||
$params = [
|
||||
'filters' => ['uuid', 'target_object'],
|
||||
'quickFilters' => ['name'],
|
||||
'conditions' => $conditions,
|
||||
'afterFind' => function(array $data) {
|
||||
foreach ($data as $i => $analystData) {
|
||||
if (!$this->request->is('ajax')) {
|
||||
unset($analystData[$this->modelSelection]['_canEdit']);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
];
|
||||
$this->CRUD->index($params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->_setViewElements();
|
||||
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'index'));
|
||||
}
|
||||
|
||||
public function getRelatedElement($type, $uuid)
|
||||
{
|
||||
$this->__typeSelector('Relationship');
|
||||
$data = $this->AnalystData->getRelatedElement($this->Auth->user(), $type, $uuid);
|
||||
return $this->RestResponse->viewData($data, 'json');
|
||||
}
|
||||
|
||||
public function getChildren($type = 'Note', $uuid, $depth=2)
|
||||
{
|
||||
$this->__typeSelector($type);
|
||||
$data = $this->AnalystData->getChildren($this->Auth->user(), $uuid, $depth);
|
||||
return $this->RestResponse->viewData($data, 'json');
|
||||
}
|
||||
|
||||
public function filterAnalystDataForPush()
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException(__('This function is only accessible via POST requests.'));
|
||||
}
|
||||
|
||||
$this->loadModel('AnalystData');
|
||||
|
||||
$allIncomingAnalystData = $this->request->data;
|
||||
$allData = $this->AnalystData->filterAnalystDataForPush($allIncomingAnalystData);
|
||||
|
||||
return $this->RestResponse->viewData($allData, $this->response->type());
|
||||
}
|
||||
|
||||
public function indexMinimal()
|
||||
{
|
||||
$this->loadModel('AnalystData');
|
||||
$filters = [];
|
||||
if ($this->request->is('post')) {
|
||||
$filters = $this->request->data;
|
||||
}
|
||||
$options = [];
|
||||
if (!empty($filters['orgc_name'])) {
|
||||
$orgcNames = $filters['orgc_name'];
|
||||
if (!is_array($orgcNames)) {
|
||||
$orgcName = [$orgcNames];
|
||||
}
|
||||
$filterName = 'orgc_uuid';
|
||||
foreach ($orgcNames as $orgcName) {
|
||||
if ($orgcName[0] === '!') {
|
||||
$orgc = $this->AnalystData->Orgc->fetchOrg(substr($orgcName, 1));
|
||||
if ($orgc === false) {
|
||||
continue;
|
||||
}
|
||||
$options[]['AND'][] = ["{$filterName} !=" => $orgc['uuid']];
|
||||
} else {
|
||||
$orgc = $this->AnalystData->Orgc->fetchOrg($orgcName);
|
||||
if ($orgc === false) {
|
||||
continue;
|
||||
}
|
||||
$options['OR'][] = [$filterName => $orgc['uuid']];
|
||||
}
|
||||
}
|
||||
}
|
||||
$allData = $this->AnalystData->indexMinimal($this->Auth->user(), $options);
|
||||
|
||||
return $this->RestResponse->viewData($allData, $this->response->type());
|
||||
}
|
||||
|
||||
public function pushAnalystData()
|
||||
{
|
||||
if (!$this->Auth->user()['Role']['perm_sync'] || !$this->Auth->user()['Role']['perm_analyst_data']) {
|
||||
throw new MethodNotAllowedException(__('You do not have the permission to do that.'));
|
||||
}
|
||||
if (!$this->_isRest()) {
|
||||
throw new MethodNotAllowedException(__('This action is only accessible via a REST request.'));
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
$this->loadModel('AnalystData');
|
||||
$analystData = $this->request->data;
|
||||
$saveResult = $this->AnalystData->captureAnalystData($this->Auth->user(), $analystData);
|
||||
$messageInfo = __('%s imported, %s ignored, %s failed. %s', $saveResult['imported'], $saveResult['ignored'], $saveResult['failed'], !empty($saveResult['errors']) ? implode(', ', $saveResult['errors']) : '');
|
||||
if ($saveResult['success']) {
|
||||
$message = __('Analyst Data imported. ') . $messageInfo;
|
||||
return $this->RestResponse->saveSuccessResponse('AnalystData', 'pushAnalystData', false, $this->response->type(), $message);
|
||||
} else {
|
||||
$message = __('Could not import analyst data. ') . $messageInfo;
|
||||
return $this->RestResponse->saveFailResponse('AnalystData', 'pushAnalystData', false, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return $vt;
|
||||
}
|
||||
}
|
||||
throw new MethodNotAllowedException(__('Invalid type.'));
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class AppController extends Controller
|
|||
|
||||
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
|
||||
|
||||
private $__queryVersion = '158';
|
||||
private $__queryVersion = '159';
|
||||
public $pyMispVersion = '2.4.185';
|
||||
public $phpmin = '7.2';
|
||||
public $phprec = '7.4';
|
||||
|
@ -306,6 +306,7 @@ class AppController extends Controller
|
|||
$this->set('isAclTagger', $role['perm_tagger']);
|
||||
$this->set('isAclGalaxyEditor', !empty($role['perm_galaxy_editor']));
|
||||
$this->set('isAclSighting', $role['perm_sighting'] ?? false);
|
||||
$this->set('isAclAnalystDataCreator', $role['perm_analyst_data'] ?? false);
|
||||
$this->set('aclComponent', $this->ACL);
|
||||
$this->userRole = $role;
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Framework\MockObject\InvalidMethodNameException;
|
||||
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
class CollectionElementsController extends AppController
|
||||
{
|
||||
|
||||
public $components = ['Session', 'RequestHandler'];
|
||||
|
||||
public $paginate = [
|
||||
'limit' => 60,
|
||||
'order' => []
|
||||
];
|
||||
|
||||
public $uses = [
|
||||
];
|
||||
|
||||
public function add($collection_id)
|
||||
{
|
||||
$this->CollectionElement->Collection->current_user = $this->Auth->user();
|
||||
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), intval($collection_id))) {
|
||||
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
|
||||
}
|
||||
$this->CRUD->add([
|
||||
'beforeSave' => function (array $collectionElement) use ($collection_id) {
|
||||
$collectionElement['CollectionElement']['collection_id'] = intval($collection_id);
|
||||
return $collectionElement;
|
||||
}
|
||||
]);
|
||||
if ($this->restResponsePayload) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$dropdownData = [
|
||||
'types' => array_combine($this->CollectionElement->valid_types, $this->CollectionElement->valid_types)
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'add_element'));
|
||||
}
|
||||
|
||||
public function delete($element_id)
|
||||
{
|
||||
$collectionElement = $this->CollectionElement->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'CollectionElement.id' => $element_id
|
||||
]
|
||||
]);
|
||||
$collection_id = $collectionElement['CollectionElement']['collection_id'];
|
||||
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), $collection_id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
|
||||
}
|
||||
$this->CRUD->delete($element_id);
|
||||
if ($this->restResponsePayload) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function index($collection_id)
|
||||
{
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'index'));
|
||||
if (!$this->CollectionElement->Collection->mayView($this->Auth->user('id'), intval($collection_id))) {
|
||||
throw new NotFoundException(__('Invalid collection or no access.'));
|
||||
}
|
||||
$params = [
|
||||
'filters' => ['uuid', 'type', 'name'],
|
||||
'quickFilters' => ['name'],
|
||||
'conditions' => ['collection_id' => $collection_id]
|
||||
];
|
||||
$this->loadModel('Event');
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
$this->CRUD->index($params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function addElementToCollection($element_type, $element_uuid)
|
||||
{
|
||||
if ($this->request->is('get')) {
|
||||
$validCollections = $this->CollectionElement->Collection->find('list', [
|
||||
'recursive' => -1,
|
||||
'fields' => ['Collection.id', 'Collection.name'],
|
||||
'conditions' => ['Collection.orgc_id' => $this->Auth->user('org_id')]
|
||||
]);
|
||||
if (empty($validCollections)) {
|
||||
if ($this->request->is('ajax')) {
|
||||
return $this->redirect(['controller' => 'collections', 'action' => 'add']);
|
||||
}
|
||||
throw new NotFoundException(__('You don\'t have any collections yet. Make sure you create one first before you can start adding elements.'));
|
||||
}
|
||||
$dropdownData = [
|
||||
'collections' => $validCollections
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
} else if ($this->request->is('post')) {
|
||||
if (!isset($this->request->data['CollectionElement'])) {
|
||||
$this->request->data = ['CollectionElement' => $this->request->data];
|
||||
}
|
||||
if (!isset($this->request->data['CollectionElement']['collection_id'])) {
|
||||
throw new NotFoundException(__('No collection_id specified.'));
|
||||
}
|
||||
$collection_id = intval($this->request->data['CollectionElement']['collection_id']);
|
||||
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), $collection_id)) {
|
||||
throw new NotFoundException(__('Invalid collection or not authorized.'));
|
||||
}
|
||||
$description = empty($this->request->data['CollectionElement']['description']) ? '' : $this->request->data['CollectionElement']['description'];
|
||||
$dataToSave = [
|
||||
'CollectionElement' => [
|
||||
'element_uuid' => $element_uuid,
|
||||
'element_type' => $element_type,
|
||||
'description' => $description,
|
||||
'collection_id' => $collection_id
|
||||
]
|
||||
];
|
||||
$this->CollectionElement->create();
|
||||
$error = '';
|
||||
try {
|
||||
$result = $this->CollectionElement->save($dataToSave);
|
||||
} catch (PDOException $e) {
|
||||
if ($e->errorInfo[0] == 23000) {
|
||||
$error = __(' Element already in Collection.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$message = __('Element added to the Collection.');
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->RestResponse->saveSuccessResponse('CollectionElements', 'addElementToCollection', false, $this->response->type(), $message);
|
||||
} else {
|
||||
$this->Flash->success($message);
|
||||
$this->redirect(Router::url($this->referer(), true));
|
||||
}
|
||||
} else {
|
||||
$message = __('Element could not be added to the Collection.%s', $error);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->RestResponse->saveFailResponse('CollectionElements', 'addElementToCollection', false, $message, $this->response->type());
|
||||
} else {
|
||||
$this->Flash->error($message);
|
||||
$this->redirect(Router::url($this->referer(), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
class CollectionsController extends AppController
|
||||
{
|
||||
|
||||
public $components = ['Session', 'RequestHandler'];
|
||||
|
||||
public $paginate = [
|
||||
'limit' => 60,
|
||||
'order' => []
|
||||
];
|
||||
|
||||
public $uses = [
|
||||
];
|
||||
|
||||
private $valid_types = [
|
||||
'campaign',
|
||||
'intrusion_set',
|
||||
'named_threat',
|
||||
'other',
|
||||
'research'
|
||||
];
|
||||
|
||||
public function add()
|
||||
{
|
||||
$this->Collection->current_user = $this->Auth->user();
|
||||
$params = [];
|
||||
if ($this->request->is('post')) {
|
||||
$data = $this->request->data;
|
||||
$params = [
|
||||
'afterSave' => function (array $collection) use ($data) {
|
||||
$this->Collection->CollectionElement->captureElements($collection);
|
||||
return $collection;
|
||||
}
|
||||
];
|
||||
}
|
||||
$this->CRUD->add($params);
|
||||
if ($this->restResponsePayload) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'add'));
|
||||
$this->loadModel('Event');
|
||||
$dropdownData = [
|
||||
'types' => array_combine($this->valid_types, $this->valid_types),
|
||||
'distributionLevels' => $this->Event->distributionLevels,
|
||||
'sgs' => $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1)
|
||||
];
|
||||
$this->set('initialDistribution', Configure::read('MISP.default_event_distribution'));
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$this->Collection->current_user = $this->Auth->user();
|
||||
if (!$this->Collection->mayModify($this->Auth->user('id'), $id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
|
||||
}
|
||||
$params = [];
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
$oldCollection = $this->Collection->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['Collection.id' => intval($id)]
|
||||
]);
|
||||
if (empty($oldCollection)) {
|
||||
throw new NotFoundException(__('Invalid collection.'));
|
||||
}
|
||||
if (empty($this->request->data['Collection'])) {
|
||||
$this->request->data = ['Collection' => $this->request->data];
|
||||
}
|
||||
$data = $this->request->data;
|
||||
if (
|
||||
isset($data['Collection']['modified']) &&
|
||||
$data['Collection']['modified'] <= $oldCollection['Collection']['modified']
|
||||
) {
|
||||
throw new ForbiddenException(__('Collection received older or same as local version.'));
|
||||
}
|
||||
$params = [
|
||||
'afterSave' => function (array &$collection) use ($data) {
|
||||
$collection = $this->Collection->CollectionElement->captureElements($collection);
|
||||
return $collection;
|
||||
}
|
||||
];
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->CRUD->edit($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'edit'));
|
||||
$this->loadModel('Event');
|
||||
$dropdownData = [
|
||||
'types' => $this->valid_types,
|
||||
'distributionLevels' => $this->Event->distributionLevels,
|
||||
'sgs' => $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1)
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
if (!$this->Collection->mayModify($this->Auth->user('id'), $id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
|
||||
}
|
||||
$this->CRUD->delete($id);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function view($id)
|
||||
{
|
||||
$this->set('mayModify', $this->Collection->mayModify($this->Auth->user('id'), $id));
|
||||
if (!$this->Collection->mayView($this->Auth->user('id'), $id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
|
||||
}
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'view'));
|
||||
$params = [
|
||||
'contain' => [
|
||||
'Orgc',
|
||||
'Org',
|
||||
'User',
|
||||
'CollectionElement'
|
||||
],
|
||||
'afterFind' => function (array $collection){
|
||||
return $this->Collection->rearrangeCollection($collection);
|
||||
}
|
||||
];
|
||||
$this->CRUD->view($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->loadModel('Event');
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
$this->render('view');
|
||||
}
|
||||
|
||||
public function index($filter = null)
|
||||
{
|
||||
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'index'));
|
||||
$params = [
|
||||
'filters' => ['Collection.uuid', 'Collection.type', 'Collection.name'],
|
||||
'quickFilters' => ['Collection.name'],
|
||||
'contain' => ['Orgc'],
|
||||
'afterFind' => function($collections) {
|
||||
foreach ($collections as $k => $collection) {
|
||||
$collections[$k]['Collection']['element_count'] = $this->Collection->CollectionElement->find('count', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['CollectionElement.collection_id' => $collection['Collection']['id']]
|
||||
]);
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
];
|
||||
if ($filter === 'my_collections') {
|
||||
$params['conditions']['Collection.user_id'] = $this->Auth->user('id');
|
||||
}
|
||||
if ($filter === 'org_collections') {
|
||||
$params['conditions']['Collection.orgc_id'] = $this->Auth->user('org_id');
|
||||
}
|
||||
$this->loadModel('Event');
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
$this->CRUD->index($params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,25 @@ class ACLComponent extends Component
|
|||
'queryACL' => array(),
|
||||
'restSearch' => array('*'),
|
||||
),
|
||||
'analystData' => [
|
||||
'add' => ['AND' => ['perm_add', 'perm_analyst_data']],
|
||||
'delete' => ['AND' => ['perm_add', 'perm_analyst_data']],
|
||||
'edit' => ['AND' => ['perm_add', 'perm_analyst_data']],
|
||||
'filterAnalystDataForPush' => ['perm_sync'],
|
||||
'getChildren' => ['*'],
|
||||
'getRelatedElement' => ['*'],
|
||||
'index' => ['*'],
|
||||
'indexMinimal' => ['*'],
|
||||
'pushAnalystData' => ['perm_sync'],
|
||||
'view' => ['*'],
|
||||
],
|
||||
'analystDataBlocklists' => array(
|
||||
'add' => array(),
|
||||
'delete' => array(),
|
||||
'edit' => array(),
|
||||
'index' => array(),
|
||||
'massDelete' => array(),
|
||||
),
|
||||
'api' => [
|
||||
'rest' => ['perm_auth'],
|
||||
'viewDeprecatedFunctionUse' => [],
|
||||
|
@ -89,6 +108,19 @@ class ACLComponent extends Component
|
|||
'pull_sgs' => [],
|
||||
'view' => []
|
||||
],
|
||||
'collections' => [
|
||||
'add' => ['perm_modify'],
|
||||
'delete' => ['perm_modify'],
|
||||
'edit' => ['perm_modify'],
|
||||
'index' => ['*'],
|
||||
'view' => ['*']
|
||||
],
|
||||
'collectionElements' => [
|
||||
'add' => ['perm_modify'],
|
||||
'addElementToCollection' => ['perm_modify'],
|
||||
'delete' => ['perm_modify'],
|
||||
'index' => ['*']
|
||||
],
|
||||
'correlationExclusions' => [
|
||||
'add' => [],
|
||||
'edit' => [],
|
||||
|
@ -1079,6 +1111,27 @@ class ACLComponent extends Component
|
|||
return $cluster['GalaxyCluster']['orgc_id'] == $user['org_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user can modify given analyst data
|
||||
*
|
||||
* @param array $user
|
||||
* @param array $analystData
|
||||
* @return bool
|
||||
*/
|
||||
public function canEditAnalystData(array $user, array $analystData, $modelType): bool
|
||||
{
|
||||
if (!isset($analystData[$modelType])) {
|
||||
throw new InvalidArgumentException('Passed object does not contain a(n) ' . $modelType);
|
||||
}
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if ($analystData[$modelType]['orgc_uuid'] == $user['Organisation']['uuid']) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user can publish given galaxy cluster
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
@ -39,6 +40,7 @@ class CRUDComponent extends Component
|
|||
if (!empty($this->Controller->paginate['fields'])) {
|
||||
$query['fields'] = $this->Controller->paginate['fields'];
|
||||
}
|
||||
$query['includeAnalystData'] = true;
|
||||
$data = $this->Controller->{$this->Controller->modelClass}->find('all', $query);
|
||||
if (isset($options['afterFind'])) {
|
||||
if (is_callable($options['afterFind'])) {
|
||||
|
@ -49,6 +51,7 @@ class CRUDComponent extends Component
|
|||
}
|
||||
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
|
||||
} else {
|
||||
$query['includeAnalystData'] = true;
|
||||
$this->Controller->paginate = $query;
|
||||
$data = $this->Controller->paginate();
|
||||
if (isset($options['afterFind'])) {
|
||||
|
@ -93,7 +96,7 @@ class CRUDComponent extends Component
|
|||
$savedData = $model->save($data);
|
||||
if ($savedData) {
|
||||
if (isset($params['afterSave'])) {
|
||||
$params['afterSave']($data);
|
||||
$params['afterSave']($savedData);
|
||||
}
|
||||
$data = $model->find('first', [
|
||||
'recursive' => -1,
|
||||
|
@ -200,7 +203,7 @@ class CRUDComponent extends Component
|
|||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
}
|
||||
if ($model->save($data)) {
|
||||
if ($data = $model->save($data)) {
|
||||
if (isset($params['afterSave'])) {
|
||||
$params['afterSave']($data);
|
||||
}
|
||||
|
@ -231,6 +234,8 @@ class CRUDComponent extends Component
|
|||
if (empty($id)) {
|
||||
throw new NotFoundException(__('Invalid %s.', $modelName));
|
||||
}
|
||||
$this->Controller->{$modelName}->includeAnalystData = true;
|
||||
$this->Controller->{$modelName}->includeAnalystDataRecursive = true;
|
||||
$query = [
|
||||
'recursive' => -1,
|
||||
'conditions' => [$modelName . '.id' => $id],
|
||||
|
@ -297,6 +302,9 @@ class CRUDComponent extends Component
|
|||
$result = $this->Controller->{$modelName}->delete($id);
|
||||
}
|
||||
if ($result) {
|
||||
if (isset($params['afterDelete']) && is_callable($params['afterDelete'])) {
|
||||
$params['afterDelete']($data);
|
||||
}
|
||||
$message = __('%s deleted.', $modelName);
|
||||
if ($this->Controller->IndexFilter->isRest()) {
|
||||
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveSuccessResponse($modelName, 'delete', $id, 'json', $message);
|
||||
|
@ -336,7 +344,9 @@ class CRUDComponent extends Component
|
|||
if ($filter === 'quickFilter') {
|
||||
continue;
|
||||
}
|
||||
if (strlen(trim($filterValue, '%')) === strlen($filterValue)) {
|
||||
if (is_array($filterValue)) {
|
||||
$query['conditions']['AND'][] = [$filter => $filterValue];
|
||||
} else if (strlen(trim($filterValue, '%')) === strlen($filterValue)) {
|
||||
$query['conditions']['AND'][] = [$filter => $filterValue];
|
||||
} else {
|
||||
$query['conditions']['AND'][] = [$filter . ' LIKE' => $filterValue];
|
||||
|
|
|
@ -213,6 +213,7 @@ class RestResponseComponent extends Component
|
|||
'perm_tag_editor',
|
||||
'default_role',
|
||||
'perm_sighting',
|
||||
'perm_analyst_data',
|
||||
'permission'
|
||||
)
|
||||
),
|
||||
|
@ -234,6 +235,7 @@ class RestResponseComponent extends Component
|
|||
'perm_tag_editor',
|
||||
'default_role',
|
||||
'perm_sighting',
|
||||
'perm_analyst_data',
|
||||
'permission'
|
||||
)
|
||||
)
|
||||
|
@ -1557,6 +1559,11 @@ class RestResponseComponent extends Component
|
|||
'type' => 'integer',
|
||||
'values' => array(1 => 'True', 0 => 'False')
|
||||
),
|
||||
'perm_analyst_data' => array(
|
||||
'input' => 'radio',
|
||||
'type' => 'integer',
|
||||
'values' => array(1 => 'True', 0 => 'False')
|
||||
),
|
||||
'permission' => array(
|
||||
'input' => 'select',
|
||||
'type' => 'string',
|
||||
|
|
|
@ -124,6 +124,7 @@ class RestSearchComponent extends Component
|
|||
'extended',
|
||||
'extensionList',
|
||||
'excludeGalaxy',
|
||||
'includeAnalystData',
|
||||
'includeRelatedTags',
|
||||
'includeDecayScore',
|
||||
'includeScoresOnEvent',
|
||||
|
|
|
@ -61,6 +61,8 @@ class EventReportsController extends AppController
|
|||
|
||||
public function view($reportId, $ajax=false)
|
||||
{
|
||||
$this->EventReport->includeAnalystData = true;
|
||||
$this->EventReport->includeAnalystDataRecursive = true;
|
||||
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($report, $this->response->type());
|
||||
|
@ -175,6 +177,7 @@ class EventReportsController extends AppController
|
|||
$filters = $this->IndexFilter->harvestParameters(['event_id', 'value', 'context', 'index_for_event', 'extended_event']);
|
||||
$filters['embedded_view'] = $this->request->is('ajax');
|
||||
$compiledConditions = $this->__generateIndexConditions($filters);
|
||||
$this->EventReport->includeAnalystData = true;
|
||||
if ($this->_isRest()) {
|
||||
$reports = $this->EventReport->find('all', [
|
||||
'recursive' => -1,
|
||||
|
@ -515,6 +518,7 @@ class EventReportsController extends AppController
|
|||
{
|
||||
$distributionLevels = $this->EventReport->Event->Attribute->distributionLevels;
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('shortDist', $this->EventReport->Event->Attribute->shortDist);
|
||||
$this->set('initialDistribution', $this->EventReport->Event->Attribute->defaultDistribution());
|
||||
}
|
||||
|
||||
|
|
|
@ -755,7 +755,8 @@ class EventsController extends AppController
|
|||
if ($nothing) {
|
||||
$this->paginate['conditions']['AND'][] = ['Event.id' => -1]; // do not fetch any event
|
||||
}
|
||||
|
||||
$this->Event->includeAnalystData = true;
|
||||
$this->paginate['includeAnalystData'] = true;
|
||||
$events = $this->paginate();
|
||||
|
||||
if (count($events) === 1 && isset($this->passedArgs['searchall'])) {
|
||||
|
@ -811,6 +812,7 @@ class EventsController extends AppController
|
|||
$rules = [
|
||||
'contain' => ['EventTag'],
|
||||
'fields' => array_keys($fieldNames),
|
||||
'includeAnalystData' => isset($passedArgs['includeAnalystData']) ? $passedArgs['includeAnalystData'] : true,
|
||||
];
|
||||
}
|
||||
if (isset($passedArgs['sort']) && isset($fieldNames[$passedArgs['sort']])) {
|
||||
|
@ -1227,6 +1229,9 @@ class EventsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
$this->Event->Attribute->includeAnalystData = true;
|
||||
$this->Event->Attribute->includeAnalystDataRecursive = true;
|
||||
|
||||
if (isset($filters['focus'])) {
|
||||
$this->set('focus', $filters['focus']);
|
||||
}
|
||||
|
@ -1691,7 +1696,7 @@ class EventsController extends AppController
|
|||
}
|
||||
|
||||
$namedParams = $this->request->params['named'];
|
||||
|
||||
$conditions['includeAnalystData'] = true;
|
||||
if ($this->_isRest()) {
|
||||
$conditions['includeAttachments'] = isset($namedParams['includeAttachments']) ? $namedParams['includeAttachments'] : true;
|
||||
} else {
|
||||
|
@ -1786,7 +1791,6 @@ class EventsController extends AppController
|
|||
} else {
|
||||
$user = $this->Auth->user();
|
||||
}
|
||||
|
||||
$results = $this->Event->fetchEvent($user, $conditions);
|
||||
if (empty($results)) {
|
||||
throw new NotFoundException(__('Invalid event'));
|
||||
|
@ -2692,7 +2696,7 @@ class EventsController extends AppController
|
|||
$this->request->data = $this->request->data['Event'];
|
||||
}
|
||||
$eventToSave = $event;
|
||||
$capturedObjects = ['Attribute', 'Object', 'Tag', 'Galaxy', 'EventReport'];
|
||||
$capturedObjects = ['Attribute', 'Object', 'Tag', 'Galaxy', 'EventReport', 'Note', 'Opinion', 'Relationship',];
|
||||
foreach ($capturedObjects as $objectType) {
|
||||
if (!empty($this->request->data[$objectType])) {
|
||||
if (!empty($regenerateUUIDs)) {
|
||||
|
@ -4373,12 +4377,12 @@ class EventsController extends AppController
|
|||
$id = $event['Event']['id'];
|
||||
$exports = array(
|
||||
'json' => array(
|
||||
'url' => $this->baseurl . '/events/restSearch/json/eventid:' . $id . '.json',
|
||||
'url' => $this->baseurl . '/events/restSearch/json/includeAnalystData:1/eventid:' . $id . '.json',
|
||||
'text' => __('MISP JSON (metadata + all attributes)'),
|
||||
'requiresPublished' => false,
|
||||
'checkbox' => true,
|
||||
'checkbox_text' => __('Encode Attachments'),
|
||||
'checkbox_set' => $this->baseurl . '/events/restSearch/json/withAttachments:1/eventid:' . $id . '.json',
|
||||
'checkbox_set' => $this->baseurl . '/events/restSearch/json/withAttachments:1/includeAnalystData:1/eventid:' . $id . '.json',
|
||||
'checkbox_default' => true,
|
||||
),
|
||||
'xml' => array(
|
||||
|
|
|
@ -173,6 +173,8 @@ class GalaxyClustersController extends AppController
|
|||
*/
|
||||
public function view($id)
|
||||
{
|
||||
$this->GalaxyCluster->includeAnalystData = true;
|
||||
$this->GalaxyCluster->includeAnalystDataRecursive = true;
|
||||
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors=true, $full=true);
|
||||
$tag = $this->GalaxyCluster->Tag->find('first', array(
|
||||
'conditions' => array(
|
||||
|
@ -208,6 +210,7 @@ class GalaxyClustersController extends AppController
|
|||
$this->loadModel('Attribute');
|
||||
$distributionLevels = $this->Attribute->distributionLevels;
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('shortDist', $this->Attribute->shortDist);
|
||||
if (!$cluster['GalaxyCluster']['default'] && !$cluster['GalaxyCluster']['published'] && $cluster['GalaxyCluster']['orgc_id'] == $this->Auth->user()['org_id']) {
|
||||
$this->Flash->warning(__('This cluster is not published. Users will not be able to use it'));
|
||||
}
|
||||
|
|
|
@ -529,7 +529,7 @@ class ServersController extends AppController
|
|||
|
||||
if (!$fail) {
|
||||
// say what fields are to be updated
|
||||
$fieldList = array('id', 'url', 'push', 'pull', 'push_sightings', 'push_galaxy_clusters', 'pull_galaxy_clusters', 'caching_enabled', 'unpublish_event', 'publish_without_email', 'remote_org_id', 'name' ,'self_signed', 'remove_missing_tags', 'cert_file', 'client_cert_file', 'push_rules', 'pull_rules', 'internal', 'skip_proxy');
|
||||
$fieldList = array('id', 'url', 'push', 'pull', 'push_sightings', 'push_galaxy_clusters', 'pull_galaxy_clusters', 'push_analyst_data', 'pull_analyst_data', 'caching_enabled', 'unpublish_event', 'publish_without_email', 'remote_org_id', 'name' ,'self_signed', 'remove_missing_tags', 'cert_file', 'client_cert_file', 'push_rules', 'pull_rules', 'internal', 'skip_proxy');
|
||||
$this->request->data['Server']['id'] = $id;
|
||||
if (isset($this->request->data['Server']['authkey']) && "" != $this->request->data['Server']['authkey']) {
|
||||
$fieldList[] = 'authkey';
|
||||
|
@ -776,7 +776,7 @@ class ServersController extends AppController
|
|||
if (!Configure::read('MISP.background_jobs')) {
|
||||
$result = $this->Server->pull($this->Auth->user(), $technique, $s);
|
||||
if (is_array($result)) {
|
||||
$success = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4]);
|
||||
$success = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled, %s analyst data pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4], $result[5]);
|
||||
} else {
|
||||
$error = $result;
|
||||
}
|
||||
|
@ -784,6 +784,7 @@ class ServersController extends AppController
|
|||
$this->set('fails', $result[1]);
|
||||
$this->set('pulledProposals', $result[2]);
|
||||
$this->set('pulledSightings', $result[3]);
|
||||
$this->set('pulledAnalystData', $result[5]);
|
||||
} else {
|
||||
$this->loadModel('Job');
|
||||
$jobId = $this->Job->createJob(
|
||||
|
@ -1889,6 +1890,7 @@ class ServersController extends AppController
|
|||
'perm_sync' => (bool) $user['Role']['perm_sync'],
|
||||
'perm_sighting' => (bool) $user['Role']['perm_sighting'],
|
||||
'perm_galaxy_editor' => (bool) $user['Role']['perm_galaxy_editor'],
|
||||
'perm_analyst_data' => (bool) $user['Role']['perm_analyst_data'],
|
||||
'uuid' => $user['Role']['perm_sync'] ? Configure::read('MISP.uuid') : '-',
|
||||
'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(),
|
||||
'filter_sightings' => true, // check if Sightings::filterSightingUuidsForPush method is supported
|
||||
|
|
|
@ -37,6 +37,10 @@ class MispAdminSyncTestWidget
|
|||
$colour = 'orange';
|
||||
$message .= ' ' . __('No sighting access.');
|
||||
}
|
||||
if (empty($result['info']['perm_analyst_data'])) {
|
||||
$colour = 'orange';
|
||||
$message .= ' ' . __('No analyst data sync access.');
|
||||
}
|
||||
} else {
|
||||
$colour = 'red';
|
||||
$message = $syncTestErrorCodes[$result['status']];
|
||||
|
|
|
@ -21,7 +21,7 @@ class JSONConverterTool
|
|||
|
||||
public static function convertObject($object, $isSiteAdmin = false, $raw = false)
|
||||
{
|
||||
$toRearrange = array('SharingGroup', 'Attribute', 'ShadowAttribute', 'Event', 'CryptographicKey');
|
||||
$toRearrange = array('SharingGroup', 'Attribute', 'ShadowAttribute', 'Event', 'CryptographicKey', 'Note', 'Opinion', 'Relationship');
|
||||
foreach ($toRearrange as $element) {
|
||||
if (isset($object[$element])) {
|
||||
$object['Object'][$element] = $object[$element];
|
||||
|
@ -40,7 +40,7 @@ class JSONConverterTool
|
|||
|
||||
public static function convert($event, $isSiteAdmin=false, $raw = false)
|
||||
{
|
||||
$toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy', 'Object', 'EventReport', 'CryptographicKey');
|
||||
$toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy', 'Object', 'EventReport', 'CryptographicKey', 'Note', 'Opinion', 'Relationship');
|
||||
foreach ($toRearrange as $object) {
|
||||
if (isset($event[$object])) {
|
||||
$event['Event'][$object] = $event[$object];
|
||||
|
@ -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)) {
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class LanguageRFC5646Tool
|
||||
{
|
||||
const RFC5646_LANGUAGE = [
|
||||
'af' => 'Afrikaans',
|
||||
'af-ZA' => 'Afrikaans (South Africa)',
|
||||
'ar' => 'Arabic',
|
||||
'ar-AE' => 'Arabic (U.A.E.)',
|
||||
'ar-BH' => 'Arabic (Bahrain)',
|
||||
'ar-DZ' => 'Arabic (Algeria)',
|
||||
'ar-EG' => 'Arabic (Egypt)',
|
||||
'ar-IQ' => 'Arabic (Iraq)',
|
||||
'ar-JO' => 'Arabic (Jordan)',
|
||||
'ar-KW' => 'Arabic (Kuwait)',
|
||||
'ar-LB' => 'Arabic (Lebanon)',
|
||||
'ar-LY' => 'Arabic (Libya)',
|
||||
'ar-MA' => 'Arabic (Morocco)',
|
||||
'ar-OM' => 'Arabic (Oman)',
|
||||
'ar-QA' => 'Arabic (Qatar)',
|
||||
'ar-SA' => 'Arabic (Saudi Arabia)',
|
||||
'ar-SY' => 'Arabic (Syria)',
|
||||
'ar-TN' => 'Arabic (Tunisia)',
|
||||
'ar-YE' => 'Arabic (Yemen)',
|
||||
'az' => 'Azeri (Latin)',
|
||||
'az-AZ' => 'Azeri (Latin) (Azerbaijan)',
|
||||
'az-Cyrl-AZ' => 'Azeri (Cyrillic) (Azerbaijan)',
|
||||
'be' => 'Belarusian',
|
||||
'be-BY' => 'Belarusian (Belarus)',
|
||||
'bg' => 'Bulgarian',
|
||||
'bg-BG' => 'Bulgarian (Bulgaria)',
|
||||
'bs-BA' => 'Bosnian (Bosnia and Herzegovina)',
|
||||
'ca' => 'Catalan',
|
||||
'ca-ES' => 'Catalan (Spain)',
|
||||
'cs' => 'Czech',
|
||||
'cs-CZ' => 'Czech (Czech Republic)',
|
||||
'cy' => 'Welsh',
|
||||
'cy-GB' => 'Welsh (United Kingdom)',
|
||||
'da' => 'Danish',
|
||||
'da-DK' => 'Danish (Denmark)',
|
||||
'de' => 'German',
|
||||
'de-AT' => 'German (Austria)',
|
||||
'de-CH' => 'German (Switzerland)',
|
||||
'de-DE' => 'German (Germany)',
|
||||
'de-LI' => 'German (Liechtenstein)',
|
||||
'de-LU' => 'German (Luxembourg)',
|
||||
'dv' => 'Divehi',
|
||||
'dv-MV' => 'Divehi (Maldives)',
|
||||
'el' => 'Greek',
|
||||
'el-GR' => 'Greek (Greece)',
|
||||
'en' => 'English',
|
||||
'en-AU' => 'English (Australia)',
|
||||
'en-BZ' => 'English (Belize)',
|
||||
'en-CA' => 'English (Canada)',
|
||||
'en-CB' => 'English (Caribbean)',
|
||||
'en-GB' => 'English (United Kingdom)',
|
||||
'en-IE' => 'English (Ireland)',
|
||||
'en-JM' => 'English (Jamaica)',
|
||||
'en-NZ' => 'English (New Zealand)',
|
||||
'en-PH' => 'English (Republic of the Philippines)',
|
||||
'en-TT' => 'English (Trinidad and Tobago)',
|
||||
'en-US' => 'English (United States)',
|
||||
'en-ZA' => 'English (South Africa)',
|
||||
'en-ZW' => 'English (Zimbabwe)',
|
||||
'eo' => 'Esperanto',
|
||||
'es' => 'Spanish',
|
||||
'es-AR' => 'Spanish (Argentina)',
|
||||
'es-BO' => 'Spanish (Bolivia)',
|
||||
'es-CL' => 'Spanish (Chile)',
|
||||
'es-CO' => 'Spanish (Colombia)',
|
||||
'es-CR' => 'Spanish (Costa Rica)',
|
||||
'es-DO' => 'Spanish (Dominican Republic)',
|
||||
'es-EC' => 'Spanish (Ecuador)',
|
||||
'es-ES' => 'Spanish (Spain)',
|
||||
'es-GT' => 'Spanish (Guatemala)',
|
||||
'es-HN' => 'Spanish (Honduras)',
|
||||
'es-MX' => 'Spanish (Mexico)',
|
||||
'es-NI' => 'Spanish (Nicaragua)',
|
||||
'es-PA' => 'Spanish (Panama)',
|
||||
'es-PE' => 'Spanish (Peru)',
|
||||
'es-PR' => 'Spanish (Puerto Rico)',
|
||||
'es-PY' => 'Spanish (Paraguay)',
|
||||
'es-SV' => 'Spanish (El Salvador)',
|
||||
'es-UY' => 'Spanish (Uruguay)',
|
||||
'es-VE' => 'Spanish (Venezuela)',
|
||||
'et' => 'Estonian',
|
||||
'et-EE' => 'Estonian (Estonia)',
|
||||
'eu' => 'Basque',
|
||||
'eu-ES' => 'Basque (Spain)',
|
||||
'fa' => 'Farsi',
|
||||
'fa-IR' => 'Farsi (Iran)',
|
||||
'fi' => 'Finnish',
|
||||
'fi-FI' => 'Finnish (Finland)',
|
||||
'fo' => 'Faroese',
|
||||
'fo-FO' => 'Faroese (Faroe Islands)',
|
||||
'fr' => 'French',
|
||||
'fr-BE' => 'French (Belgium)',
|
||||
'fr-CA' => 'French (Canada)',
|
||||
'fr-CH' => 'French (Switzerland)',
|
||||
'fr-FR' => 'French (France)',
|
||||
'fr-LU' => 'French (Luxembourg)',
|
||||
'fr-MC' => 'French (Principality of Monaco)',
|
||||
'gl' => 'Galician',
|
||||
'gl-ES' => 'Galician (Spain)',
|
||||
'gu' => 'Gujarati',
|
||||
'gu-IN' => 'Gujarati (India)',
|
||||
'he' => 'Hebrew',
|
||||
'he-IL' => 'Hebrew (Israel)',
|
||||
'hi' => 'Hindi',
|
||||
'hi-IN' => 'Hindi (India)',
|
||||
'hr' => 'Croatian',
|
||||
'hr-BA' => 'Croatian (Bosnia and Herzegovina)',
|
||||
'hr-HR' => 'Croatian (Croatia)',
|
||||
'hu' => 'Hungarian',
|
||||
'hu-HU' => 'Hungarian (Hungary)',
|
||||
'hy' => 'Armenian',
|
||||
'hy-AM' => 'Armenian (Armenia)',
|
||||
'id' => 'Indonesian',
|
||||
'id-ID' => 'Indonesian (Indonesia)',
|
||||
'is' => 'Icelandic',
|
||||
'is-IS' => 'Icelandic (Iceland)',
|
||||
'it' => 'Italian',
|
||||
'it-CH' => 'Italian (Switzerland)',
|
||||
'it-IT' => 'Italian (Italy)',
|
||||
'ja' => 'Japanese',
|
||||
'ja-JP' => 'Japanese (Japan)',
|
||||
'ka' => 'Georgian',
|
||||
'ka-GE' => 'Georgian (Georgia)',
|
||||
'kk' => 'Kazakh',
|
||||
'kk-KZ' => 'Kazakh (Kazakhstan)',
|
||||
'kn' => 'Kannada',
|
||||
'kn-IN' => 'Kannada (India)',
|
||||
'ko' => 'Korean',
|
||||
'ko-KR' => 'Korean (Korea)',
|
||||
'kok' => 'Konkani',
|
||||
'kok-IN' => 'Konkani (India)',
|
||||
'ky' => 'Kyrgyz',
|
||||
'ky-KG' => 'Kyrgyz (Kyrgyzstan)',
|
||||
'lt' => 'Lithuanian',
|
||||
'lt-LT' => 'Lithuanian (Lithuania)',
|
||||
'lv' => 'Latvian',
|
||||
'lv-LV' => 'Latvian (Latvia)',
|
||||
'mi' => 'Maori',
|
||||
'mi-NZ' => 'Maori (New Zealand)',
|
||||
'mk' => 'FYRO Macedonian',
|
||||
'mk-MK' => 'FYRO Macedonian (Former Yugoslav Republic of Macedonia)',
|
||||
'mn' => 'Mongolian',
|
||||
'mn-MN' => 'Mongolian (Mongolia)',
|
||||
'mr' => 'Marathi',
|
||||
'mr-IN' => 'Marathi (India)',
|
||||
'ms' => 'Malay',
|
||||
'ms-BN' => 'Malay (Brunei Darussalam)',
|
||||
'ms-MY' => 'Malay (Malaysia)',
|
||||
'mt' => 'Maltese',
|
||||
'mt-MT' => 'Maltese (Malta)',
|
||||
'nb' => 'Norwegian (Bokm?l)',
|
||||
'nb-NO' => 'Norwegian (Bokm?l) (Norway)',
|
||||
'nl' => 'Dutch',
|
||||
'nl-BE' => 'Dutch (Belgium)',
|
||||
'nl-NL' => 'Dutch (Netherlands)',
|
||||
'nn-NO' => 'Norwegian (Nynorsk) (Norway)',
|
||||
'ns' => 'Northern Sotho',
|
||||
'ns-ZA' => 'Northern Sotho (South Africa)',
|
||||
'pa' => 'Punjabi',
|
||||
'pa-IN' => 'Punjabi (India)',
|
||||
'pl' => 'Polish',
|
||||
'pl-PL' => 'Polish (Poland)',
|
||||
'ps' => 'Pashto',
|
||||
'ps-AR' => 'Pashto (Afghanistan)',
|
||||
'pt' => 'Portuguese',
|
||||
'pt-BR' => 'Portuguese (Brazil)',
|
||||
'pt-PT' => 'Portuguese (Portugal)',
|
||||
'qu' => 'Quechua',
|
||||
'qu-BO' => 'Quechua (Bolivia)',
|
||||
'qu-EC' => 'Quechua (Ecuador)',
|
||||
'qu-PE' => 'Quechua (Peru)',
|
||||
'ro' => 'Romanian',
|
||||
'ro-RO' => 'Romanian (Romania)',
|
||||
'ru' => 'Russian',
|
||||
'ru-RU' => 'Russian (Russia)',
|
||||
'sa' => 'Sanskrit',
|
||||
'sa-IN' => 'Sanskrit (India)',
|
||||
'se' => 'Sami',
|
||||
'se-FI' => 'Sami (Finland)',
|
||||
'se-NO' => 'Sami (Norway)',
|
||||
'se-SE' => 'Sami (Sweden)',
|
||||
'sk' => 'Slovak',
|
||||
'sk-SK' => 'Slovak (Slovakia)',
|
||||
'sl' => 'Slovenian',
|
||||
'sl-SI' => 'Slovenian (Slovenia)',
|
||||
'sq' => 'Albanian',
|
||||
'sq-AL' => 'Albanian (Albania)',
|
||||
'sr-BA' => 'Serbian (Latin) (Bosnia and Herzegovina)',
|
||||
'sr-Cyrl-BA' => 'Serbian (Cyrillic) (Bosnia and Herzegovina)',
|
||||
'sr-SP' => 'Serbian (Latin) (Serbia and Montenegro)',
|
||||
'sr-Cyrl-SP' => 'Serbian (Cyrillic) (Serbia and Montenegro)',
|
||||
'sv' => 'Swedish',
|
||||
'sv-FI' => 'Swedish (Finland)',
|
||||
'sv-SE' => 'Swedish (Sweden)',
|
||||
'sw' => 'Swahili',
|
||||
'sw-KE' => 'Swahili (Kenya)',
|
||||
'syr' => 'Syriac',
|
||||
'syr-SY' => 'Syriac (Syria)',
|
||||
'ta' => 'Tamil',
|
||||
'ta-IN' => 'Tamil (India)',
|
||||
'te' => 'Telugu',
|
||||
'te-IN' => 'Telugu (India)',
|
||||
'th' => 'Thai',
|
||||
'th-TH' => 'Thai (Thailand)',
|
||||
'tl' => 'Tagalog',
|
||||
'tl-PH' => 'Tagalog (Philippines)',
|
||||
'tn' => 'Tswana',
|
||||
'tn-ZA' => 'Tswana (South Africa)',
|
||||
'tr' => 'Turkish',
|
||||
'tr-TR' => 'Turkish (Turkey)',
|
||||
'tt' => 'Tatar',
|
||||
'tt-RU' => 'Tatar (Russia)',
|
||||
'ts' => 'Tsonga',
|
||||
'uk' => 'Ukrainian',
|
||||
'uk-UA' => 'Ukrainian (Ukraine)',
|
||||
'ur' => 'Urdu',
|
||||
'ur-PK' => 'Urdu (Islamic Republic of Pakistan)',
|
||||
'uz' => 'Uzbek (Latin)',
|
||||
'uz-UZ' => 'Uzbek (Latin) (Uzbekistan)',
|
||||
'uz-Cyrl-UZ' => 'Uzbek (Cyrillic) (Uzbekistan)',
|
||||
'vi' => 'Vietnamese',
|
||||
'vi-VN' => 'Vietnamese (Viet Nam)',
|
||||
'xh' => 'Xhosa',
|
||||
'xh-ZA' => 'Xhosa (South Africa)',
|
||||
'zh' => 'Chinese',
|
||||
'zh-CN' => 'Chinese (S)',
|
||||
'zh-HK' => 'Chinese (Hong Kong)',
|
||||
'zh-MO' => 'Chinese (Macau)',
|
||||
'zh-SG' => 'Chinese (Singapore)',
|
||||
'zh-TW' => 'Chinese (T)',
|
||||
'zu' => 'Zulu',
|
||||
'zu-ZA' => 'Zulu (South Africa)'
|
||||
];
|
||||
|
||||
public static function getLanguages()
|
||||
{
|
||||
return self::RFC5646_LANGUAGE;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ class ServerSyncTool
|
|||
FEATURE_EDIT_OF_GALAXY_CLUSTER = 'edit_of_galaxy_cluster',
|
||||
PERM_SYNC = 'perm_sync',
|
||||
PERM_GALAXY_EDITOR = 'perm_galaxy_editor',
|
||||
PERM_ANALYST_DATA = 'perm_analyst_data',
|
||||
FEATURE_SIGHTING_REST_SEARCH = 'sighting_rest';
|
||||
|
||||
/** @var array */
|
||||
|
@ -215,6 +216,71 @@ class ServerSyncTool
|
|||
return $this->post('/galaxies/pushCluster', [$cluster], $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rules
|
||||
* @return HttpSocketResponseExtended
|
||||
* @throws HttpSocketHttpException
|
||||
* @throws HttpSocketJsonException
|
||||
*/
|
||||
public function filterAnalystDataForPush(array $candidates)
|
||||
{
|
||||
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->post('/analyst_data/filterAnalystDataForPush', $candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rules
|
||||
* @return HttpSocketResponseExtended
|
||||
* @throws HttpSocketHttpException
|
||||
* @throws HttpSocketJsonException
|
||||
*/
|
||||
public function fetchIndexMinimal(array $rules)
|
||||
{
|
||||
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->post('/analyst_data/indexMinimal', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpSocketJsonException
|
||||
* @throws HttpSocketHttpException
|
||||
*/
|
||||
public function fetchAnalystData($type, array $uuids)
|
||||
{
|
||||
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$params = [
|
||||
'uuid' => $uuids,
|
||||
];
|
||||
|
||||
$url = '/analyst_data/index/' . $type;
|
||||
$url .= $this->createParams($params);
|
||||
$url .= '.json';
|
||||
return $this->get($url);
|
||||
|
||||
// $response = $this->post('/analyst_data/restSearch' , $params);
|
||||
// return $response->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $analystData
|
||||
* @return HttpSocketResponseExtended
|
||||
* @throws HttpSocketHttpException
|
||||
* @throws HttpSocketJsonException
|
||||
*/
|
||||
public function pushAnalystData($type, array $analystData)
|
||||
{
|
||||
$logMessage = "Pushing Analyst Data #{$analystData[$type]['uuid']} to Server #{$this->serverId()}";
|
||||
return $this->post('/analyst_data/pushAnalystData', $analystData, $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return HttpSocketResponseExtended
|
||||
|
@ -414,6 +480,8 @@ class ServerSyncTool
|
|||
return isset($info['perm_sync']) && $info['perm_sync'];
|
||||
case self::PERM_GALAXY_EDITOR:
|
||||
return isset($info['perm_galaxy_editor']) && $info['perm_galaxy_editor'];
|
||||
case self::PERM_ANALYST_DATA:
|
||||
return isset($info['perm_analyst_data']) && $info['perm_analyst_data'];
|
||||
case self::FEATURE_SIGHTING_REST_SEARCH:
|
||||
$version = explode('.', $info['version']);
|
||||
return $version[0] == 2 && $version[1] == 4 && $version[2] > 164;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
class AnalystDataBlocklist extends AppModel
|
||||
{
|
||||
public $useTable = 'analyst_data_blocklists';
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'AuditLog',
|
||||
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
|
||||
'userModel' => 'User',
|
||||
'userKey' => 'user_id',
|
||||
'change' => 'full'
|
||||
),
|
||||
'Containable',
|
||||
);
|
||||
|
||||
public $blocklistFields = array('analyst_data_uuid', 'comment', 'analyst_data_info', 'analyst_data_orgc');
|
||||
public $blocklistTarget = 'analyst_data';
|
||||
|
||||
public $validate = array(
|
||||
'analyst_data_uuid' => array(
|
||||
'unique' => array(
|
||||
'rule' => 'isUnique',
|
||||
'message' => 'Analyst Data already blocklisted.'
|
||||
),
|
||||
'uuid' => array(
|
||||
'rule' => array('uuid'),
|
||||
'message' => 'Please provide a valid UUID'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
parent::beforeValidate();
|
||||
if (empty($this->data['AnalystDataBlocklist']['id'])) {
|
||||
$this->data['AnalystDataBlocklist']['date_created'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
if (empty($this->data['AnalystDataBlocklist']['comment'])) {
|
||||
$this->data['AnalystDataBlocklist']['comment'] = '';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $analystDataUUID
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIfBlocked($analystDataUUID)
|
||||
{
|
||||
return $this->hasAny([
|
||||
'analyst_data_uuid' => $analystDataUUID,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -48,6 +48,9 @@ class AppModel extends Model
|
|||
/** @var Workflow|null */
|
||||
private $Workflow;
|
||||
|
||||
public $includeAnalystData;
|
||||
public $includeAnalystDataRecursive;
|
||||
|
||||
// deprecated, use $db_changes
|
||||
// major -> minor -> hotfix -> requires_logout
|
||||
const OLD_DB_CHANGES = array(
|
||||
|
@ -87,7 +90,7 @@ class AppModel extends Model
|
|||
99 => false, 100 => false, 101 => false, 102 => false, 103 => false, 104 => false,
|
||||
105 => false, 106 => false, 107 => false, 108 => false, 109 => false, 110 => false,
|
||||
111 => false, 112 => false, 113 => true, 114 => false, 115 => false, 116 => false,
|
||||
117 => false, 118 => false, 119 => false, 120 => false
|
||||
117 => false, 118 => false, 119 => false, 120 => false, 121 => false, 122 => false,
|
||||
);
|
||||
|
||||
const ADVANCED_UPDATES_DESCRIPTION = array(
|
||||
|
@ -2013,6 +2016,145 @@ class AppModel extends Model
|
|||
case 119:
|
||||
$sqlArray[] = "ALTER TABLE `access_logs` MODIFY `action` varchar(191) NOT NULL";
|
||||
break;
|
||||
case 121:
|
||||
$sqlArray[] = "CREATE TABLE `notes` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`authors` text,
|
||||
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`created` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
|
||||
`distribution` tinyint(4) NOT NULL,
|
||||
`sharing_group_id` int(10) unsigned,
|
||||
`locked` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`note` mediumtext,
|
||||
`language` varchar(16) DEFAULT 'en',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `object_uuid` (`object_uuid`),
|
||||
KEY `object_type` (`object_type`),
|
||||
KEY `org_uuid` (`org_uuid`),
|
||||
KEY `orgc_uuid` (`orgc_uuid`),
|
||||
KEY `distribution` (`distribution`),
|
||||
KEY `sharing_group_id` (`sharing_group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
|
||||
$sqlArray[] = "CREATE TABLE `opinions` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`authors` text,
|
||||
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`created` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
|
||||
`distribution` tinyint(4) NOT NULL,
|
||||
`sharing_group_id` int(10) unsigned,
|
||||
`locked` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`opinion` int(10) unsigned,
|
||||
`comment` text,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `object_uuid` (`object_uuid`),
|
||||
KEY `object_type` (`object_type`),
|
||||
KEY `org_uuid` (`org_uuid`),
|
||||
KEY `orgc_uuid` (`orgc_uuid`),
|
||||
KEY `distribution` (`distribution`),
|
||||
KEY `sharing_group_id` (`sharing_group_id`),
|
||||
KEY `opinion` (`opinion`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
|
||||
$sqlArray[] = "CREATE TABLE `relationships` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii NOT NULL,
|
||||
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`authors` text,
|
||||
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`created` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
|
||||
`distribution` tinyint(4) NOT NULL,
|
||||
`sharing_group_id` int(10) unsigned,
|
||||
`locked` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`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`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `object_uuid` (`object_uuid`),
|
||||
KEY `object_type` (`object_type`),
|
||||
KEY `org_uuid` (`org_uuid`),
|
||||
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;";
|
||||
|
||||
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `analyst_data_blocklists` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`analyst_data_uuid` varchar(40) COLLATE utf8_bin NOT NULL,
|
||||
`created` datetime NOT NULL,
|
||||
`analyst_data_info` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||
`comment` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||
`analyst_data_orgc` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `analyst_data_uuid` (`analyst_data_uuid`),
|
||||
KEY `analyst_data_orgc` (`analyst_data_orgc`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;";
|
||||
|
||||
$sqlArray[] = "ALTER TABLE `roles` ADD `perm_analyst_data` tinyint(1) NOT NULL DEFAULT 0;";
|
||||
$sqlArray[] = "UPDATE `roles` SET `perm_analyst_data`=1 WHERE `perm_add` = 1;";
|
||||
|
||||
$sqlArray[] = "ALTER TABLE `servers` ADD `push_analyst_data` tinyint(1) NOT NULL DEFAULT 0 AFTER `push_galaxy_clusters`;";
|
||||
$sqlArray[] = "ALTER TABLE `servers` ADD `pull_analyst_data` tinyint(1) NOT NULL DEFAULT 0 AFTER `push_analyst_data`;";
|
||||
break;
|
||||
case 122:
|
||||
$sqlArray[] = "CREATE TABLE `collections` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`org_id` int(10) unsigned NOT NULL,
|
||||
`orgc_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`created` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
|
||||
`distribution` tinyint(4) NOT NULL,
|
||||
`sharing_group_id` int(10) unsigned,
|
||||
`name` varchar(191) NOT NULL,
|
||||
`type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`description` mediumtext,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `name` (`name`),
|
||||
KEY `type` (`type`),
|
||||
KEY `org_id` (`org_id`),
|
||||
KEY `orgc_id` (`orgc_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `distribution` (`distribution`),
|
||||
KEY `sharing_group_id` (`sharing_group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
|
||||
$sqlArray[] = "CREATE TABLE `collection_elements` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`element_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`element_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
|
||||
`collection_id` int(10) unsigned NOT NULL,
|
||||
`description` text,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `element_uuid` (`element_uuid`),
|
||||
KEY `element_type` (`element_type`),
|
||||
KEY `collection_id` (`collection_id`),
|
||||
UNIQUE KEY `unique_element` (`element_uuid`, `collection_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
break;
|
||||
case 'fixNonEmptySharingGroupID':
|
||||
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
|
||||
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
|
||||
|
@ -4051,8 +4193,17 @@ class AppModel extends Model
|
|||
if (!empty($query['order']) && $this->validOrderClause($query['order']) === false) {
|
||||
throw new InvalidArgumentException('Invalid order clause');
|
||||
}
|
||||
|
||||
return parent::find($type, $query);
|
||||
$results = parent::find($type, $query);
|
||||
if (!empty($query['includeAnalystData']) && $this->Behaviors->enabled('AnalystDataParent')) {
|
||||
if ($type === 'first') {
|
||||
$results[$this->alias] = array_merge($results[$this->alias], $this->attachAnalystData($results[$this->alias]));
|
||||
} else if ($type === 'all') {
|
||||
foreach ($results as $k => $result) {
|
||||
$results[$k][$this->alias] = array_merge($results[$k][$this->alias], $this->attachAnalystData($results[$k][$this->alias]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function validOrderClause($order)
|
||||
|
@ -4087,7 +4238,6 @@ class AppModel extends Model
|
|||
$newImageDir = APP . 'files/img';
|
||||
$oldOrgDir = new Folder($oldImageDir . '/orgs');
|
||||
$oldCustomDir = new Folder($oldImageDir . '/custom');
|
||||
$result = false;
|
||||
$result = $oldOrgDir->copy([
|
||||
'from' => $oldImageDir . '/orgs',
|
||||
'to' => $newImageDir . '/orgs',
|
||||
|
|
|
@ -36,7 +36,8 @@ class Attribute extends AppModel
|
|||
'Trim',
|
||||
'Containable',
|
||||
'Regexp' => array('fields' => array('value')),
|
||||
'LightPaginator'
|
||||
'LightPaginator',
|
||||
'AnalystDataParent',
|
||||
);
|
||||
|
||||
public $displayField = 'value';
|
||||
|
@ -2658,6 +2659,7 @@ class Attribute extends AppModel
|
|||
if (!empty($attribute['Sighting'])) {
|
||||
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
|
||||
}
|
||||
$this->Event->captureAnalystData($user, $attribute);
|
||||
}
|
||||
if (!empty($this->validationErrors)) {
|
||||
$validationErrors = $this->validationErrors;
|
||||
|
@ -2798,6 +2800,7 @@ class Attribute extends AppModel
|
|||
if (!empty($attribute['Sighting'])) {
|
||||
$this->Sighting->captureSightings($attribute['Sighting'], $attributeId, $eventId, $user);
|
||||
}
|
||||
$this->Event->captureAnalystData($user, $attribute);
|
||||
if ($user['Role']['perm_tagger']) {
|
||||
/*
|
||||
We should unwrap the line below and remove the server option in the future once we have tag soft-delete
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Common functions for the 3 analyst objects
|
||||
*/
|
||||
class AnalystDataBehavior extends ModelBehavior
|
||||
{
|
||||
public $SharingGroup;
|
||||
|
||||
private $__current_type = null;
|
||||
|
||||
public function setup(Model $Model, $settings = array()) {
|
||||
// We want to know whether we're a Note, Opinion or Relationship
|
||||
$this->__current_type = $Model->alias;
|
||||
}
|
||||
|
||||
// Return the analystData of the current type for a given UUID (this only checks the ACL of the analystData, NOT of the parent.)
|
||||
public function fetchForUuid(Model $Model, $uuid, $user = null)
|
||||
{
|
||||
$conditions = [
|
||||
'object_uuid' => $uuid
|
||||
];
|
||||
$type = $Model->current_type;
|
||||
if (empty($user['Role']['perm_site_admin'])) {
|
||||
$validSharingGroups = $Model->SharingGroup->authorizedIds($user, true);
|
||||
$conditions['AND'][] = [
|
||||
'OR' => [
|
||||
$type . '.orgc_uuid' => $user['Organisation']['uuid'],
|
||||
$type . '.org_uuid' => $user['Organisation']['uuid'],
|
||||
$type . '.distribution IN' => [1, 2, 3],
|
||||
'AND' => [
|
||||
$type . '.distribution' => 4,
|
||||
$type . '.sharing_group_id IN' => $validSharingGroups
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
return $Model->find('all', [
|
||||
'recursive' => -1,
|
||||
'conditions' => $conditions,
|
||||
'contain' => ['Org', 'Orgc', 'SharingGroup'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkACL()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Common functions for the 3 analyst objects
|
||||
*/
|
||||
class AnalystDataParentBehavior extends ModelBehavior
|
||||
{
|
||||
private $__currentUser = null;
|
||||
|
||||
public $User;
|
||||
|
||||
|
||||
|
||||
public function attachAnalystData(Model $model, array $object, array $types = ['Note', 'Opinion', 'Relationship'])
|
||||
{
|
||||
// No uuid, nothing to attach
|
||||
if (empty($object['uuid'])) {
|
||||
return $object;
|
||||
}
|
||||
if (empty($this->__currentUser)) {
|
||||
$user_id = Configure::read('CurrentUserId');
|
||||
$this->User = ClassRegistry::init('User');
|
||||
if ($user_id) {
|
||||
$this->__currentUser = $this->User->getAuthUser($user_id);
|
||||
}
|
||||
}
|
||||
$data = [];
|
||||
foreach ($types as $type) {
|
||||
$this->{$type} = ClassRegistry::init($type);
|
||||
$this->{$type}->fetchRecursive = !empty($model->includeAnalystDataRecursive);
|
||||
$temp = $this->{$type}->fetchForUuid($object['uuid'], $this->__currentUser);
|
||||
if (!empty($temp)) {
|
||||
foreach ($temp as $k => $temp_element) {
|
||||
$data[$type][] = $temp_element[$type];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function afterFind(Model $model, $results, $primary = false)
|
||||
{
|
||||
if (!empty($model->includeAnalystData)) {
|
||||
foreach ($results as $k => $item) {
|
||||
if (isset($item[$model->alias])) {
|
||||
$results[$k] = array_merge($results[$k], $this->attachAnalystData($model, $item[$model->alias]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
class Collection extends AppModel
|
||||
{
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'Containable'
|
||||
);
|
||||
|
||||
public $belongsTo = [
|
||||
'Orgc' => array(
|
||||
'className' => 'Organisation',
|
||||
'foreignKey' => 'orgc_id',
|
||||
'fields' => [
|
||||
'Orgc.id',
|
||||
'Orgc.uuid',
|
||||
'Orgc.name'
|
||||
]
|
||||
),
|
||||
'Org' => array(
|
||||
'className' => 'Organisation',
|
||||
'foreignKey' => 'org_id',
|
||||
'fields' => [
|
||||
'Org.id',
|
||||
'Org.uuid',
|
||||
'Org.name'
|
||||
]
|
||||
),
|
||||
'User' => array(
|
||||
'className' => 'User',
|
||||
'foreignKey' => 'user_id',
|
||||
'fields' => [
|
||||
'User.id',
|
||||
'User.email'
|
||||
]
|
||||
)
|
||||
];
|
||||
|
||||
public $hasMany = [
|
||||
'CollectionElement' => [
|
||||
'dependent' => true
|
||||
]
|
||||
];
|
||||
|
||||
public $valid_targets = [
|
||||
'Attribute',
|
||||
'Event',
|
||||
'GalaxyCluster',
|
||||
'Galaxy',
|
||||
'Object',
|
||||
'Note',
|
||||
'Opinion',
|
||||
'Relationship',
|
||||
'Organisation',
|
||||
'SharingGroup'
|
||||
];
|
||||
|
||||
public $current_user = null;
|
||||
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
if (empty($this->data['Collection'])) {
|
||||
$this->data = ['Collection' => $this->data];
|
||||
}
|
||||
if (empty($this->id) && empty($this->data['Collection']['uuid'])) {
|
||||
$this->data['Collection']['uuid'] = CakeText::uuid();
|
||||
}
|
||||
if (empty($this->id)) {
|
||||
$this->data['Collection']['user_id'] = $this->current_user['id'];
|
||||
if (empty($this->data['Collection']['orgc_id']) || empty($this->current_user['Role']['perm_sync'])) {
|
||||
$this->data['Collection']['orgc_id'] = $this->current_user['Organisation']['id'];
|
||||
}
|
||||
$this->data['Collection']['org_id'] = $this->current_user['Organisation']['id'];
|
||||
$this->data['Collection']['user_id'] = $this->current_user['id'];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function mayModify($user_id, $collection_id)
|
||||
{
|
||||
$user = $this->User->getAuthUser($user_id);
|
||||
$collection = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['Collection.id' => $collection_id]
|
||||
]);
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if (empty($user['Role']['perm_modify'])) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($user['Role']['perm_modify_org'])) {
|
||||
if ($user['org_id'] == $collection['Collection']['Orgc_id']) {
|
||||
return true;
|
||||
}
|
||||
if ($user['Role']['perm_sync'] && $user['org_id'] == $collection['Collection']['Org_id']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!empty($user['Role']['perm_modify']) && $user['id'] === $collection['Collection']['user_id']) {
|
||||
}
|
||||
}
|
||||
|
||||
public function mayView($user_id, $collection_id)
|
||||
{
|
||||
$user = $this->User->getAuthUser($user_id);
|
||||
$collection = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['Collection.id' => $collection_id]
|
||||
]);
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if ($collection['Collection']['org_id'] == $user('org_id')) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($collection['Collection']['distribution'], [1,2,3])) {
|
||||
return true;
|
||||
}
|
||||
if ($collection['Collection']['distribution'] === 4) {
|
||||
$SharingGroup = ClassRegistry::init('SharingGroup');
|
||||
$sgs = $this->SharingGroup->fetchAllAuthorised($user, 'uuid');
|
||||
if (isset($sgs[$collection['Collection']['sharing_group_id']])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function rearrangeCollection(array $collection) {
|
||||
foreach ($collection as $key => $elements) {
|
||||
if ($key !== 'Collection') {
|
||||
$collection['Collection'][$key] = $elements;
|
||||
unset($collection[$key]);
|
||||
}
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
class CollectionElement extends AppModel
|
||||
{
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'Containable'
|
||||
);
|
||||
|
||||
public $belongsTo = array(
|
||||
'Collection' => array(
|
||||
'className' => 'Collection',
|
||||
'foreignKey' => 'collection_id'
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure you also update the validation for element_type to include anything you add here.
|
||||
public $valid_types = [
|
||||
'Event',
|
||||
'GalaxyCluster'
|
||||
];
|
||||
|
||||
public $validate = [
|
||||
'collection_id' => [
|
||||
'numeric' => [
|
||||
'rule' => ['numeric']
|
||||
]
|
||||
],
|
||||
'uuid' => [
|
||||
'uuid' => [
|
||||
'rule' => 'uuid',
|
||||
'message' => 'Please provide a valid RFC 4122 UUID'
|
||||
]
|
||||
],
|
||||
'element_uuid' => [
|
||||
'element_uuid' => [
|
||||
'rule' => 'uuid',
|
||||
'message' => 'Please provide a valid RFC 4122 UUID'
|
||||
]
|
||||
],
|
||||
'element_type' => [
|
||||
'element_type' => [
|
||||
'rule' => ['inList', ['Event', 'GalaxyCluster']],
|
||||
'message' => 'Invalid object type.'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
// Massage to a common format
|
||||
if (empty($this->data['CollectionElement'])) {
|
||||
$this->data = ['CollectionElement' => $this->data];
|
||||
}
|
||||
|
||||
// if we're creating a new element, assign a uuid (unless provided)
|
||||
if (empty($this->id) && empty($this->data['CollectionElement']['uuid'])) {
|
||||
$this->data['CollectionElement']['uuid'] = CakeText::uuid();
|
||||
}
|
||||
if (
|
||||
empty($this->id) &&
|
||||
empty($this->data['CollectionElement']['element_type']) &&
|
||||
!empty($this->data['CollectionElement']['element_uuid'])
|
||||
) {
|
||||
$this->data['CollectionElement']['element_type'] = $this->deduceType($this->data['CollectionElement']['element_uuid']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function mayModify(int $user_id, int $collection_id)
|
||||
{
|
||||
$user = $this->User->getAuthUser($user_id);
|
||||
$collection = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['Collection.id' => $collection_id]
|
||||
]);
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if (empty($user['Role']['perm_modify'])) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($user['Role']['perm_modify_org'])) {
|
||||
if ($user['org_id'] == $collection['Collection']['Orgc_id']) {
|
||||
return true;
|
||||
}
|
||||
if ($user['Role']['perm_sync'] && $user['org_id'] == $collection['Collection']['Org_id']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!empty($user['Role']['perm_modify']) && $user['id'] === $collection['Collection']['user_id']) {
|
||||
}
|
||||
}
|
||||
|
||||
public function mayView(int $user_id, int $collection_id)
|
||||
{
|
||||
$user = $this->User->getAuthUser($user_id);
|
||||
$collection = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['Collection.id' => $collection_id]
|
||||
]);
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if ($collection['Collection']['org_id'] == $user('org_id')) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($collection['Collection']['distribution'], [1,2,3])) {
|
||||
return true;
|
||||
}
|
||||
if ($collection['Collection']['distribution'] === 4) {
|
||||
$SharingGroup = ClassRegistry::init('SharingGroup');
|
||||
$sgs = $this->SharingGroup->fetchAllAuthorised($user, 'uuid');
|
||||
if (isset($sgs[$collection['Collection']['sharing_group_id']])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deduceType(string $uuid)
|
||||
{
|
||||
foreach ($this->valid_types as $valid_type) {
|
||||
$this->{$valid_type} = ClassRegistry::init($valid_type);
|
||||
$result = $this->$valid_type->find('first', [
|
||||
'conditions' => [$valid_type.'.uuid' => $uuid],
|
||||
'recursive' => -1
|
||||
]);
|
||||
if (!empty($result)) {
|
||||
return $valid_type;
|
||||
}
|
||||
}
|
||||
throw new NotFoundException(__('Invalid UUID'));
|
||||
}
|
||||
|
||||
/*
|
||||
* Pass a Collection as received from another instance to this function to capture the elements
|
||||
* The received object is authoritative, so all elements that no longer exist in the upstream will be culled.
|
||||
*/
|
||||
public function captureElements($data) {
|
||||
$temp = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['CollectionElement.collection_id' => $data['Collection']['id']]
|
||||
]);
|
||||
$oldElements = [];
|
||||
foreach ($temp as $oldElement) {
|
||||
$oldElements[$oldElement['CollectionElement']['uuid']] = $oldElement['CollectionElement'];
|
||||
}
|
||||
if (isset($data['Collection']['CollectionElement'])) {
|
||||
$elementsToSave = [];
|
||||
foreach ($data['Collection']['CollectionElement'] as $k => $element) {
|
||||
if (empty($element['uuid'])) {
|
||||
$element['uuid'] = CakeText::uuid();
|
||||
}
|
||||
if (isset($oldElements[$element['uuid']])) {
|
||||
if (isset($element['description'])) {
|
||||
$oldElements[$element['uuid']]['description'] = $element['description'];
|
||||
}
|
||||
$elementsToSave[$k] = $oldElements[$element['uuid']];
|
||||
unset($oldElements[$element['uuid']]);
|
||||
} else {
|
||||
$elementsToSave[$k] = [
|
||||
'CollectionElement' => [
|
||||
'uuid' => $element['uuid'],
|
||||
'element_uuid' => $element['element_uuid'],
|
||||
'element_type' => $element['element_type'],
|
||||
'description' => $element['description'],
|
||||
'collection_id' => $data['Collection']['id']
|
||||
]
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
foreach ($elementsToSave as $k => $element) {
|
||||
if (empty($element['CollectionElement']['id'])) {
|
||||
$this->create();
|
||||
}
|
||||
try{
|
||||
$this->save($element);
|
||||
} catch (PDOException $e) {
|
||||
// duplicate value?
|
||||
}
|
||||
}
|
||||
foreach ($oldElements as $toDelete) {
|
||||
$this->delete($toDelete['id']);
|
||||
}
|
||||
$temp = $this->find('all', [
|
||||
'conditions' => ['CollectionElement.collection_id' => $data['Collection']['id']],
|
||||
'recursive' => -1
|
||||
]);
|
||||
$data['Collection']['CollectionElement'] = [];
|
||||
foreach ($temp as $element) {
|
||||
$data['Collection']['CollectionElement'][] = $element['CollectionElement'];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,9 @@ App::uses('ProcessTool', 'Tools');
|
|||
* @property Organisation $Org
|
||||
* @property Organisation $Orgc
|
||||
* @property CryptographicKey $CryptographicKey
|
||||
* @property Note $Note
|
||||
* @property Opinion $Opinion
|
||||
* @property Relationship $Relationship
|
||||
*/
|
||||
class Event extends AppModel
|
||||
{
|
||||
|
@ -40,7 +43,8 @@ class Event extends AppModel
|
|||
'change' => 'full'),
|
||||
'Trim',
|
||||
'Containable',
|
||||
'EventWarning'
|
||||
'EventWarning',
|
||||
'AnalystDataParent'
|
||||
);
|
||||
|
||||
public $displayField = 'id';
|
||||
|
@ -1760,6 +1764,11 @@ class Event extends AppModel
|
|||
if (!isset($options['fetchFullClusterRelationship'])) {
|
||||
$options['fetchFullClusterRelationship'] = false;
|
||||
}
|
||||
if (!isset($options['includeAnalystData'])) {
|
||||
$options['includeAnalystData'] = false;
|
||||
} else {
|
||||
$options['includeAnalystData'] = !empty($options['includeAnalystData']);
|
||||
}
|
||||
foreach ($this->possibleOptions as $opt) {
|
||||
if (!isset($options[$opt])) {
|
||||
$options[$opt] = false;
|
||||
|
@ -2038,6 +2047,8 @@ class Event extends AppModel
|
|||
if (!empty($options['page'])) {
|
||||
$params['page'] = $options['page'];
|
||||
}
|
||||
$this->includeAnalystData = $options['includeAnalystData'];
|
||||
$this->includeAnalystDataRecursive = $options['includeAnalystData'];
|
||||
if (!empty($options['order'])) {
|
||||
$params['order'] = $this->findOrder(
|
||||
$options['order'],
|
||||
|
@ -2198,6 +2209,13 @@ class Event extends AppModel
|
|||
if (!empty($options['includeGranularCorrelations'])) {
|
||||
$event['Attribute'] = $this->Attribute->Correlation->attachCorrelationExclusion($event['Attribute']);
|
||||
}
|
||||
if (!empty($options['includeAnalystData'])) {
|
||||
foreach ($event['Attribute'] as $k => $attribute) {
|
||||
$this->Attribute->includeAnalystDataRecursive = true;
|
||||
$analyst_data = $this->Attribute->attachAnalystData($attribute);
|
||||
$event['Attribute'][$k] = array_merge($event['Attribute'][$k], $analyst_data);
|
||||
}
|
||||
}
|
||||
|
||||
// move all object attributes to a temporary container
|
||||
$tempObjectAttributeContainer = array();
|
||||
|
@ -2248,6 +2266,11 @@ class Event extends AppModel
|
|||
if (isset($tempObjectAttributeContainer[$objectValue['id']])) {
|
||||
$objectValue['Attribute'] = $tempObjectAttributeContainer[$objectValue['id']];
|
||||
}
|
||||
if (!empty($options['includeAnalystData'])) {
|
||||
$this->Object->includeAnalystDataRecursive = true;
|
||||
$analyst_data = $this->Object->attachAnalystData($objectValue);
|
||||
$objectValue = array_merge($objectValue, $analyst_data);
|
||||
}
|
||||
}
|
||||
unset($tempObjectAttributeContainer);
|
||||
}
|
||||
|
@ -3633,6 +3656,7 @@ class Event extends AppModel
|
|||
$created_id = 0;
|
||||
$event['Event']['locked'] = 1;
|
||||
$event['Event']['published'] = $publish;
|
||||
$event = $this->updatedLockedFieldForAllAnalystData($event);
|
||||
$result = $this->_add($event, true, $user, '', null, false, null, $created_id, $validationIssues);
|
||||
$results[] = [
|
||||
'info' => $event['Event']['info'],
|
||||
|
@ -3644,6 +3668,59 @@ class Event extends AppModel
|
|||
return $results;
|
||||
}
|
||||
|
||||
private function updatedLockedFieldForAllAnalystData(array $event): array
|
||||
{
|
||||
$event = $this->updatedLockedFieldForAnalystData($event, 'Event');
|
||||
if (!empty($event['Event']['Attribute'])) {
|
||||
for ($i=0; $i < count($event['Event']['Attribute']); $i++) {
|
||||
$event['Event']['Attribute'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['Attribute'][$i]);
|
||||
}
|
||||
}
|
||||
if (!empty($event['Event']['Object'])) {
|
||||
for ($i=0; $i < count($event['Event']['Object']); $i++) {
|
||||
$event['Event']['Object'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['Object'][$i]);
|
||||
if (!empty($event['Event']['Object'][$i])) {
|
||||
for ($j=0; $j < count($event['Event']['Object'][$i]['Attribute']); $j++) {
|
||||
$event['Event']['Object'][$i]['Attribute'][$j] = $this->updatedLockedFieldForAnalystData($event['Event']['Object'][$i]['Attribute'][$j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($event['Event']['EventReport'])) {
|
||||
for ($i=0; $i < count($event['Event']['EventReport']); $i++) {
|
||||
$event['Event']['EventReport'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['EventReport'][$i]);
|
||||
}
|
||||
}
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function updatedLockedFieldForAnalystData(array $data, $model=false): array
|
||||
{
|
||||
$this->AnalystData = ClassRegistry::init('AnalystData');
|
||||
if (!empty($model)) {
|
||||
$data = $data[$model];
|
||||
}
|
||||
foreach ($this->AnalystData::ANALYST_DATA_TYPES as $type) {
|
||||
if (!empty($data[$type])) {
|
||||
for ($i=0; $i < count($data[$type]); $i++) {
|
||||
$data[$type][$i]['locked'] = true;
|
||||
foreach ($this->AnalystData::ANALYST_DATA_TYPES as $childType) {
|
||||
if (!empty($data[$type][$i][$childType])) {
|
||||
for ($j=0; $j < count($data[$type][$i][$childType]); $j++) {
|
||||
$data[$type][$i][$childType][$j]['locked'] = true;
|
||||
$data[$type][$i][$childType][$j] = $this->updatedLockedFieldForAnalystData($data[$type][$i][$childType][$j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($model)) {
|
||||
$data = [$model => $data];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Low level function to add an Event based on an Event $data array.
|
||||
*
|
||||
|
@ -3881,6 +3958,8 @@ class Event extends AppModel
|
|||
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
|
||||
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
|
||||
}
|
||||
|
||||
$this->captureAnalystData($user, $data['Event']);
|
||||
if ($fromXml) {
|
||||
$created_id = $this->id;
|
||||
}
|
||||
|
@ -4182,6 +4261,8 @@ class Event extends AppModel
|
|||
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
|
||||
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
|
||||
}
|
||||
|
||||
$this->captureAnalystData($user, $data['Event']);
|
||||
// if published -> do the actual publishing
|
||||
if ($changed && (!empty($data['Event']['published']) && 1 == $data['Event']['published'])) {
|
||||
// The edited event is from a remote server ?
|
||||
|
@ -7948,6 +8029,20 @@ class Event extends AppModel
|
|||
}
|
||||
}
|
||||
|
||||
public function captureAnalystData($user, $data)
|
||||
{
|
||||
$this->Note = ClassRegistry::init('Note');
|
||||
$this->Opinion = ClassRegistry::init('Opinion');
|
||||
$this->Relationship = ClassRegistry::init('Relationship');
|
||||
foreach ($this->Note::ANALYST_DATA_TYPES as $type) {
|
||||
if (!empty($data[$type])) {
|
||||
foreach ($data[$type] as $analystData) {
|
||||
$this->{$type}->captureAnalystData($user, $analystData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTrendsForTags(array $user, array $eventFilters=[], int $baseDayRange, int $rollingWindows=3, $tagFilterPrefixes=null): array
|
||||
{
|
||||
$fullDayNumber = $baseDayRange + $baseDayRange * $rollingWindows;
|
||||
|
|
|
@ -17,6 +17,7 @@ class EventReport extends AppModel
|
|||
'change' => 'full'
|
||||
),
|
||||
'Regexp' => array('fields' => array('value')),
|
||||
'AnalystDataParent',
|
||||
);
|
||||
|
||||
public $validate = array(
|
||||
|
@ -118,6 +119,8 @@ class EventReport extends AppModel
|
|||
__('Event Report dropped due to validation for Event report %s failed: %s', $this->data['EventReport']['uuid'], $this->data['EventReport']['name']),
|
||||
__('Validation errors: %s.%sFull report: %s', json_encode($errors), PHP_EOL, json_encode($report['EventReport']))
|
||||
);
|
||||
} else {
|
||||
$this->Event->captureAnalystData($user, $report);
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
@ -188,6 +191,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
$errors = $this->saveAndReturnErrors($report, ['fieldList' => self::CAPTURE_FIELDS], $errors);
|
||||
if (empty($errors)) {
|
||||
$this->Event->captureAnalystData($user, $report['EventReport']);
|
||||
$this->Event->unpublishEvent($eventId);
|
||||
}
|
||||
return $errors;
|
||||
|
|
|
@ -22,6 +22,7 @@ class GalaxyCluster extends AppModel
|
|||
'userKey' => 'user_id',
|
||||
'change' => 'full'),
|
||||
'Containable',
|
||||
'AnalystDataParent',
|
||||
);
|
||||
|
||||
private $__assetCache = array();
|
||||
|
@ -196,7 +197,7 @@ class GalaxyCluster extends AppModel
|
|||
*/
|
||||
public function arrangeData($cluster)
|
||||
{
|
||||
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation');
|
||||
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation', 'Note', 'Opinion', 'Relationship');
|
||||
foreach ($models as $model) {
|
||||
if (isset($cluster[$model])) {
|
||||
$cluster['GalaxyCluster'][$model] = $cluster[$model];
|
||||
|
|
|
@ -20,11 +20,12 @@ class MispObject extends AppModel
|
|||
|
||||
public $actsAs = array(
|
||||
'AuditLog',
|
||||
'Containable',
|
||||
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
|
||||
'userModel' => 'User',
|
||||
'userKey' => 'user_id',
|
||||
'change' => 'full'),
|
||||
'Containable',
|
||||
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
|
||||
'userModel' => 'User',
|
||||
'userKey' => 'user_id',
|
||||
'change' => 'full'),
|
||||
'AnalystDataParent'
|
||||
);
|
||||
|
||||
public $belongsTo = array(
|
||||
|
@ -571,11 +572,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;
|
||||
|
@ -1131,6 +1139,7 @@ class MispObject extends AppModel
|
|||
$this->Attribute->captureAttribute($attribute, $eventId, $user, $objectId, false, $parentEvent);
|
||||
}
|
||||
}
|
||||
$this->Event->captureAnalystData($user, $object['Object']);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1210,6 +1219,7 @@ class MispObject extends AppModel
|
|||
);
|
||||
return $this->validationErrors;
|
||||
}
|
||||
$this->Event->captureAnalystData($user, $object);
|
||||
if (!empty($object['Attribute'])) {
|
||||
$attributes = [];
|
||||
foreach ($object['Attribute'] as $attribute) {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
App::uses('AnalystData', 'Model');
|
||||
class Note extends AnalystData
|
||||
{
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'AuditLog',
|
||||
'Containable',
|
||||
'AnalystData',
|
||||
);
|
||||
|
||||
public $current_type = 'Note';
|
||||
public $current_type_id = 0;
|
||||
|
||||
public $validate = array(
|
||||
);
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
parent::beforeValidate();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
App::uses('AnalystData', 'Model');
|
||||
class Opinion extends AnalystData
|
||||
{
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'AuditLog',
|
||||
'Containable',
|
||||
'AnalystData',
|
||||
);
|
||||
|
||||
public $current_type = 'Opinion';
|
||||
public $current_type_id = 1;
|
||||
|
||||
public $validate = array(
|
||||
);
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
parent::beforeValidate();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -196,7 +196,7 @@ class Organisation extends AppModel
|
|||
* @return int Organisation ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public function captureOrg($org, array $user, $force = false)
|
||||
public function captureOrg($org, array $user, $force = false, $returnUUID = false)
|
||||
{
|
||||
$fieldsToFetch = $force ?
|
||||
['id', 'uuid', 'type', 'date_created', 'date_modified', 'nationality', 'sector', 'contacts'] :
|
||||
|
@ -238,7 +238,7 @@ class Organisation extends AppModel
|
|||
}
|
||||
$this->create();
|
||||
$this->save($organisation);
|
||||
return $this->id;
|
||||
return $returnUUID ? $organisation['uuid'] : $this->id;
|
||||
} else {
|
||||
$changed = false;
|
||||
if (isset($org['uuid']) && empty($existingOrg[$this->alias]['uuid'])) {
|
||||
|
@ -262,7 +262,7 @@ class Organisation extends AppModel
|
|||
$this->save($existingOrg);
|
||||
}
|
||||
}
|
||||
return $existingOrg[$this->alias]['id'];
|
||||
return $returnUUID ? $existingOrg[$this->alias]['uuid']: $existingOrg[$this->alias]['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
App::uses('AnalystData', 'Model');
|
||||
class Relationship extends AnalystData
|
||||
{
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'AuditLog',
|
||||
'Containable',
|
||||
'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) {
|
||||
if (!empty($v[$this->alias]['related_object_type']) && !empty($v[$this->alias]['related_object_uuid'])) {
|
||||
$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 = [
|
||||
];
|
||||
$backup = $this->Event->includeAnalystData;
|
||||
$this->Event->includeAnalystData = false;
|
||||
$data = $this->Event->fetchSimpleEvent($user, $uuid, $params);
|
||||
$this->Event->includeAnalystData = $backup;
|
||||
} else if ($type == 'Attribute') {
|
||||
$this->Attribute = ClassRegistry::init('Attribute');
|
||||
$params = [
|
||||
'conditions' => [
|
||||
['Attribute.uuid' => $uuid],
|
||||
],
|
||||
'contain' => ['Event' => 'Orgc', 'Object',]
|
||||
];
|
||||
$backup = $this->Attribute->includeAnalystData;
|
||||
$this->Attribute->includeAnalystData = false;
|
||||
$data = $this->Attribute->fetchAttributeSimple($user, $params);
|
||||
$this->Attribute->includeAnalystData = $backup;
|
||||
$data = $this->rearrangeData($data, 'Attribute');
|
||||
} else if ($type == 'Object') {
|
||||
$this->Object = ClassRegistry::init('MispObject');
|
||||
$params = [
|
||||
'conditions' => [
|
||||
['Object.uuid' => $uuid],
|
||||
],
|
||||
'contain' => ['Event' => 'Orgc',]
|
||||
];
|
||||
$backup = $this->Object->includeAnalystData;
|
||||
$this->Object->includeAnalystData = false;
|
||||
$data = $this->Object->fetchObjectSimple($user, $params);
|
||||
$this->Object->includeAnalystData = $backup;
|
||||
if (!empty($data)) {
|
||||
$data = $data[0];
|
||||
}
|
||||
$data = $this->rearrangeData($data, 'Object');
|
||||
} else if ($type == 'Note') {
|
||||
$this->Note = ClassRegistry::init('Note');
|
||||
$params = [
|
||||
|
||||
];
|
||||
$backup = $this->Note->includeAnalystData;
|
||||
$this->Note->includeAnalystData = false;
|
||||
$data = $this->Note->fetchNote();
|
||||
$this->Note->includeAnalystData = $backup;
|
||||
} else if ($type == 'Opinion') {
|
||||
$this->Opinion = ClassRegistry::init('Opinion');
|
||||
$params = [
|
||||
|
||||
];
|
||||
$backup = $this->Opinion->includeAnalystData;
|
||||
$this->Opinion->includeAnalystData = false;
|
||||
$data = $this->Opinion->fetchOpinion();
|
||||
$this->Opinion->includeAnalystData = $backup;
|
||||
} else if ($type == 'Relationship') {
|
||||
$this->Relationship = ClassRegistry::init('Relationship');
|
||||
$params = [
|
||||
|
||||
];
|
||||
$backup = $this->Relationship->includeAnalystData;
|
||||
$this->Relationship->includeAnalystData = false;
|
||||
$data = $this->Relationship->fetchRelationship();
|
||||
$this->Relationship->includeAnalystData = $backup;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function rearrangeData(array $data, $objectType): array
|
||||
{
|
||||
$models = ['Event', 'Attribute', 'Object', 'Organisation', ];
|
||||
if (!empty($data) && !empty($data[$objectType])) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -331,6 +331,12 @@ class Role extends AppModel
|
|||
'readonlyenabled' => true,
|
||||
'title' => __('Allow the viewing of feed correlations. Enabling this can come at a performance cost.'),
|
||||
),
|
||||
'perm_analyst_data' => array(
|
||||
'id' => 'RolePermAnalystData',
|
||||
'text' => 'Analyst Data Creator',
|
||||
'readonlyenabled' => false,
|
||||
'title' => __('Create or modify Analyst Data such as Analyst Notes or Opinions.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -683,21 +683,24 @@ class Server extends AppModel
|
|||
$job->saveProgress($jobId, 'Pulling sightings.', 75);
|
||||
}
|
||||
$pulledSightings = $eventModel->Sighting->pullSightings($user, $serverSync);
|
||||
$this->AnalystData = ClassRegistry::init('AnalystData');
|
||||
$pulledAnalystData = $this->AnalystData->pull($user, $serverSync);
|
||||
}
|
||||
if ($jobId) {
|
||||
$job->saveStatus($jobId, true, 'Pull completed.');
|
||||
}
|
||||
|
||||
$change = sprintf(
|
||||
'%s events, %s proposals, %s sightings and %s galaxy clusters pulled or updated. %s events failed or didn\'t need an update.',
|
||||
'%s events, %s proposals, %s sightings, %s galaxy clusters and %s analyst data pulled or updated. %s events failed or didn\'t need an update.',
|
||||
count($successes),
|
||||
$pulledProposals,
|
||||
$pulledSightings,
|
||||
$pulledClusters,
|
||||
$pulledAnalystData,
|
||||
count($fails)
|
||||
);
|
||||
$this->loadLog()->createLogEntry($user, 'pull', 'Server', $server['Server']['id'], 'Pull from ' . $server['Server']['url'] . ' initiated by ' . $email, $change);
|
||||
return [$successes, $fails, $pulledProposals, $pulledSightings, $pulledClusters];
|
||||
return [$successes, $fails, $pulledProposals, $pulledSightings, $pulledClusters, $pulledAnalystData];
|
||||
}
|
||||
|
||||
public function filterRuleToParameter($filter_rules)
|
||||
|
@ -757,6 +760,41 @@ class Server extends AppModel
|
|||
return $clusterArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetchUUIDsFromServer Fetch remote analyst datas' UUIDs and timestamp
|
||||
*
|
||||
* @param ServerSyncTool $serverSync
|
||||
* @param array $conditions
|
||||
* @return array The list of analyst data
|
||||
* @throws JsonException|HttpSocketHttpException|HttpSocketJsonException
|
||||
*/
|
||||
public function fetchUUIDsFromServer(ServerSyncTool $serverSync, array $conditions = [])
|
||||
{
|
||||
$filterRules = $conditions;
|
||||
$dataArray = $serverSync->fetchIndexMinimal($filterRules)->json();
|
||||
if (isset($dataArray['response'])) {
|
||||
$dataArray = $dataArray['response'];
|
||||
}
|
||||
return $dataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* filterAnalystDataForPush Send a candidate data to be pushed and returns the list of accepted entries
|
||||
*
|
||||
* @param ServerSyncTool $serverSync
|
||||
* @param array $conditions
|
||||
* @return array The list of analyst data
|
||||
* @throws JsonException|HttpSocketHttpException|HttpSocketJsonException
|
||||
*/
|
||||
public function filterAnalystDataForPush(ServerSyncTool $serverSync, array $candidates = [])
|
||||
{
|
||||
$dataArray = $serverSync->filterAnalystDataForPush($candidates)->json();
|
||||
if (isset($dataArray['response'])) {
|
||||
$dataArray = $dataArray['response'];
|
||||
}
|
||||
return $dataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of cluster IDs that are present on the remote server and returns clusters that should be pulled
|
||||
*
|
||||
|
@ -1239,6 +1277,20 @@ class Server extends AppModel
|
|||
} else {
|
||||
$successes = array_merge($successes, $sightingSuccesses);
|
||||
}
|
||||
|
||||
if ($push['canPush'] || $push['canEditAnalystData']) {
|
||||
$this->AnalystData = ClassRegistry::init('AnalystData');
|
||||
$analystDataSuccesses = $this->AnalystData->push($user, $serverSync);
|
||||
} else {
|
||||
$analystDataSuccesses = array();
|
||||
}
|
||||
|
||||
if (!isset($successes)) {
|
||||
$successes = $analystDataSuccesses;
|
||||
} else {
|
||||
$successes = array_merge($successes, $analystDataSuccesses);
|
||||
}
|
||||
|
||||
if (!isset($fails)) {
|
||||
$fails = array();
|
||||
}
|
||||
|
@ -2543,33 +2595,33 @@ class Server extends AppModel
|
|||
|
||||
public function getFileRules()
|
||||
{
|
||||
return array(
|
||||
'orgs' => array(
|
||||
return [
|
||||
'orgs' => [
|
||||
'name' => __('Organisation logos'),
|
||||
'description' => __('The logo used by an organisation on the event index, event view, discussions, proposals, etc. Make sure that the filename is in the org.png format, where org is the case-sensitive organisation name.'),
|
||||
'expected' => array(),
|
||||
'expected' => [],
|
||||
'valid_format' => __('48x48 pixel .png files or .svg file'),
|
||||
'path' => APP . 'webroot' . DS . 'img' . DS . 'orgs',
|
||||
'path' => APP . 'files' . DS . 'img' . DS . 'orgs',
|
||||
'regex' => '.*\.(png|svg)$',
|
||||
'regex_error' => __('Filename must be in the following format: *.png or *.svg'),
|
||||
'files' => array(),
|
||||
),
|
||||
'img' => array(
|
||||
'files' => [],
|
||||
],
|
||||
'img' => [
|
||||
'name' => __('Additional image files'),
|
||||
'description' => __('Image files uploaded into this directory can be used for various purposes, such as for the login page logos'),
|
||||
'expected' => array(
|
||||
'expected' => [
|
||||
'MISP.footer_logo' => Configure::read('MISP.footer_logo'),
|
||||
'MISP.home_logo' => Configure::read('MISP.home_logo'),
|
||||
'MISP.welcome_logo' => Configure::read('MISP.welcome_logo'),
|
||||
'MISP.welcome_logo2' => Configure::read('MISP.welcome_logo2'),
|
||||
),
|
||||
],
|
||||
'valid_format' => __('PNG or SVG file'),
|
||||
'path' => APP . 'webroot' . DS . 'img' . DS . 'custom',
|
||||
'path' => APP . 'files' . DS . 'img' . DS . 'custom',
|
||||
'regex' => '.*\.(png|svg)$',
|
||||
'regex_error' => __('Filename must be in the following format: *.png or *.svg'),
|
||||
'files' => array(),
|
||||
),
|
||||
);
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function grabFiles()
|
||||
|
@ -2752,6 +2804,7 @@ class Server extends AppModel
|
|||
$canPush = isset($remoteVersion['perm_sync']) ? $remoteVersion['perm_sync'] : false;
|
||||
$canSight = isset($remoteVersion['perm_sighting']) ? $remoteVersion['perm_sighting'] : false;
|
||||
$canEditGalaxyCluster = isset($remoteVersion['perm_galaxy_editor']) ? $remoteVersion['perm_galaxy_editor'] : false;
|
||||
$canEditAnalystData = isset($remoteVersion['perm_analyst_data']) ? $remoteVersion['perm_analyst_data'] : false;
|
||||
$remoteVersionString = $remoteVersion['version'];
|
||||
$remoteVersion = explode('.', $remoteVersion['version']);
|
||||
if (!isset($remoteVersion[0])) {
|
||||
|
@ -2801,6 +2854,7 @@ class Server extends AppModel
|
|||
'response' => $response,
|
||||
'canPush' => $canPush,
|
||||
'canSight' => $canSight,
|
||||
'canEditAnalystData' => $canEditAnalystData,
|
||||
'canEditGalaxyCluster' => $canEditGalaxyCluster,
|
||||
'version' => $remoteVersion,
|
||||
'protectedMode' => $protectedMode,
|
||||
|
|
|
@ -39,22 +39,21 @@ class Module_attribute_edition_operation extends WorkflowBaseActionModule
|
|||
protected function __saveAttributes(array $attributes, array $rData, array $params, array $user): array
|
||||
{
|
||||
$success = false;
|
||||
$attributes = [];
|
||||
$newAttributes = [];
|
||||
foreach ($attributes as $k => $attribute) {
|
||||
$newAttribute = $this->_editAttribute($attribute, $rData, $params);
|
||||
$newAttributes[] = $newAttribute;
|
||||
unset($newAttribute['timestamp']);
|
||||
$newAttributes[] = $newAttribute;
|
||||
$result = $this->Attribute->editAttribute($newAttribute, $rData, $user, $newAttribute['object_id']);
|
||||
if (is_array($result)) {
|
||||
$attributes[] = $result;
|
||||
}
|
||||
}
|
||||
$this->Attribute->editAttributeBulk($attributes, $rData, $user);
|
||||
foreach ($attributes as $k => $attribute) {
|
||||
$this->Attribute->editAttributeBulk($newAttributes, $rData, $user);
|
||||
foreach ($newAttributes as $k => $attribute) {
|
||||
$saveSuccess = empty($this->Attribute->validationErrors[$k]);
|
||||
if ($saveSuccess) {
|
||||
$rData = $this->_overrideAttribute($attribute, $newAttribute, $rData);
|
||||
$rData = $this->_overrideAttribute($attribute, $attribute, $rData);
|
||||
}
|
||||
$success = $success || !empty($saveSuccess);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
$edit = $this->request->params['action'] === 'edit' ? true : false;
|
||||
$fields = [
|
||||
[
|
||||
'field' => 'object_type',
|
||||
'class' => 'span2',
|
||||
'disabled' => !empty($this->data[$modelSelection]['object_type']),
|
||||
'default' => empty($this->data[$modelSelection]['object_type']) ? null : $this->data[$modelSelection]['object_type'],
|
||||
'options' => $dropdownData['valid_targets'],
|
||||
'type' => 'dropdown',
|
||||
'stayInLine' => 1
|
||||
],
|
||||
[
|
||||
'field' => 'object_uuid',
|
||||
'class' => 'span4',
|
||||
'disabled' => !empty($this->data[$modelSelection]['object_uuid']),
|
||||
'default' => empty($this->data[$modelSelection]['object_uuid']) ? null : $this->data[$modelSelection]['object_uuid']
|
||||
],
|
||||
[
|
||||
'field' => 'distribution',
|
||||
'class' => 'input',
|
||||
'options' => $dropdownData['distributionLevels'],
|
||||
'default' => isset($attribute['Attribute']['distribution']) ? $attribute['Attribute']['distribution'] : $initialDistribution,
|
||||
'stayInLine' => 1,
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'sharing_group_id',
|
||||
'class' => 'input',
|
||||
'options' => $dropdownData['sgs'],
|
||||
'label' => __("Sharing Group"),
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'authors',
|
||||
'class' => 'span3',
|
||||
'stayInLine' => $modelSelection === 'Note',
|
||||
'default' => $me['email'],
|
||||
],
|
||||
];
|
||||
|
||||
if ($modelSelection === 'Note') {
|
||||
$fields = array_merge($fields,
|
||||
[
|
||||
[
|
||||
'field' => 'language',
|
||||
'class' => 'span3',
|
||||
'options' => $languageRFC5646,
|
||||
'type' => 'dropdown',
|
||||
],
|
||||
[
|
||||
'field' => 'note',
|
||||
'type' => 'textarea',
|
||||
'class' => 'input span6',
|
||||
]
|
||||
]
|
||||
);
|
||||
} 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',
|
||||
'options' => $existingRelations,
|
||||
'type' => 'text',
|
||||
'picker' => array(
|
||||
'text' => __('Pick Relationship'),
|
||||
'function' => 'pickerRelationshipTypes',
|
||||
)
|
||||
],
|
||||
[
|
||||
'field' => 'related_object_type',
|
||||
'class' => 'span2',
|
||||
'options' => $dropdownData['valid_targets'],
|
||||
'type' => 'dropdown',
|
||||
'stayInLine' => 1,
|
||||
],
|
||||
[
|
||||
'field' => 'related_object_uuid',
|
||||
'class' => 'span4',
|
||||
],
|
||||
sprintf('<div style="max-width: 960px;"><label>%s:</label><div id="related-object-container">%s</div></div>', __('Related Object'), __('- No UUID provided -'))
|
||||
]
|
||||
);
|
||||
}
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => false,
|
||||
'model' => $modelSelection,
|
||||
'title' => $edit ? __('Edit %s', $modelSelection) : __('Add new %s', $modelSelection),
|
||||
'fields' => $fields,
|
||||
'submit' => [
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => 'submitGenericFormInPlace(analystDataSubmitSuccess, true);'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
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) {
|
||||
if (!type || !uuid) {
|
||||
return
|
||||
}
|
||||
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() {
|
||||
$('#<?= h($modelSelection) ?>Distribution').change(function() {
|
||||
checkSharingGroup('<?= h($modelSelection) ?>');
|
||||
});
|
||||
checkSharingGroup('<?= h($modelSelection) ?>');
|
||||
|
||||
$('#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())
|
||||
}
|
||||
})
|
||||
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
|
||||
})
|
||||
|
||||
<?php if ($modelSelection === 'Relationship'): ?>
|
||||
function pickerRelationshipTypes() {
|
||||
var existingRelationTypes = <?= json_encode(array_values($existingRelations)) ?> ;
|
||||
var $select = $('<select id="pickerRelationshipTypeSelect"/>');
|
||||
existingRelationTypes.forEach(function(type) {
|
||||
$select.append($('<option/>').val(type).text(type))
|
||||
})
|
||||
var html = '<div>' + $select[0].outerHTML + '</div>';
|
||||
var that = this
|
||||
openPopover(this, html, false, 'right', function($popover) {
|
||||
$popover.css('z-index', 1060)
|
||||
$popover.find('select').chosen({
|
||||
width: '300px',
|
||||
}).on('change', function(evt, param) {
|
||||
$('#RelationshipRelationshipType').val($('#pickerRelationshipTypeSelect').val());
|
||||
$(that).popover('hide')
|
||||
});
|
||||
});
|
||||
}
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#related-object-container {
|
||||
box-shadow: 0 0 5px 0px #22222266;
|
||||
padding: 0.5rem;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'name' => __('Id'),
|
||||
'sort' => $modelSelection . '.id',
|
||||
'data_path' => $modelSelection . '.id'
|
||||
],
|
||||
[
|
||||
'name' => __('OrgC'),
|
||||
'element' => 'org',
|
||||
'data_path' => $modelSelection . '.Orgc'
|
||||
],
|
||||
[
|
||||
'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',
|
||||
'data_path' => $modelSelection . '.object_uuid'
|
||||
],
|
||||
[
|
||||
'name' => __('Creator org'),
|
||||
'data_path' => $modelSelection . '.orgc_uuid'
|
||||
],
|
||||
[
|
||||
'name' => __('Created'),
|
||||
'sort' => $modelSelection . '.created',
|
||||
'data_path' => $modelSelection . '.created'
|
||||
],
|
||||
[
|
||||
'name' => __('Modified'),
|
||||
'sort' => $modelSelection . '.modified',
|
||||
'data_path' => $modelSelection . '.modified'
|
||||
],
|
||||
[
|
||||
'name' => __('Distribution'),
|
||||
'element' => 'distribution_levels',
|
||||
'sort' => $modelSelection . '.distribution',
|
||||
'class' => 'short',
|
||||
'data_path' => $modelSelection . '.distribution',
|
||||
'sg_path' => $modelSelection . '.SharingGroup',
|
||||
]
|
||||
];
|
||||
|
||||
if ($modelSelection === 'Note') {
|
||||
$fields = array_merge($fields,
|
||||
[
|
||||
[
|
||||
'name' => __('Language'),
|
||||
'sort' => $modelSelection . '.language',
|
||||
'data_path' => $modelSelection . '.language'
|
||||
],
|
||||
[
|
||||
'name' => __('Note'),
|
||||
'sort' => $modelSelection . '.note',
|
||||
'data_path' => $modelSelection . '.note'
|
||||
]
|
||||
]
|
||||
);
|
||||
} else if ($modelSelection === 'Opinion') {
|
||||
$fields = array_merge($fields,
|
||||
[
|
||||
[
|
||||
'name' => __('Comment'),
|
||||
'data_path' => $modelSelection . '.comment'
|
||||
],
|
||||
[
|
||||
'name' => __('Opinion'),
|
||||
'data_path' => $modelSelection . '.opinion',
|
||||
'element' => 'opinion_scale',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
} else if ($modelSelection === 'Relationship') {
|
||||
$fields = array_merge($fields,
|
||||
[
|
||||
[
|
||||
'name' => __('Related Object'),
|
||||
'element' => 'custom',
|
||||
'function' => function (array $row) use ($baseurl, $modelSelection) {
|
||||
$path = Inflector::pluralize(strtolower($row[$modelSelection]['related_object_type']));
|
||||
return sprintf(
|
||||
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
|
||||
h($row[$modelSelection]['related_object_type']),
|
||||
h($baseurl),
|
||||
h($path),
|
||||
h($row[$modelSelection]['related_object_uuid']),
|
||||
h($row[$modelSelection]['related_object_uuid'])
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => __('Relationship_type'),
|
||||
'sort' => $modelSelection . '.relationship_type',
|
||||
'data_path' => $modelSelection . '.relationship_type'
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
echo $this->element('genericElements/IndexTable/scaffold', [
|
||||
'scaffold_data' => [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'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'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'quickFilter'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'title' => empty($ajax) ? __('%s index', Inflector::pluralize($modelSelection)) : false,
|
||||
'actions' => [
|
||||
[
|
||||
'url' => $baseurl . '/analystData/view/' . $modelSelection,
|
||||
'url_params_data_paths' => [$modelSelection . '.id'],
|
||||
'icon' => 'eye',
|
||||
'dbclickAction' => true,
|
||||
],
|
||||
[
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/analystData/edit/' . $modelSelection . '/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => $modelSelection . '.id',
|
||||
'title' => __('Edit %s', $modelSelection),
|
||||
'icon' => 'edit',
|
||||
'complex_requirement' => function($item) use ($modelSelection) {
|
||||
return !empty($item[$modelSelection]['_canEdit']);
|
||||
}
|
||||
],
|
||||
[
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/analystData/delete/' . $modelSelection . '/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => $modelSelection . '.id',
|
||||
'icon' => 'trash',
|
||||
'complex_requirement' => function($item) use ($modelSelection) {
|
||||
return !empty($item[$modelSelection]['_canEdit']);
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
?>
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => $modelSelection . '.id'
|
||||
],
|
||||
[
|
||||
'key' => 'UUID',
|
||||
'path' => $modelSelection . '.uuid',
|
||||
'class' => '',
|
||||
'type' => 'uuid',
|
||||
'object_type' => $modelSelection,
|
||||
'notes_path' => $modelSelection . '.Note',
|
||||
'opinions_path' => $modelSelection . '.Opinion',
|
||||
'relationships_path' => $modelSelection . '.Relationship',
|
||||
],
|
||||
[
|
||||
'key' => __('Note Type'),
|
||||
'path' => $modelSelection . '.note_type_name'
|
||||
],
|
||||
[
|
||||
'key' => __('Target Object'),
|
||||
'type' => 'custom',
|
||||
'function' => function (array $row) use ($baseurl, $modelSelection) {
|
||||
$path = Inflector::pluralize(strtolower($row[$modelSelection]['object_type']));
|
||||
return sprintf(
|
||||
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
|
||||
h($row[$modelSelection]['object_type']),
|
||||
h($baseurl),
|
||||
h($path),
|
||||
h($row[$modelSelection]['object_uuid']),
|
||||
h($row[$modelSelection]['object_uuid'])
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'key' => __('Creator org'),
|
||||
'path' => $modelSelection . '.Orgc',
|
||||
'pathName' => $modelSelection . '.orgc_uuid',
|
||||
'type' => 'org',
|
||||
'model' => 'organisations'
|
||||
],
|
||||
[
|
||||
'key' => __('Created'),
|
||||
'path' => $modelSelection . '.created'
|
||||
],
|
||||
[
|
||||
'key' => __('Modified'),
|
||||
'path' => $modelSelection . '.modified'
|
||||
],
|
||||
[
|
||||
'key' => __('Distribution'),
|
||||
'path' => $modelSelection . '.distribution',
|
||||
'event_id_path' => $modelSelection . '.id',
|
||||
'disable_distribution_graph' => true,
|
||||
'sg_path' => $modelSelection . '.SharingGroup',
|
||||
'type' => 'distribution'
|
||||
],
|
||||
[
|
||||
'key' => __('Authors'),
|
||||
'path' => $modelSelection . '.authors'
|
||||
],
|
||||
];
|
||||
|
||||
if ($modelSelection === 'Note') {
|
||||
$fields[] = [
|
||||
'key' => __('Language'),
|
||||
'path' => $modelSelection . '.language'
|
||||
];
|
||||
$fields[] = [
|
||||
'key' => __('Note'),
|
||||
'path' => $modelSelection . '.note'
|
||||
];
|
||||
} else if ($modelSelection === 'Opinion') {
|
||||
$fields[] = [
|
||||
'key' => __('Comment'),
|
||||
'path' => $modelSelection . '.comment'
|
||||
];
|
||||
$fields[] = [
|
||||
'key' => __('Opinion'),
|
||||
'path' => $modelSelection . '.opinion',
|
||||
'type' => 'opinion_scale',
|
||||
];
|
||||
} else if ($modelSelection === 'Relationship') {
|
||||
$fields[] = [
|
||||
'key' => __('Related Object'),
|
||||
'type' => 'custom',
|
||||
'function' => function (array $row) use ($baseurl, $modelSelection) {
|
||||
$path = Inflector::pluralize(strtolower($row[$modelSelection]['related_object_type']));
|
||||
return sprintf(
|
||||
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
|
||||
h($row[$modelSelection]['related_object_type']),
|
||||
h($baseurl),
|
||||
h($path),
|
||||
h($row[$modelSelection]['related_object_uuid']),
|
||||
h($row[$modelSelection]['related_object_uuid'])
|
||||
);
|
||||
}
|
||||
];
|
||||
$fields[] = [
|
||||
'key' => __('Relationship_type'),
|
||||
'path' => $modelSelection . '.relationship_type'
|
||||
];
|
||||
}
|
||||
|
||||
echo $this->element(
|
||||
'genericElements/SingleViews/single_view',
|
||||
[
|
||||
'title' => __('%s view', h($modelSelection)),
|
||||
'data' => $data,
|
||||
'fields' => $fields,
|
||||
'side_panels' => [
|
||||
[
|
||||
'type' => 'html',
|
||||
'html' => '<div id="analyst_data_thread" class="panel-container"></div>',
|
||||
]
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$object_uuid = Hash::get($data, $modelSelection . '.uuid');
|
||||
$options = [
|
||||
'container_id' => 'analyst_data_thread',
|
||||
'object_type' => $modelSelection,
|
||||
'object_uuid' => $object_uuid,
|
||||
'shortDist' => $shortDist,
|
||||
'notes' => $data[$modelSelection]['Note'] ?? [],
|
||||
'opinions' => $data[$modelSelection]['Opinion'] ?? [],
|
||||
'relationships' => $data[$modelSelection]['Relationship'] ?? [],
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/assetLoader', [
|
||||
'js' => ['doT', 'moment.min'],
|
||||
'css' => ['analyst-data',],
|
||||
]);
|
||||
echo $this->element('genericElements/Analyst_data/thread', $options);
|
||||
?>
|
||||
|
||||
<?php if ($modelSelection == 'Relationship') : ?>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#analyst_data_thread').find('li > a[href^="#relationship"]').tab('show')
|
||||
})
|
||||
</script>
|
||||
<?php endif; ?>
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
$fieldDesc = array();
|
||||
$fieldDesc['uuids'] = __('Enter a single or a list of UUIDs');
|
||||
$fieldDesc['analyst_data_orgc'] = __('(Optional) The organisation that the event is associated with');
|
||||
$fieldDesc['analyst_data_info'] = __('(Optional) The analyst data value that you would like to block');
|
||||
$fieldDesc['comment'] = __('(Optional) Any comments you would like to add regarding this (or these) entries');
|
||||
echo $this->element('genericElements/Form/genericForm', array(
|
||||
'form' => $this->Form,
|
||||
'data' => array(
|
||||
'model' => 'AnalystDataBlocklist',
|
||||
'title' => $action == 'add' ? __('Add block entry for Analyst Data') : __('Edit block entry for Analyst Data'),
|
||||
'fields' => array(
|
||||
array(
|
||||
'disabled' => $action != 'add' ? 'disabled' : '',
|
||||
'field' => 'uuids',
|
||||
'class' => 'span6',
|
||||
'label' => __('Analyst Data UUID'),
|
||||
'type' => 'textarea',
|
||||
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_uuid']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_uuid'] : '',
|
||||
),
|
||||
array(
|
||||
'field' => 'analyst_data_orgc',
|
||||
'label' => __('Creating organisation'),
|
||||
'class' => 'span6',
|
||||
'type' => 'text',
|
||||
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_orgc']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_orgc'] : ''
|
||||
),
|
||||
array(
|
||||
'field' => 'analyst_data_info',
|
||||
'label' => __('Analyst Data value'),
|
||||
'class' => 'span6',
|
||||
'type' => 'text',
|
||||
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_info']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_info'] : ''
|
||||
),
|
||||
array(
|
||||
'field' => 'comment',
|
||||
'label' => __('Comment'),
|
||||
'class' => 'span6',
|
||||
'type' => 'text',
|
||||
'default' => isset($blockEntry['AnalystDataBlocklist']['comment']) ? $blockEntry['AnalystDataBlocklist']['comment'] : ''
|
||||
),
|
||||
),
|
||||
'submit' => array(
|
||||
'ajaxSubmit' => ''
|
||||
)
|
||||
),
|
||||
'fieldDesc' => $fieldDesc
|
||||
));
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'analyst_data', 'menuItem' => 'index_blocklist'));
|
||||
?>
|
||||
|
||||
<?php echo $this->Js->writeBuffer(); // Write cached scripts
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
echo '<div class="index">';
|
||||
echo $this->element('/genericElements/IndexTable/index_table', array(
|
||||
'data' => array(
|
||||
'data' => $response,
|
||||
'top_bar' => array(
|
||||
'children' => array(
|
||||
array(
|
||||
'type' => 'simple',
|
||||
'children' => array(
|
||||
array(
|
||||
'url' => sprintf('%s/analyst_data_blocklists/add/', $baseurl),
|
||||
'text' => __('+ Add entry to blocklist'),
|
||||
),
|
||||
)
|
||||
),
|
||||
array(
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'searchall'
|
||||
)
|
||||
)
|
||||
),
|
||||
'fields' => array(
|
||||
array(
|
||||
'name' => __('Id'),
|
||||
'sort' => 'id',
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.id',
|
||||
),
|
||||
array(
|
||||
'name' => __('Org'),
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.analyst_data_orgc',
|
||||
),
|
||||
array(
|
||||
'name' => __('Analyst Data UUID'),
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.analyst_data_uuid',
|
||||
),
|
||||
array(
|
||||
'name' => __('Created'),
|
||||
'sort' => 'created',
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.created',
|
||||
),
|
||||
array(
|
||||
'name' => __('Analyst Data value'),
|
||||
'sort' => 'value',
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.analyst_data_info',
|
||||
),
|
||||
array(
|
||||
'name' => __('Comment'),
|
||||
'sort' => 'comment',
|
||||
'class' => 'short',
|
||||
'data_path' => 'AnalystDataBlocklist.comment',
|
||||
),
|
||||
),
|
||||
'title' => __('Analyst Data Blocklist Index'),
|
||||
'description' => __('List all analyst data that will be prevented to be created (also via synchronization) on this instance'),
|
||||
'actions' => array(
|
||||
array(
|
||||
'title' => 'Edit',
|
||||
'url' => '/analyst_data_blocklists/edit',
|
||||
'url_params_data_paths' => array(
|
||||
'AnalystDataBlocklist.id'
|
||||
),
|
||||
'icon' => 'edit',
|
||||
),
|
||||
array(
|
||||
'title' => 'Delete',
|
||||
'url' => $baseurl . '/analyst_data_blocklists/delete',
|
||||
'url_params_data_paths' => array(
|
||||
'AnalystDataBlocklist.id'
|
||||
),
|
||||
'postLink' => true,
|
||||
'postLinkConfirm' => __('Are you sure you want to delete the entry?'),
|
||||
'icon' => 'trash'
|
||||
),
|
||||
)
|
||||
)
|
||||
));
|
||||
echo '</div>';
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'analyst_data', 'menuItem' => 'index_blocklist'));
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
var passedArgsArray = <?php echo $passedArgs; ?>;
|
||||
if (passedArgsArray['context'] === undefined) {
|
||||
passedArgsArray['context'] = 'pending';
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('#quickFilterButton').click(function() {
|
||||
runIndexQuickFilter('/context:' + passedArgsArray['context']);
|
||||
});
|
||||
$('#quickFilterField').on('keypress', function (e) {
|
||||
if(e.which === 13) {
|
||||
runIndexQuickFilter('/context:' + passedArgsArray['context']);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
$edit = $this->request->params['action'] === 'edit' ? true : false;
|
||||
$fields = [
|
||||
[
|
||||
'field' => 'element_uuid',
|
||||
'class' => 'input span6',
|
||||
'onChange' => 'alert(1);'
|
||||
],
|
||||
[
|
||||
'field' => 'element_type',
|
||||
'class' => 'input span6',
|
||||
'options' => $dropdownData['types'],
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
'class' => 'span6',
|
||||
'type' => 'textarea'
|
||||
]
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => null,
|
||||
'model' => 'CollectionElement',
|
||||
'title' => __('Add element to Collection'),
|
||||
'fields' => $fields,
|
||||
'submit' => [
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => 'submitGenericFormInPlace();'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$ajax) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'field' => 'collection_id',
|
||||
'class' => 'input span6',
|
||||
'options' => $dropdownData['collections'],
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
'class' => 'span6',
|
||||
'type' => 'textarea'
|
||||
]
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => null,
|
||||
'model' => 'CollectionElement',
|
||||
'title' => __('Add element to Collection'),
|
||||
'fields' => $fields,
|
||||
'submit' => [
|
||||
'action' => $this->request->params['action'],
|
||||
//'ajaxSubmit' => 'submitGenericFormInPlace();'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'name' => __('Id'),
|
||||
'sort' => 'CollectionElement.id',
|
||||
'data_path' => 'CollectionElement.id'
|
||||
],
|
||||
[
|
||||
'name' => __('UUID'),
|
||||
'data_path' => 'CollectionElement.uuid'
|
||||
],
|
||||
[
|
||||
'name' => __('Element'),
|
||||
'sort' => 'CollectionElement.element_type',
|
||||
'element' => 'model',
|
||||
'model_name' => 'CollectionElement.element_type',
|
||||
'model_id' => 'CollectionElement.element_uuid'
|
||||
],
|
||||
[
|
||||
'name' => __('Element type'),
|
||||
'data_path' => 'CollectionElement.element_type'
|
||||
],
|
||||
[
|
||||
'name' => __('Description'),
|
||||
'data_path' => 'CollectionElement.description'
|
||||
]
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/IndexTable/scaffold', [
|
||||
'scaffold_data' => [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'pull' => 'right',
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'quickFilter'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'title' => empty($ajax) ? __('Collection element index') : false,
|
||||
'actions' => [
|
||||
[
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/collectionElements/delete/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => 'CollectionElement.id',
|
||||
'icon' => 'trash'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
?>
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
$edit = $this->request->params['action'] === 'edit' ? true : false;
|
||||
$fields = [
|
||||
[
|
||||
'field' => 'name',
|
||||
'class' => 'span6'
|
||||
],
|
||||
[
|
||||
'field' => 'type',
|
||||
'class' => 'input span6',
|
||||
'options' => $dropdownData['types'],
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
'class' => 'span6',
|
||||
'type' => 'textarea'
|
||||
],
|
||||
[
|
||||
'field' => 'distribution',
|
||||
'class' => 'input',
|
||||
'options' => $dropdownData['distributionLevels'],
|
||||
'default' => isset($data['Collection']['distribution']) ? $data['Collection']['distribution'] : $initialDistribution,
|
||||
'stayInLine' => 1,
|
||||
'type' => 'dropdown'
|
||||
],
|
||||
[
|
||||
'field' => 'sharing_group_id',
|
||||
'class' => 'input',
|
||||
'options' => $dropdownData['sgs'],
|
||||
'label' => __("Sharing Group"),
|
||||
'type' => 'dropdown'
|
||||
]
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => __('Create collections to organise data shared by the community into buckets based on commonalities or as part of your research process. Collections are first class citizens and adhere to the same sharing rules as for example events do.'),
|
||||
'model' => 'Collection',
|
||||
'title' => $edit ? __('Edit collection') : __('Add new collection'),
|
||||
'fields' => $fields,
|
||||
'submit' => [
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => 'submitGenericFormInPlace();'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$ajax) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'name' => __('Id'),
|
||||
'sort' => 'Collection.id',
|
||||
'data_path' => 'Collection.id'
|
||||
],
|
||||
[
|
||||
'name' => __('Name'),
|
||||
'sort' => 'Collection.name',
|
||||
'data_path' => 'Collection.name'
|
||||
],
|
||||
[
|
||||
'name' => __('Organisation'),
|
||||
'sort' => 'Orgc.name',
|
||||
'data_path' => 'Orgc',
|
||||
'element' => 'org'
|
||||
],
|
||||
[
|
||||
'name' => __('Elements'),
|
||||
'sort' => 'Collection.element_count',
|
||||
'data_path' => 'Collection.element_count'
|
||||
],
|
||||
[
|
||||
'name' => __('UUID'),
|
||||
'data_path' => 'Collection.uuid'
|
||||
],
|
||||
[
|
||||
'name' => __('Type'),
|
||||
'data_path' => 'Collection.type'
|
||||
],
|
||||
[
|
||||
'name' => __('Created'),
|
||||
'sort' => 'Collection.created',
|
||||
'data_path' => 'Collection.created'
|
||||
],
|
||||
[
|
||||
'name' => __('Modified'),
|
||||
'sort' => 'Collection.modified',
|
||||
'data_path' => 'Collection.modified'
|
||||
],
|
||||
[
|
||||
'name' => __('Distribution'),
|
||||
'sort' => 'distribution',
|
||||
'data_path' => 'Collection.distribution',
|
||||
'element' => 'distribution_levels'
|
||||
],
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/IndexTable/scaffold', [
|
||||
'scaffold_data' => [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'pull' => 'right',
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'quickFilter'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'title' => empty($ajax) ? __('Collections index') : false,
|
||||
'actions' => [
|
||||
[
|
||||
'url' => $baseurl . '/collections/view',
|
||||
'url_params_data_paths' => ['Collection.id'],
|
||||
'icon' => 'eye'
|
||||
],
|
||||
[
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/collections/edit/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => 'Collection.id',
|
||||
'title' => __('Edit Collection'),
|
||||
'icon' => 'edit'
|
||||
],
|
||||
[
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/collections/delete/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => 'Collection.id',
|
||||
'icon' => 'trash'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
?>
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
echo $this->element(
|
||||
'genericElements/SingleViews/single_view',
|
||||
[
|
||||
'title' => __('Collection view'),
|
||||
'data' => $data,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'Collection.id'
|
||||
],
|
||||
[
|
||||
'key' => __('UUID'),
|
||||
'path' => 'Collection.uuid'
|
||||
],
|
||||
[
|
||||
'key' => __('Creator org'),
|
||||
'path' => 'Collection.orgc_id',
|
||||
'pathName' => 'Collection.Orgc.name',
|
||||
'type' => 'model',
|
||||
'model' => 'organisations'
|
||||
],
|
||||
[
|
||||
'key' => __('Owner org'),
|
||||
'path' => 'Collection.org_id',
|
||||
'pathName' => 'Collection.Org.name',
|
||||
'type' => 'model',
|
||||
'model' => 'organisations'
|
||||
],
|
||||
[
|
||||
'key' => __('Created'),
|
||||
'path' => 'Collection.created'
|
||||
],
|
||||
[
|
||||
'key' => __('Modified'),
|
||||
'path' => 'Collection.modified'
|
||||
],
|
||||
[
|
||||
'key' => __('Name'),
|
||||
'path' => 'Collection.name'
|
||||
],
|
||||
[
|
||||
'key' => __('Description'),
|
||||
'path' => 'Collection.description'
|
||||
],
|
||||
[
|
||||
'key' => __('Distribution'),
|
||||
'path' => 'Collection.distribution',
|
||||
'event_id_path' => 'Collection.id',
|
||||
'disable_distribution_graph' => true,
|
||||
'sg_path' => 'Collection.sharing_group_id',
|
||||
'type' => 'distribution'
|
||||
]
|
||||
],
|
||||
'children' => [
|
||||
[
|
||||
'url' => '/collectionElements/index/{{0}}/',
|
||||
'url_params' => ['Collection.id'],
|
||||
'title' => __('Collection elements'),
|
||||
'elementId' => 'preview_elements_container'
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
|
@ -68,11 +68,37 @@
|
|||
</td>
|
||||
<?php endif; ?>
|
||||
<td class="short context hidden"><?= $objectId ?></td>
|
||||
<td class="short context hidden uuid quickSelect"><?php echo h($object['uuid']); ?></td>
|
||||
<td class="short context hidden uuid">
|
||||
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
|
||||
<?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
|
||||
$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)):
|
||||
?>
|
||||
|
|
|
@ -30,11 +30,37 @@ $objectId = intval($object['id']);
|
|||
endif;
|
||||
?>
|
||||
<td class="short context hidden"><?= $objectId ?></td>
|
||||
<td class="short context hidden uuid quickSelect"><?php echo h($object['uuid']); ?></td>
|
||||
<td class="short context hidden uuid">
|
||||
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
|
||||
<?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
|
||||
$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):
|
||||
?>
|
||||
|
|
|
@ -45,7 +45,19 @@
|
|||
echo h($object['id']);
|
||||
?>
|
||||
</td>
|
||||
<td class="short context hidden uuid quickSelect"><?= h($object['uuid']) ?></td>
|
||||
<td class="short context hidden uuid">
|
||||
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
|
||||
<?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>
|
||||
|
@ -55,6 +67,20 @@
|
|||
else echo ' ';
|
||||
?>
|
||||
</td>
|
||||
<td class="short context">
|
||||
<?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):
|
||||
?>
|
||||
|
|
|
@ -91,6 +91,7 @@
|
|||
<th class="context hidden">UUID</th>
|
||||
<th class="context hidden"><?= $this->Paginator->sort('first_seen', __('First seen')) ?> <i class="fas fa-arrow-right"></i> <?= $this->Paginator->sort('last_seen', __('Last seen')) ?></th>
|
||||
<th><?php echo $this->Paginator->sort('timestamp', __('Date'), array('direction' => 'desc'));?></th>
|
||||
<th class="context"><?= __('Context') ?></th>
|
||||
<?php if ($extended): ?>
|
||||
<th class="event_id"><?php echo $this->Paginator->sort('event_id', __('Event'));?></th>
|
||||
<?php endif; ?>
|
||||
|
@ -212,7 +213,6 @@ attributes or the appropriate distribution level. If you think there is a mistak
|
|||
<?php
|
||||
endif;
|
||||
?>
|
||||
setContextFields();
|
||||
popoverStartup();
|
||||
$('.select_attribute').prop('checked', false).click(function(e) {
|
||||
if ($(this).is(':checked')) {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<div class="pull-right" style="position:relative;padding-top:9px;z-index:2;">
|
||||
<?php
|
||||
if (Configure::read('MISP.footer_logo')) {
|
||||
echo $this->Html->image('custom/' . h(Configure::read('MISP.footer_logo')), array('alt' => 'Footer Logo', 'onerror' => "this.style.display='none';", 'style' => 'height:24px'));
|
||||
echo '<img src="' . $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.footer_logo')) . '" alt="' . __('Footer logo') . '" style="height:24px" onerror="this.style.display=\'none\';">';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
$seed = mt_rand();
|
||||
|
||||
$notes = $analyst_data['notes'] ?? [];
|
||||
$opinions = $analyst_data['opinions'] ?? [];
|
||||
$relationships = $analyst_data['relationships'] ?? [];
|
||||
|
||||
$notesOpinions = array_merge($notes, $opinions);
|
||||
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
|
||||
|
||||
if(!function_exists("countNotes")) {
|
||||
function countNotes($notesOpinions) {
|
||||
$notesTotalCount = count($notesOpinions);
|
||||
$notesCount = 0;
|
||||
$relationsCount = 0;
|
||||
foreach ($notesOpinions as $notesOpinion) {
|
||||
if ($notesOpinion['note_type'] == 2) { // relationship
|
||||
$relationsCount += 1;
|
||||
} else {
|
||||
$notesCount += 1;
|
||||
}
|
||||
if (!empty($notesOpinion['Note'])) {
|
||||
$nestedCounts = countNotes($notesOpinion['Note']);
|
||||
$notesTotalCount += $nestedCounts['total'];
|
||||
$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, 'notesOpinions' => $notesCount, 'relations' => $relationsCount];
|
||||
}
|
||||
}
|
||||
$counts = countNotes($notesOpinions);
|
||||
$notesOpinionCount = $counts['notesOpinions'];
|
||||
$relationshipsCount = count($relationships);
|
||||
?>
|
||||
|
||||
<?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 node-opener-<?= $seed ?>">
|
||||
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer" title="<?= __('Notes and opinions for this UUID') ?>"></i>
|
||||
<?= $notesOpinionCount; ?>
|
||||
<i class="<?= $this->FontAwesome->getClass('project-diagram') ?> useCursorPointer" title="<?= __('Relationships for this UUID') ?>"></i>
|
||||
<?= $relationshipsCount; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.node-opener-<?= $seed ?>').click(function() {
|
||||
openNotes(this)
|
||||
})
|
||||
|
||||
function adjustPopoverPosition() {
|
||||
var $popover = $('.popover:last');
|
||||
$popover.css('top', Math.max($popover.position().top, 50) + 'px')
|
||||
}
|
||||
|
||||
function openNotes(clicked) {
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<?php
|
||||
echo $this->element('genericElements/Analyst_data/thread', [
|
||||
'seed' => $seed,
|
||||
'notes' => $notes,
|
||||
'opinions' => $opinions,
|
||||
'relationships' => $relationships,
|
||||
'object_type' => $object_type,
|
||||
'object_uuid' => $object_uuid,
|
||||
'shortDist' => $shortDist,
|
||||
]);
|
||||
?>
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
echo $this->element('genericElements/assetLoader', [
|
||||
'css' => ['analyst-data',],
|
||||
]);
|
||||
|
||||
$seed = mt_rand();
|
||||
$forceInline = empty($forceInline) ? false : !empty($forceInline);
|
||||
$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($opinion)));
|
||||
$opinionText = ($opinion >= 81) ? __("Strongly Agree") : (($opinion >= 61) ? __("Agree") : (($opinion >= 41) ? __("Neutral") : (($opinion >= 21) ? __("Disagree") : __("Strongly Disagree"))));
|
||||
$opinionColor = $opinion == 50 ? '#333' : ( $opinion > 50 ? '#468847' : '#b94a48');
|
||||
?>
|
||||
|
||||
<div class="main-container-<?= $seed ?>" style="margin: 0.75rem 0 0.25rem 0;" title="<?= __('Opinion:') ?> 50 /100">
|
||||
<div class="opinion-gradient-container" style="width: 10rem; height: 6px; position: relative;">
|
||||
<span class="opinion-gradient-dot"></span>
|
||||
<div class="opinion-gradient opinion-gradient-negative"></div>
|
||||
<div class="opinion-gradient opinion-gradient-positive"></div>
|
||||
</div>
|
||||
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;">
|
||||
<b class="opinion-text" style="margin-left: 0.5rem; color: <?= $opinionColor ?>"><?= $opinionText ?></b>
|
||||
<b class="opinion-value" style="margin-left: 0.25rem; color: <?= $opinionColor ?>"><?= $opinion ?></b>
|
||||
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.main-container-<?= $seed ?> {
|
||||
<?php if ($forceInline): ?>
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
<?php endif; ?>
|
||||
}
|
||||
|
||||
.main-container-<?= $seed ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
|
||||
opacity: 0;
|
||||
}
|
||||
.main-container-<?= $seed ?> .opinion-gradient-dot {
|
||||
left: calc(<?= $opinion ?>% - 6px);
|
||||
background-color: <?= $opinion == 50 ? '#555' : $opinion_color_scale_100[$opinion] ?>;
|
||||
}
|
||||
<?php if ($opinion >= 50): ?>
|
||||
.main-container-<?= $seed ?> .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: ?>
|
||||
.main-container-<?= $seed ?> .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; ?>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,608 @@
|
|||
<?php
|
||||
$URL_ADD = '/analystData/add/';
|
||||
$URL_EDIT = '/analystData/edit/';
|
||||
$URL_DELETE = '/analystData/delete/';
|
||||
|
||||
$seed = isset($seed) ? $seed : mt_rand();
|
||||
|
||||
$notes = !empty($notes) ? $notes : [];
|
||||
$opinions = !empty($opinions) ? $opinions : [];
|
||||
$relationships = !empty($relationships) ? $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);
|
||||
?>
|
||||
|
||||
<script>
|
||||
|
||||
if (!window.shortDist) {
|
||||
var shortDist = <?= json_encode($shortDist) ?>;
|
||||
}
|
||||
var renderedNotes<?= $seed ?> = null
|
||||
|
||||
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, relationship_related_object)
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
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.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 note') ?>"><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="<?= $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.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> \
|
||||
')
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
(function() {
|
||||
var notes = <?= json_encode($notesOpinions) ?>;
|
||||
var relationships = <?= json_encode($relationships) ?>;
|
||||
var relationship_related_object = <?= json_encode($related_objects) ?>;
|
||||
var container_id = false
|
||||
<?php if (isset($container_id)): ?>
|
||||
container_id = '<?= h($container_id) ?>'
|
||||
<?php endif; ?>
|
||||
|
||||
var nodeContainerTemplate = doT.template('\
|
||||
<div> \
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 10px;"> \
|
||||
<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; 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-<?= $seed ?>" class="tab-pane"> \
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships}}</div>\
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
')
|
||||
|
||||
function renderAllNotesWithForm(relationship_related_object) {
|
||||
var buttonContainer = '<div id="add-button-container" 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,
|
||||
})
|
||||
if (container_id) {
|
||||
$('#' + container_id).html(renderedNotes<?= $seed ?>)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})()
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
</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>
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
$seed = mt_rand();
|
||||
$params['type'] = 'number';
|
||||
$params['min'] = 0;
|
||||
$params['max'] = 100;
|
||||
$params['class'] .= ' opinion-' . $seed;
|
||||
echo $this->Form->input($fieldData['field'], $params);
|
||||
|
||||
echo $this->element('genericElements/assetLoader', [
|
||||
'css' => ['analyst-data',],
|
||||
]);
|
||||
?>
|
||||
|
||||
<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" step="10" 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>
|
||||
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>
|
|
@ -7,6 +7,9 @@ if ($quickedit) {
|
|||
}
|
||||
|
||||
$distributionLevel = Hash::extract($row, $field['data_path'])[0];
|
||||
if ($distributionLevel == 4) {
|
||||
$sg = empty($field['sg_path']) ? $row['SharingGroup'] : Hash::extract($row, $field['sg_path']);
|
||||
}
|
||||
|
||||
echo sprintf('<div%s>', $quickedit ? sprintf(
|
||||
" onmouseenter=\"quickEditHover(this, '%s', %s, 'distribution');\"",
|
||||
|
@ -25,8 +28,8 @@ echo sprintf(
|
|||
sprintf(
|
||||
'<a href="%s/sharing_groups/view/%s">%s</a>',
|
||||
$baseurl,
|
||||
h($row['SharingGroup']['id']),
|
||||
h($row['SharingGroup']['name'])
|
||||
h($sg['id']),
|
||||
h($sg['name'])
|
||||
)
|
||||
);
|
||||
if ($quickedit) {
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
<?php
|
||||
$data = Hash::extract($row, $field['data_path']);
|
||||
echo h($data['model']) . ' #' . intval($data['model_id']);
|
||||
if (!empty($field['data_path'])) {
|
||||
$data = Hash::extract($row, $field['data_path']);
|
||||
if (isset($data['model_id'])) {
|
||||
echo h($data['model']) . ' #' . intval($data['model_id']);
|
||||
}
|
||||
} else {
|
||||
$model_name = Hash::extract($row, $field['model_name'])[0];
|
||||
$model_path = Inflector::Pluralize($model_name);
|
||||
$model_id = Hash::extract($row, $field['model_id'])[0];
|
||||
echo sprintf(
|
||||
'<a href="%s/%s/view/%s">%s (%s)</a>',
|
||||
$baseurl,
|
||||
h($model_path),
|
||||
h($model_id),
|
||||
h($model_name),
|
||||
h($model_id)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
$opinion = Hash::get($row, $field['data_path']);
|
||||
|
||||
echo $this->element('genericElements/Analyst_data/opinion_scale', [
|
||||
'opinion' => $opinion,
|
||||
]);
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
$uuid = Hash::get($row, $field['data_path']);
|
||||
if (empty($uuid) || empty($field['object_type'])) {
|
||||
throw new MethodNotAllowedException(__('No UUID or object_type provided'), 500);
|
||||
|
||||
}
|
||||
$notes = Hash::extract($row, $field['notes_data_path'] ?? 'Note');
|
||||
$opinions = Hash::extract($row, $field['opinions_data_path'] ?? 'Opnion');
|
||||
$relationships = Hash::extract($row, $field['relationships_data_path'] ?? 'Relationship');
|
||||
echo $this->element('genericElements/shortUuidWithNotes', [
|
||||
'uuid' => $uuid,
|
||||
'object_type' => $field['object_type'],
|
||||
'notes' => $notes,
|
||||
'opinions' => $opinions,
|
||||
'relationships' => $relationships,
|
||||
]);
|
|
@ -265,6 +265,22 @@ $divider = '<li class="divider"></li>';
|
|||
'text' => __('Download as…')
|
||||
));
|
||||
echo $divider;
|
||||
if ($me['Role']['perm_modify']) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'onClick' => array(
|
||||
'function' => 'openGenericModal',
|
||||
'params' => [
|
||||
sprintf(
|
||||
'%s/collectionElements/addElementToCollection/Event/%s',
|
||||
$baseurl,
|
||||
h($event['Event']['uuid'])
|
||||
)
|
||||
]
|
||||
),
|
||||
'text' => __('Add Event to Collection')
|
||||
));
|
||||
echo $divider;
|
||||
}
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'url' => $baseurl . '/events/index',
|
||||
'text' => __('List Events')
|
||||
|
@ -314,7 +330,50 @@ $divider = '<li class="divider"></li>';
|
|||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'collections':
|
||||
if ($menuItem === 'edit' || $menuItem === 'view') {
|
||||
if ($this->Acl->canAccess('collections', 'add') && $mayModify) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', [
|
||||
'element_id' => 'edit',
|
||||
'url' => $baseurl . '/collections/edit/' . h($id),
|
||||
'text' => __('Edit Collection')
|
||||
]);
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', [
|
||||
'element_id' => 'delete',
|
||||
'onClick' => [
|
||||
'function' => 'openGenericModal',
|
||||
'params' => array($baseurl . '/Collections/delete/' . h($id))
|
||||
],
|
||||
'text' => __('Delete Collection')
|
||||
]);
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', [
|
||||
'text' => __('Add Element to Collection'),
|
||||
'onClick' => [
|
||||
'function' => 'openGenericModal',
|
||||
'params' => array($baseurl . '/CollectionElements/add/' . h($id))
|
||||
],
|
||||
]);
|
||||
echo $divider;
|
||||
}
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'view',
|
||||
'url' => $baseurl . '/collections/view/' . h($id),
|
||||
'text' => __('View Collection')
|
||||
));
|
||||
}
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index',
|
||||
'url' => $baseurl . '/collections/index',
|
||||
'text' => __('List Collections')
|
||||
));
|
||||
if ($this->Acl->canAccess('collection', 'add')) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'add',
|
||||
'url' => $baseurl . '/collections/add',
|
||||
'text' => __('Add Collection')
|
||||
));
|
||||
}
|
||||
break;
|
||||
case 'event-collection':
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index',
|
||||
|
@ -1493,6 +1552,22 @@ $divider = '<li class="divider"></li>';
|
|||
'text' => __('View Correlation Graph')
|
||||
));
|
||||
}
|
||||
if ($me['Role']['perm_modify']) {
|
||||
echo $divider;
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'onClick' => array(
|
||||
'function' => 'openGenericModal',
|
||||
'params' => [
|
||||
sprintf(
|
||||
'%s/collectionElements/addElementToCollection/GalaxyCluster/%s',
|
||||
$baseurl,
|
||||
h($cluster['GalaxyCluster']['uuid'])
|
||||
)
|
||||
]
|
||||
),
|
||||
'text' => __('Add Cluster to Collection')
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($menuItem === 'view' || $menuItem === 'export') {
|
||||
echo $divider;
|
||||
|
@ -1726,6 +1801,54 @@ $divider = '<li class="divider"></li>';
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'analyst_data':
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index',
|
||||
'url' => '/analystData/index',
|
||||
'text' => __('List Analyst Data')
|
||||
));
|
||||
if ($this->Acl->canAccess('analyst_data_blocklists', 'index')) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index_blocklist',
|
||||
'url' => $baseurl . '/analyst_data_blocklists/index',
|
||||
'text' => __('List Analyst-Data Blocklists')
|
||||
));
|
||||
}
|
||||
if ($this->Acl->canAccess('analyst_data', 'add')) {
|
||||
echo $divider;
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'add_note',
|
||||
'url' => sprintf('/analystData/add/Note'),
|
||||
'text' => __('Add Analyst Note')
|
||||
));
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'add_opinion',
|
||||
'url' => sprintf('/analystData/add/Opinion'),
|
||||
'text' => __('Add Analyst Opinion')
|
||||
));
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'add_relationship',
|
||||
'url' => sprintf('/analystData/add/Relationship'),
|
||||
'text' => __('Add Analyst Relationship')
|
||||
));
|
||||
}
|
||||
if ($menuItem === 'view' || $menuItem === 'edit') {
|
||||
echo $divider;
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'view',
|
||||
'url' => sprintf('/analystData/view/%s/%s', h($modelSelection), h($id)),
|
||||
'text' => __('View Analyst Data')
|
||||
));
|
||||
if ($isSiteAdmin) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'edit',
|
||||
'url' => sprintf('/analystData/edit/%s/%s', h($modelSelection), h($id)),
|
||||
'text' => __('Edit Analyst Data')
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
echo !empty($side_panel['html']) ? $side_panel['html'] : '';
|
||||
?>
|
|
@ -24,25 +24,27 @@ if ($distribution == 4) {
|
|||
}
|
||||
|
||||
$eventDistributionGraph = '';
|
||||
if (!($distribution == 4 && empty($sg))) {
|
||||
$eventDistributionGraph = sprintf(
|
||||
'%s %s %s',
|
||||
sprintf(
|
||||
'<span id="distribution_graph_bar" style="margin-left: 5px;" data-object-id="%s" data-object-context="event"></span>',
|
||||
h($event_id_path)
|
||||
),
|
||||
sprintf(
|
||||
'<it class="%s" data-object-id="%s" data-object-context="event" data-shown="false"></it><div style="display: none">%s</div>',
|
||||
'useCursorPointer fa fa-info-circle distribution_graph',
|
||||
h($event_id_path),
|
||||
$this->element('view_event_distribution_graph')
|
||||
),
|
||||
sprintf(
|
||||
'<it type="button" id="showAdvancedSharingButton" title="%s" class="%s" aria-hidden="true" style="margin-left: 5px;"></it>',
|
||||
__('Toggle advanced sharing network viewer'),
|
||||
'fa fa-share-alt useCursorPointer'
|
||||
)
|
||||
);
|
||||
if (empty($field['disable_distribution_graph'])) {
|
||||
if (!($distribution == 4 && empty($sg))) {
|
||||
$eventDistributionGraph = sprintf(
|
||||
'%s %s %s',
|
||||
sprintf(
|
||||
'<span id="distribution_graph_bar" style="margin-left: 5px;" data-object-id="%s" data-object-context="event"></span>',
|
||||
h($event_id_path)
|
||||
),
|
||||
sprintf(
|
||||
'<it class="%s" data-object-id="%s" data-object-context="event" data-shown="false"></it><div style="display: none">%s</div>',
|
||||
'useCursorPointer fa fa-info-circle distribution_graph',
|
||||
h($event_id_path),
|
||||
$this->element('view_event_distribution_graph')
|
||||
),
|
||||
sprintf(
|
||||
'<it type="button" id="showAdvancedSharingButton" title="%s" class="%s" aria-hidden="true" style="margin-left: 5px;"></it>',
|
||||
__('Toggle advanced sharing network viewer'),
|
||||
'fa fa-share-alt useCursorPointer'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
echo sprintf(
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
$opinion = Hash::get($data, $field['path']);
|
||||
|
||||
echo $this->element('genericElements/Analyst_data/opinion_scale', [
|
||||
'opinion' => $opinion,
|
||||
'forceInline' => true,
|
||||
]);
|
|
@ -4,3 +4,17 @@
|
|||
'<span class="quickSelect">%s</span>',
|
||||
h($uuid)
|
||||
);
|
||||
|
||||
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']
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
$uuidHalfWidth = 3;
|
||||
$shortUUID = sprintf('%s...%s', substr($uuid, 0, $uuidHalfWidth), substr($uuid, 36-$uuidHalfWidth, $uuidHalfWidth));
|
||||
echo sprintf('<span title="%s">%s</span>', $uuid, $shortUUID);
|
||||
|
||||
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
|
||||
]);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -73,6 +73,17 @@
|
|||
'class' => 'short',
|
||||
'data_path' => 'EventReport.id',
|
||||
),
|
||||
array(
|
||||
'name' => __('Context'),
|
||||
'sort' => 'uuid',
|
||||
'class' => 'short',
|
||||
'data_path' => 'EventReport.uuid',
|
||||
'notes_data_path' => 'Note',
|
||||
'opinions_data_path' => 'Opinion',
|
||||
'relationships_data_path' => 'Relationship',
|
||||
'element' => 'shortUUIDWithNotes',
|
||||
'object_type' => 'EventReport',
|
||||
),
|
||||
array(
|
||||
'name' => __('Name'),
|
||||
'class' => 'useCursorPointer',
|
||||
|
|
|
@ -52,6 +52,17 @@
|
|||
'element' => 'links',
|
||||
'url' => $baseurl . '/eventReports/view/%s'
|
||||
),
|
||||
array(
|
||||
'name' => __('Context'),
|
||||
'sort' => 'uuid',
|
||||
'class' => 'short',
|
||||
'data_path' => 'EventReport.uuid',
|
||||
'notes_data_path' => 'Note',
|
||||
'opinions_data_path' => 'Opinion',
|
||||
'relationships_data_path' => 'Relationship',
|
||||
'element' => 'shortUUIDWithNotes',
|
||||
'object_type' => 'EventReport',
|
||||
),
|
||||
array(
|
||||
'name' => __('Name'),
|
||||
'data_path' => 'EventReport.name',
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
<?php
|
||||
$table_data = array();
|
||||
$table_data[] = array('key' => __('ID'), 'value' => $report['EventReport']['id']);
|
||||
$table_data[] = array('key' => __('UUID'), 'value' => $report['EventReport']['uuid'], 'value_class' => 'quickSelect');
|
||||
$table_data[] = [
|
||||
'key' => __('UUID'),
|
||||
'element' => 'genericElements/SingleViews/Fields/uuidField',
|
||||
'element_params' => [
|
||||
'data' => $report,
|
||||
'field' => [
|
||||
'path' => 'EventReport.uuid',
|
||||
'object_type' => 'EventReport',
|
||||
]
|
||||
],
|
||||
];
|
||||
$table_data[] = array(
|
||||
'key' => __('Event'),
|
||||
'html' => sprintf(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/assetLoader', [
|
||||
'css' => ['query-builder.default', 'attack_matrix'],
|
||||
'css' => ['query-builder.default', 'attack_matrix', 'analyst-data'],
|
||||
'js' => ['doT', 'extendext', 'moment.min', 'query-builder', 'network-distribution-graph', 'd3', 'd3.custom', 'jquery-ui.min'],
|
||||
]);
|
||||
echo $this->element(
|
||||
|
@ -16,8 +16,12 @@
|
|||
[
|
||||
'key' => 'UUID',
|
||||
'path' => 'Event.uuid',
|
||||
'class' => 'quickSelect',
|
||||
'class' => '',
|
||||
'type' => 'uuid',
|
||||
'object_type' => 'Event',
|
||||
'notes_path' => 'Note',
|
||||
'opinions_path' => 'Opinion',
|
||||
'relationship_path' => 'Relationship',
|
||||
'action_buttons' => [
|
||||
[
|
||||
'url' => $baseurl . '/events/add/extends:' . h($event['Event']['uuid']),
|
||||
|
|
|
@ -50,7 +50,20 @@ if (!$cluster['GalaxyCluster']['default']) {
|
|||
}
|
||||
$table_data[] = array('key' => __('Default'), 'boolean' => $cluster['GalaxyCluster']['default'], 'class' => 'black');
|
||||
$table_data[] = array('key' => __('Version'), 'value' => $cluster['GalaxyCluster']['version']);
|
||||
$table_data[] = array('key' => __('UUID'), 'value' => $cluster['GalaxyCluster']['uuid'], 'value_class' => 'quickSelect');
|
||||
$table_data[] = [
|
||||
'key' => __('UUID'),
|
||||
'element' => 'genericElements/SingleViews/Fields/uuidField',
|
||||
'element_params' => [
|
||||
'data' => $cluster,
|
||||
'field' => [
|
||||
'path' => 'GalaxyCluster.uuid',
|
||||
'object_type' => 'GalaxyCluster',
|
||||
'notes_path' => 'GalaxyCluster.Note',
|
||||
'opinions_path' => 'GalaxyCluster.Opinion',
|
||||
'relationships_path' => 'GalaxyCluster.Relationship',
|
||||
]
|
||||
],
|
||||
];
|
||||
$table_data[] = array('key' => __('Collection UUID'), 'value' => $cluster['GalaxyCluster']['collection_uuid'], 'value_class' => 'quickSelect');
|
||||
$table_data[] = array(
|
||||
'key' => __('Source'),
|
||||
|
@ -94,6 +107,9 @@ if (!empty($extendedByHtml)) {
|
|||
</h2>
|
||||
<?php echo $this->element('genericElements/viewMetaTable', array('table_data' => $table_data)); ?>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div id="analyst_data_thread" class="panel-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fuild">
|
||||
<div id="matrix_container"></div>
|
||||
|
@ -143,4 +159,25 @@ md.disable(['image'])
|
|||
var $md = $('.md');
|
||||
$md.html(md.render($md.text()));
|
||||
</script>
|
||||
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster'));
|
||||
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster')); ?>
|
||||
|
||||
<?php
|
||||
|
||||
$object_uuid = $cluster['GalaxyCluster']['uuid'];
|
||||
$options = [
|
||||
'container_id' => 'analyst_data_thread',
|
||||
'object_type' => 'GalaxyCluster',
|
||||
'object_uuid' => $object_uuid,
|
||||
'shortDist' => $shortDist,
|
||||
'notes' => $cluster['GalaxyCluster']['Note'] ?? [],
|
||||
'opinions' => $cluster['GalaxyCluster']['Opinion'] ?? [],
|
||||
'relationships' => $cluster['GalaxyCluster']['Relationship'] ?? [],
|
||||
];
|
||||
|
||||
echo $this->element('genericElements/assetLoader', [
|
||||
'js' => ['doT', 'moment.min'],
|
||||
'css' => ['analyst-data',],
|
||||
]);
|
||||
echo $this->element('genericElements/Analyst_data/thread', $options);
|
||||
|
||||
?>
|
|
@ -115,4 +115,13 @@ class AclHelper extends Helper
|
|||
{
|
||||
return $this->ACL->canModifyGalaxyCluster($this->me, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cluster
|
||||
* @return bool
|
||||
*/
|
||||
public function canEditAnalystData(array $analystData, $modelType): bool
|
||||
{
|
||||
return $this->ACL->canEditAnalystData($this->me, $analystData, $modelType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
App::uses('AppHelper', 'View/Helper');
|
||||
App::uses('FileAccessTool', 'Lib/Tools');
|
||||
|
||||
class ImageHelper extends AppHelper
|
||||
{
|
||||
/** @var array */
|
||||
private $imageCache = [];
|
||||
|
||||
/**
|
||||
* @param string $imagePath Path to file
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function base64($imagePath)
|
||||
{
|
||||
if (isset($this->imageCache[$imagePath])) {
|
||||
return $this->imageCache[$imagePath];
|
||||
}
|
||||
|
||||
$ext = pathinfo($imagePath, PATHINFO_EXTENSION);
|
||||
if ($ext === 'svg') {
|
||||
$mime = 'image/svg+xml';
|
||||
} else if ($ext === 'png') {
|
||||
$mime = 'image/png';
|
||||
} else {
|
||||
throw new InvalidArgumentException("Only SVG and PNG images are supported");
|
||||
}
|
||||
|
||||
$fileContent = base64_encode(FileAccessTool::readFromFile($imagePath));
|
||||
$base64 = "data:$mime;base64,$fileContent";
|
||||
|
||||
return $this->imageCache[$imagePath] = $base64;
|
||||
}
|
||||
}
|
|
@ -22,8 +22,8 @@ class OrgImgHelper extends AppHelper
|
|||
$link = $baseurl . '/organisations/view/' . (empty($organisation['Organisation']['id']) ? h($organisation['Organisation']['name']) : h($organisation['Organisation']['id']));
|
||||
}
|
||||
if ($orgImgName) {
|
||||
$orgImgUrl = $baseurl . '/img/orgs/' . $orgImgName;
|
||||
return sprintf('<a href="%s" style="background-image: url(\'%s\')" class="orgImg">%s</a>', $link, $orgImgUrl, h($organisation['Organisation']['name']));
|
||||
$base64 = $this->_View->Image->base64(self::IMG_PATH . $orgImgName);
|
||||
return sprintf('<a href="%s" style="background-image: url(\'%s\')" class="orgImg">%s</a>', $link, $base64, h($organisation['Organisation']['name']));
|
||||
} else {
|
||||
return sprintf('<a href="%s">%s</a>', $link, h($organisation['Organisation']['name']));
|
||||
}
|
||||
|
@ -56,9 +56,8 @@ class OrgImgHelper extends AppHelper
|
|||
if ($orgImgName) {
|
||||
$size = !empty($options['size']) ? $options['size'] : 48;
|
||||
$result = sprintf(
|
||||
'<img src="data:image/%s;base64,%s" title="%s" width="%s" height="%s">',
|
||||
'png',
|
||||
base64_encode(FileAccessTool::readFromFile(self::IMG_PATH . $orgImgName)),
|
||||
'<img src="%s" title="%s" width="%s" height="%s">',
|
||||
$this->_View->Image->base64(self::IMG_PATH . $orgImgName),
|
||||
isset($options['name']) ? h($options['name']) : h($options['id']),
|
||||
(int)$size,
|
||||
(int)$size
|
||||
|
|
|
@ -93,6 +93,8 @@
|
|||
echo $this->Form->input('caching_enabled', array());
|
||||
echo $this->Form->input('push_galaxy_clusters', array());
|
||||
echo $this->Form->input('pull_galaxy_clusters', array());
|
||||
echo $this->Form->input('push_analyst_data', array());
|
||||
echo $this->Form->input('pull_analyst_data', array());
|
||||
echo '<div class="input clear" style="width:100%;"><hr><h4>' . __('Misc settings') . '</h4></div>';
|
||||
echo $this->Form->input('unpublish_event', array(
|
||||
'type' => 'checkbox',
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
<th><?php echo $this->Paginator->sort('push_sightings', 'Push Sightings');?></th>
|
||||
<th><?php echo $this->Paginator->sort('push_galaxy_clusters', 'Push Clusters');?></th>
|
||||
<th><?php echo $this->Paginator->sort('pull_galaxy_clusters', 'Pull Clusters');?></th>
|
||||
<th><?php echo $this->Paginator->sort('push_analyst_data', 'Push Analyst Data');?></th>
|
||||
<th><?php echo $this->Paginator->sort('pull_analyst_data', 'Pull Analyst Data');?></th>
|
||||
<th><?php echo $this->Paginator->sort('caching_enabled', 'Cache');?></th>
|
||||
<th><?php echo $this->Paginator->sort('unpublish_event');?></th>
|
||||
<th><?php echo $this->Paginator->sort('publish_without_email');?></th>
|
||||
|
@ -120,6 +122,8 @@ foreach ($servers as $server):
|
|||
<td class="short"><span class="<?= $server['Server']['push_sightings'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_sightings'] ? __('Yes') : __('No'); ?>"></span></td>
|
||||
<td class="short"><span class="<?= $server['Server']['push_galaxy_clusters'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_galaxy_clusters'] ? __('Yes') : __('No'); ?>"></span></td>
|
||||
<td class="short"><span class="<?= $server['Server']['pull_galaxy_clusters'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['pull_galaxy_clusters'] ? __('Yes') : __('No'); ?>"></span></td>
|
||||
<td class="short"><span class="<?= $server['Server']['push_analyst_data'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_analyst_data'] ? __('Yes') : __('No'); ?>"></span></td>
|
||||
<td class="short"><span class="<?= $server['Server']['pull_analyst_data'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['pull_analyst_data'] ? __('Yes') : __('No'); ?>"></span></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($server['Server']['caching_enabled']) {
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
</span><br /><br />
|
||||
<div>
|
||||
<?php if (Configure::read('MISP.main_logo') && file_exists(APP . '/webroot/img/custom/' . Configure::read('MISP.main_logo'))): ?>
|
||||
<img src="<?php echo $baseurl?>/img/custom/<?php echo h(Configure::read('MISP.main_logo'));?>" style=" display:block; margin-left: auto; margin-right: auto;" />
|
||||
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.home_logo')) ?>" style=" display:block; margin-left: auto; margin-right: auto;">
|
||||
<?php else: ?>
|
||||
<img src="<?php echo $baseurl?>/img/misp-logo-s-u.png" style="display:block; margin-left: auto; margin-right: auto;"/>
|
||||
<img src="<?php echo $baseurl?>/img/misp-logo-s-u.png" style="display:block; margin-left: auto; margin-right: auto;">
|
||||
<?php endif;?>
|
||||
</div>
|
||||
<?php
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
.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));
|
||||
}
|
||||
|
||||
.opinion-gradient-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 2px 0px #00000066;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
}
|
|
@ -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) {
|
||||
|
@ -4285,27 +4287,9 @@ function feedFormUpdate() {
|
|||
checkSharingGroup('Feed');
|
||||
}
|
||||
|
||||
function setContextFields() {
|
||||
if (typeof showContext === "undefined") {
|
||||
showContext = false;
|
||||
}
|
||||
|
||||
var $button = $('#show_attribute_context');
|
||||
if (showContext) {
|
||||
$('.context').show();
|
||||
$button.removeClass("btn-inverse").addClass("btn-primary");
|
||||
} else {
|
||||
$('.context').hide();
|
||||
$button.removeClass("btn-primary").addClass("btn-inverse");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleContextFields() {
|
||||
if (typeof showContext === "undefined") {
|
||||
showContext = false;
|
||||
}
|
||||
showContext = !showContext;
|
||||
setContextFields();
|
||||
$('.context').toggle()
|
||||
$('#show_attribute_context').toggleClass("btn-inverse").toggleClass("btn-primary")
|
||||
}
|
||||
|
||||
function checkOrphanedAttributes() {
|
||||
|
@ -5586,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.
|
||||
|
@ -5606,7 +5594,7 @@ function submitGenericFormInPlace(callback) {
|
|||
$('#genericModal').modal();
|
||||
},
|
||||
error: xhrFailCallback,
|
||||
});
|
||||
}, ajaxOptions));
|
||||
}
|
||||
|
||||
function openIdSelection(clicked, scope, action) {
|
||||
|
|
6457
db_schema.json
6457
db_schema.json
File diff suppressed because it is too large
Load Diff
|
@ -87,7 +87,7 @@ assert event_preview["Event"]["uuid"] == event.uuid
|
|||
url = f'servers/pull/{remote_server["id"]}/disable_background_processing:1'
|
||||
pull_response = pymisp._check_response(pymisp._prepare_request('GET', url))
|
||||
check_response(pull_response)
|
||||
assert "Pull completed. 0 events pulled, 0 events could not be pulled, 0 proposals pulled, 0 sightings pulled, 0 clusters pulled." == pull_response["message"], pull_response["message"]
|
||||
assert "Pull completed. 0 events pulled, 0 events could not be pulled, 0 proposals pulled, 0 sightings pulled, 0 clusters pulled, 0 analyst data pulled." == pull_response["message"], pull_response["message"]
|
||||
|
||||
# Test pull background
|
||||
check_response(pymisp.server_pull(remote_server))
|
||||
|
|
Loading…
Reference in New Issue