fix: [UI] Attribute search input description

pull/8302/head
Jakub Onderka 2022-04-25 17:41:13 +02:00
parent 13d98012d9
commit 0bb267b181
7 changed files with 129 additions and 154 deletions

View File

@ -489,7 +489,14 @@ class AttributesController extends AppController
$this->set('sharingGroups', $distributionData['sgs']);
$this->set('distributionLevels', $distributionData['levels']);
$this->set('initialDistribution', $distributionData['initial']);
$this->set('fieldDesc', $this->__fieldDesc());
}
/**
* @return array|array[]
*/
private function __fieldDesc()
{
$fieldDesc = ['category' => [], 'type' => [], 'distribution' => []];
foreach ($this->Attribute->categoryDefinitions as $key => $value) {
$fieldDesc['category'][$key] = isset($value['formdesc']) ? $value['formdesc'] : $value['desc'];
@ -500,7 +507,7 @@ class AttributesController extends AppController
foreach ($this->Attribute->distributionLevels as $key => $value) {
$fieldDesc['distribution'][$key] = $this->Attribute->distributionDescriptions[$key]['formdesc'];
}
$this->set('fieldDesc', $fieldDesc);
return $fieldDesc;
}
// Imports the CSV threatConnect file to multiple attributes
@ -1517,11 +1524,7 @@ class AttributesController extends AppController
$this->Session->write('search_attributes_filters', json_encode($filters));
} elseif ($continue === 'results') {
$filters = $this->Session->read('search_attributes_filters');
if (empty($filters)) {
$filters = array();
} else {
$filters = json_decode($filters, true);
}
$filters = empty($filters) ? [] : $this->_jsonDecode($filters);
} else {
$types = $this->_arrayToValuesIndexArray(array_keys($this->Attribute->typeDefinitions));
ksort($types);
@ -1531,13 +1534,13 @@ class AttributesController extends AppController
$this->set('categories', $categories);
$categoryDefinition = $this->Attribute->categoryDefinitions;
$categoryDefinition['ALL'] = ['types' => array_keys($this->Attribute->typeDefinitions), 'formdesc' => ''];
$categoryDefinition = array_merge(["ALL" => ['types' => array_keys($this->Attribute->typeDefinitions), 'formdesc' => '']], $categoryDefinition);
foreach ($categoryDefinition as &$def) {
$def['types'] = array_merge(['ALL'], $def['types']);
}
$this->set('categoryDefinitions', $categoryDefinition);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('fieldDesc', $this->__fieldDesc());
$this->Session->write('search_attributes_filters', null);
}
@ -1553,7 +1556,7 @@ class AttributesController extends AppController
$orgTable = $this->Attribute->Event->Orgc->find('all', [
'fields' => ['Orgc.id', 'Orgc.name', 'Orgc.uuid'],
]);
$orgTable = Hash::combine($orgTable, '{n}.Orgc.id', '{n}.Orgc');
$orgTable = array_column(array_column($orgTable, 'Orgc'), null, 'id');
foreach ($attributes as &$attribute) {
if (isset($orgTable[$attribute['Event']['orgc_id']])) {
$attribute['Event']['Orgc'] = $orgTable[$attribute['Event']['orgc_id']];
@ -1575,7 +1578,7 @@ class AttributesController extends AppController
if (!is_array($filters['tags'])) {
$filters['tags'] = array($filters['tags']);
}
foreach ($filters['tags'] as $k => &$v) {
foreach ($filters['tags'] as &$v) {
if (!is_numeric($v))
continue;
$tag = $this->Tag->find('first', [
@ -2924,13 +2927,17 @@ class AttributesController extends AppController
public function exportSearch($type = false)
{
$filters = $this->Session->read('search_attributes_filters');
if ($filters === null) {
throw new NotFoundException('No search to export.');
}
if (empty($type)) {
$exports = array_keys($this->Attribute->validFormats);
$this->set('exports', $exports);
$this->render('ajax/exportSearch');
} else {
$filters = $this->Session->read('search_attributes_filters');
$filters = json_decode($filters, true);
$filters = $this->_jsonDecode($filters);
$final = $this->Attribute->restSearch($this->Auth->user(), $type, $filters);
$responseType = $this->Attribute->validFormats[$type][0];
return $this->RestResponse->viewData($final, $responseType, false, true, 'search.' . $type . '.' . $responseType);

View File

@ -3213,13 +3213,25 @@ class Attribute extends AppModel
return $conditions;
}
public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0, &$renderView = false)
/**
* @param array $user
* @param string $returnFormat
* @param array $filters
* @param bool $paramsOnly
* @param int $jobId Not used
* @param int $elementCounter
* @param bool $renderView
* @return array|TmpFileTool Array when $paramsOnly is true
* @throws Exception
*/
public function restSearch(array $user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0, &$renderView = false)
{
if (!isset($this->validFormats[$returnFormat][1])) {
throw new NotFoundException('Invalid output format.');
}
App::uses($this->validFormats[$returnFormat][1], 'Export');
$exportTool = new $this->validFormats[$returnFormat][1]();
$className = $this->validFormats[$returnFormat][1];
App::uses($className, 'Export');
$exportTool = new $className();
if (method_exists($exportTool, 'setDefaultFilters')) {
$exportTool->setDefaultFilters($filters);
}
@ -3255,23 +3267,23 @@ class Attribute extends AppModel
$filters = $this->Event->addFiltersFromUserSettings($user, $filters);
$conditions = $this->buildFilterConditions($user, $filters);
$params = array(
'conditions' => $conditions,
'fields' => array('Attribute.*', 'Event.org_id', 'Event.distribution'),
'withAttachments' => !empty($filters['withAttachments']) ? $filters['withAttachments'] : 0,
'enforceWarninglist' => !empty($filters['enforceWarninglist']) ? $filters['enforceWarninglist'] : 0,
'includeAllTags' => !empty($filters['includeAllTags']) ? $filters['includeAllTags'] : 0,
'flatten' => 1,
'includeEventUuid' => !empty($filters['includeEventUuid']) ? $filters['includeEventUuid'] : 0,
'includeEventTags' => !empty($filters['includeEventTags']) ? $filters['includeEventTags'] : 0,
'includeProposals' => !empty($filters['includeProposals']) ? $filters['includeProposals'] : 0,
'includeWarninglistHits' => !empty($filters['includeWarninglistHits']) ? $filters['includeWarninglistHits'] : 0,
'includeContext' => !empty($filters['includeContext']) ? $filters['includeContext'] : 0,
'includeSightings' => !empty($filters['includeSightings']) ? $filters['includeSightings'] : 0,
'includeSightingdb' => !empty($filters['includeSightingdb']) ? $filters['includeSightingdb'] : 0,
'includeCorrelations' => !empty($filters['includeCorrelations']) ? $filters['includeCorrelations'] : 0,
'includeDecayScore' => !empty($filters['includeDecayScore']) ? $filters['includeDecayScore'] : 0,
'includeFullModel' => !empty($filters['includeFullModel']) ? $filters['includeFullModel'] : 0,
'allow_proposal_blocking' => !empty($filters['allow_proposal_blocking']) ? $filters['allow_proposal_blocking'] : 0
'conditions' => $conditions,
'fields' => array('Attribute.*', 'Event.org_id', 'Event.distribution'),
'withAttachments' => !empty($filters['withAttachments']) ? $filters['withAttachments'] : 0,
'enforceWarninglist' => !empty($filters['enforceWarninglist']) ? $filters['enforceWarninglist'] : 0,
'includeAllTags' => !empty($filters['includeAllTags']) ? $filters['includeAllTags'] : 0,
'flatten' => 1,
'includeEventUuid' => !empty($filters['includeEventUuid']) ? $filters['includeEventUuid'] : 0,
'includeEventTags' => !empty($filters['includeEventTags']) ? $filters['includeEventTags'] : 0,
'includeProposals' => !empty($filters['includeProposals']) ? $filters['includeProposals'] : 0,
'includeWarninglistHits' => !empty($filters['includeWarninglistHits']) ? $filters['includeWarninglistHits'] : 0,
'includeContext' => !empty($filters['includeContext']) ? $filters['includeContext'] : 0,
'includeSightings' => !empty($filters['includeSightings']) ? $filters['includeSightings'] : 0,
'includeSightingdb' => !empty($filters['includeSightingdb']) ? $filters['includeSightingdb'] : 0,
'includeCorrelations' => !empty($filters['includeCorrelations']) ? $filters['includeCorrelations'] : 0,
'includeDecayScore' => !empty($filters['includeDecayScore']) ? $filters['includeDecayScore'] : 0,
'includeFullModel' => !empty($filters['includeFullModel']) ? $filters['includeFullModel'] : 0,
'allow_proposal_blocking' => !empty($filters['allow_proposal_blocking']) ? $filters['allow_proposal_blocking'] : 0
);
if (!empty($filters['attackGalaxy'])) {
$params['attackGalaxy'] = $filters['attackGalaxy'];

View File

@ -1,10 +1,9 @@
<div class="popover_choice">
<legend><?php echo __('Choose the format that you wish to download the search results in'); ?></legend>
<div class="popover_choice_main" id ="popover_choice_main">
<div class="popover_choice_main" id="popover_choice_main">
<table style="width:100%;">
<?php
foreach ($exports as $k => $export) {
$tr = 'style="border-bottom:1px solid black;" class="templateChoiceButton"';
foreach ($exports as $export) {
$td = sprintf(
'class="" tabindex="0" title="%s" style="%s" data-type="%s"',
__('Export as %s', h($export)),
@ -19,19 +18,19 @@
$div
);
$td = sprintf(
'<td class="export_choice_button" tabindex="0" title="%s", style="%s">%s</td>',
'<td class="export_choice_button" tabindex="0" title="%s" style="%s">%s</td>',
__('Export as %s', h($export)),
'padding-left:10px; text-align:center;width:100%;',
$a
);
echo sprintf('<tr %s>%s</tr>', $tr, $td);
echo sprintf('<tr style="border-bottom:1px solid black;" class="templateChoiceButton">%s</tr>', $td);
}
?>
</table>
</div>
<div role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" title="<?php echo __('Cancel');?>" class="templateChoiceButton templateChoiceButtonLast" onClick="cancelPopoverForm();"><?php echo __('Cancel');?></div>
<div role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" title="<?php echo __('Cancel');?>" class="templateChoiceButton templateChoiceButtonLast" onclick="cancelPopoverForm();"><?php echo __('Cancel');?></div>
</div>
<script type="text/javascript">
<script>
$(document).ready(function() {
resizePopoverBody();
});

View File

@ -293,6 +293,10 @@ echo $this->element('/genericElements/IndexTable/index_table', [
]
]);
if ($isSearch) {
echo "<button class=\"btn\" onclick=\"getPopup(0, 'attributes', 'exportSearch')\">" . __("Export found attributes as&hellip;") . "</button>";
}
echo '</div>';
// Generate form for adding sighting just once, generation for every attribute is surprisingly too slow
@ -301,12 +305,12 @@ echo $this->Form->input('id', ['label' => false, 'type' => 'number']);
echo $this->Form->input('type', ['label' => false]);
echo $this->Form->end();
$class = $isSearch == 1 ? 'searchAttributes2' : 'listAttributes';
$class = $isSearch ? 'searchAttributes' : 'listAttributes';
echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'event-collection', 'menuItem' => $class]);
?>
<script type="text/javascript">
<script>
// tooltips
$(function() {
$("td, div").tooltip({

View File

@ -2,25 +2,43 @@
<?php echo $this->Form->create('Attribute', array('url' => array('controller' => 'attributes', 'action' => 'search', 'results')));?>
<fieldset>
<legend><?php echo __('Search Attribute'); ?></legend>
<?php echo __('You can search for attributes based on contained expression within the value, event ID, submitting organisation, category and type. <br />For the value, event ID and organisation, you can enter several search terms by entering each term as a new line. To exclude things from a result, use the NOT operator (!) in front of the term.'); ?>
<br />
<?php echo __('For string searches (such as searching for an expression, tags, etc) - lookups are simple string matches. If you want a substring match encapsulate the lookup string between "%" characters.'); ?>
<br /><br />
<?= __('You can search for attributes based on contained expression within the value, event ID, submitting organisation, category and type. <br>For the value, event ID and organisation, you can enter several search terms by entering each term as a new line. To exclude things from a result, use the NOT operator (!) in front of the term.'); ?>
<br>
<?= __('For string searches (such as searching for an expression, tags, etc) - lookups are simple string matches. If you want a substring match encapsulate the lookup string between "%" characters.'); ?>
<br><br>
<?php
echo $this->Form->input('value', array('type' => 'textarea', 'rows' => 2, 'label' => __('Containing the following expressions'), 'div' => 'clear', 'class' => 'input-xxlarge', 'required' => false));
echo $this->Form->input('tags', array('type' => 'textarea', 'rows' => 2, 'label' => __('Having tag or being an attribute of an event having the tag'), 'div' => 'clear', 'class' => 'input-xxlarge', 'required' => false));
echo $this->Form->input('uuid', array('type' => 'textarea', 'rows' => 2, 'maxlength' => false, 'label' => __('Being attributes of the following event IDs, event UUIDs or attribute UUIDs'), 'div' => 'clear', 'class' => 'input-xxlarge', 'required' => false));
echo $this->Form->input('org', array(
'type' => 'textarea',
'label' => __('From the following organisation(s)'),
'div' => 'input clear',
'rows' => 2,
'class' => 'input-xxlarge'));
'type' => 'textarea',
'label' => __('From the following organisation(s)'),
'div' => 'input clear',
'rows' => 2,
'class' => 'input-xxlarge'));
$typeFormInfo = $this->element('genericElements/Form/formInfo', [
'field' => [
'field' => 'type'
],
'modelForForm' => 'Attribute',
'fieldDesc' => $fieldDesc['type'],
]);
echo $this->Form->input('type', array(
'div' => 'input clear',
'required' => false
'required' => false,
"label" => __("Type") . " " . $typeFormInfo,
));
$categoryFormInfo = $this->element('genericElements/Form/formInfo', [
'field' => [
'field' => 'category'
],
'modelForForm' => 'Attribute',
'fieldDesc' => $fieldDesc['category'],
]);
echo $this->Form->input('category', array(
'required' => false,
"label" => __("Category") . " " . $categoryFormInfo,
));
echo $this->Form->input('category', array('required' => false));
?>
<div class="input clear"></div>
<?php
@ -44,114 +62,57 @@
<p><?php echo __('Attributes not having first seen or last seen set might not appear in the search'); ?></p>
</div>
</fieldset>
<?php echo $this->Form->end(); ?>
<div id="bothSeenSliderContainer"></div>
<button onclick="$('#AttributeSearchForm').submit();" class="btn btn-primary">Submit</button>
<div class="clear"></div>
<button class="btn btn-primary" type="submit"><?= __("Search") ?></button>
<?php echo $this->Form->end(); ?>
</div>
<?php echo $this->element('form_seen_input'); ?>
<script type="text/javascript">
<script>
var category_type_mapping = <?= json_encode(array_map(function(array $value) {
return $value['types'];
}, $categoryDefinitions)); ?>;
//
// Generate Type / Category filtering array
//
var type_category_mapping = new Array();
<?php
// all categories for Type ALL
echo "type_category_mapping['ALL'] = {'ALL': 'ALL'";
foreach ($categoryDefinitions as $type => $def) {
echo ", '" . addslashes($type) . "': '" . addslashes($type) . "'";
}
echo "}; \n";
// Categories per Type
foreach ($typeDefinitions as $type => $def) {
echo "type_category_mapping['" . addslashes($type) . "'] = {'ALL': 'ALL'";
foreach ($categoryDefinitions as $category => $def) {
if ( in_array ( $type , $def['types'])) {
echo ", '" . addslashes($category) . "': '" . addslashes($category) . "'";
}
}
echo "}; \n";
}
?>
function formTypeChanged() {
var alreadySelected = $('#AttributeCategory').val();
var $categorySelect = $('#AttributeCategory');
var alreadySelected = $categorySelect.val();
// empty the categories
$('option', $('#AttributeCategory')).remove();
$('option', $categorySelect).remove();
// add new items to options
var options = $('#AttributeCategory').prop('options');
$.each(type_category_mapping[$('#AttributeType').val()], function(val, text) {
options[options.length] = new Option(text, val);
if (val == alreadySelected) {
options[options.length-1].selected = true;
var options = $categorySelect.prop('options');
var selectedType = $('#AttributeType').val();
$.each(category_type_mapping, function (category, types) {
if (types.indexOf(selectedType) !== -1) {
var option = new Option(category, category);
if (category === alreadySelected) {
option.selected = true;
}
options.add(option);
}
});
// enable the form element
$('#AttributeCategory').prop('disabled', false);
$categorySelect.prop('disabled', false);
}
var formInfoValues = new Array();
<?php
foreach ($typeDefinitions as $type => $def) {
$info = isset($def['formdesc']) ? $def['formdesc'] : $def['desc'];
echo "formInfoValues['$type'] = \"$info\";\n";
}
foreach ($categoryDefinitions as $category => $def) {
$info = isset($def['formdesc']) ? $def['formdesc'] : $def['desc'];
echo "formInfoValues['$category'] = \"$info\";\n";
}
$this->Js->get('#AttributeCategory')->event('change', 'formCategoryChanged("Attribute")');
$this->Js->get('#AttributeType')->event('change', 'formTypeChanged()');
?>
formInfoValues[''] = '';
$(function() {
$('#AttributeCategory, #AttributeType').chosen();
$("#AttributeType, #AttributeCategory").on('mouseleave', function(e) {
$('#'+e.currentTarget.id).popover('destroy');
});
$("#AttributeCategory").change(function () {
formCategoryChanged("Attribute");
$("#AttributeType").chosen('destroy').chosen();
}).change();
$("#AttributeType, #AttributeCategory").on('mouseover', function(e) {
var $e = $(e.target);
if ($e.is('option')) {
$('#'+e.currentTarget.id).popover('destroy');
$('#'+e.currentTarget.id).popover({
trigger: 'manual',
placement: 'right',
content: formInfoValues[$e.val()],
}).popover('show');
$("#AttributeType").change(function () {
formTypeChanged();
$("#AttributeCategory").chosen('destroy').chosen();
}).change();
$('.input-xxlarge').keydown(function (e) {
if (e.ctrlKey && e.keyCode == 13) {
$('#AttributeSearchForm').submit();
}
});
// workaround for browsers like IE and Chrome that do now have an onmouseover on the 'options' of a select.
// disadvantage is that user needs to click on the item to see the tooltip.
// no solutions exist, except to generate the select completely using html.
$("#AttributeType, #AttributeCategory").on('change', function(e) {
var $e = $(e.target);
$('#'+e.currentTarget.id).popover('destroy');
$('#'+e.currentTarget.id).popover({
trigger: 'manual',
placement: 'right',
content: formInfoValues[$e.val()],
}).popover('show');
});
});
$('.input-xxlarge').keydown(function (e) {
if (e.ctrlKey && e.keyCode == 13) {
$('#AttributeSearchForm').submit();
}
});
</script>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => 'searchAttributes'));
echo $this->Js->writeBuffer();
?>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => 'searchAttributes'));

View File

@ -1,8 +1,9 @@
<?php
$attribute = Hash::extract($row, 'Attribute');
$event = Hash::extract($row, 'Event');
$attribute = $row['Attribute'];
$event = $row['Event'];
$mayModify = ($isSiteAdmin || ($isAclModify && $event['user_id'] == $me['id'] && $event['org_id'] == $me['org_id']) || ($isAclModifyOrg && $event['orgc_id'] == $me['org_id']));
echo '<div id="attribute_' . intval($attribute['id']) . '_galaxy">';
echo $this->element('galaxyQuickViewNew', array(
'mayModify' => $mayModify,
'isAclTagger' => $isAclTagger,
@ -11,3 +12,4 @@ echo $this->element('galaxyQuickViewNew', array(
'target_id' => $attribute['id'],
'target_type' => 'attribute',
));
echo '</div>';

View File

@ -359,16 +359,6 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => $baseurl . '/attributes/search',
'text' => __('Search Attributes')
));
if ($menuItem == 'searchAttributes2') {
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'onClick' => array(
'function' => 'getPopup',
'params' => array(0, 'attributes', 'exportSearch')
),
'text' => __('Download as…')
));
}
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'viewProposals',