chg: [tagging] WIP - bulk tagging via generic picker on tag level

pull/4024/head
mokaddem 2019-01-09 14:19:14 +01:00
parent b7c1ce79ba
commit bbdbd3b184
9 changed files with 146 additions and 137 deletions

View File

@ -2850,11 +2850,33 @@ class AttributesController extends AppController
$tag_id_list[] = $tagCollectionTag['tag_id'];
}
} else {
$tag = $this->Event->EventTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
if (empty($tag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
// try to parse json array
$tag_ids = json_decode($tag_id);
if ($tag_ids !== null) { // can decode json
$tag_id_list = array();
foreach ($tag_ids as $tag_id) {
if (preg_match('/^collection_[0-9]+$/i', $tag_id)) {
$tagChoice = explode('_', $tag_id)[1];
$this->loadModel('TagCollection');
$tagCollection = $this->TagCollection->fetchTagCollection($this->Auth->user(), array('conditions' => array('TagCollection.id' => $tagChoice)));
if (empty($tagCollection)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag Collection.')), 'status'=>200, 'type' => 'json'));
}
$tag_id_list = array();
foreach ($tagCollection[0]['TagCollectionTag'] as $tagCollectionTag) {
$tag_id_list[] = $tagCollectionTag['tag_id'];
}
} else {
$tag_id_list[] = $tag_id;
}
}
} else {
$tag = $this->Event->EventTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
if (empty($tag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
}
$tag_id = $tag['Tag']['id'];
}
$tag_id = $tag['Tag']['id'];
}
}
if (!isset($idList)) {

View File

@ -3293,16 +3293,17 @@ class EventsController extends AppController
}
public function genericPicker() {
$this->set('options', array(
'require_choice' => true,
'pre_choices' => array(
'Tag Collections' => "'4/collections/attribute', 'tags', 'selectTag'",
'All Tags' => "4/all/attribute, 'tags', 'selectTag'",
)
));
$this->set('options', array());
$this->set('items', array('item1', 'item2', 'item3'));
$this->render('/Elements/generic_picker');
}
public function genericPrePicker() {
$this->set('choices', array(
'Tag Collections' => "/tags/selectTag/4/collections/attribute",
'All Tags' => "/tags/selectTag/4/all/attribute"
));
$this->render('/Elements/generic_pre_picker');
}
public function addTag($id = false, $tag_id = false)
{

View File

@ -638,13 +638,6 @@ class TagsController extends AppController
$expanded[$tagCollection['TagCollection']['id']] .= sprintf(' (%s)', $tagList);
}
}
$this->set('scope', $scope);
$this->set('object_id', $id);
$this->set('options', $options);
$this->set('expanded', $expanded);
$this->set('custom', $taxonomy_id == 0 ? true : false);
$this->set('filterData', $filterData);
$this->render('ajax/select_tag');
} else {
if ($taxonomy_id === '0') {
$options = $this->Taxonomy->getAllTaxonomyTags(true);
@ -693,19 +686,40 @@ class TagsController extends AppController
unset($options[$hidden_tag]);
unset($expanded[$hidden_tag]);
}
$this->set('scope', $scope);
$this->set('object_id', $id);
foreach ($options as $k => $v) {
if (substr($v, 0, strlen('misp-galaxy:')) === 'misp-galaxy:') {
unset($options[$k]);
}
}
$this->set('options', $options);
$this->set('expanded', $expanded);
$this->set('custom', $taxonomy_id == 0 ? true : false);
$this->set('filterData', $filterData);
$this->render('ajax/select_tag');
}
$this->set('scope', $scope);
$this->set('object_id', $id);
$items = array();
foreach ($options as $k => $option) {
$choice_id = $k;
if ($taxonomy_id === 'collections') {
$choice_id = 'collection_' . $choice_id;
}
$onClickForm = 'quickSubmitTagForm';
if ($scope === 'attribute') {
$onClickForm = 'quickSubmitAttributeTagForm';
}
if ($scope === 'tag_collection') {
$onClickForm = 'quickSubmitTagCollectionTagForm';
}
$items[h($option)] = array(
'value' => h($choice_id),
'additionalData' => array(
'id' => h($id)
)
);
}
$this->set('items', $items);
$this->set('options', array( // set chosen (select picker) options
'select_options' => array(
'multiple' => true,
),
'functionName' => $onClickForm,
));
$this->render('ajax/select_tag');
}
public function tagStatistics($percentage = false, $keysort = false)

View File

@ -1,24 +1,25 @@
<?php
/**
* Generic select picker from JSON
* Required: $options, items
* Note: setting `require_choice` to true, disable items
* Generic select picker
*/
// prevent exception if not set
$options = isset($options) ? $options : array();
$items = isset($items) ? $items : array();
$defaults_options = array(
'select_options' => array(
// 'multiple' => '', // set to add possibility to pick multiple options in the select
//'placeholder' => '' // set to replace the default placeholder text
),
'chosen_options' => array(
'width' => '400px',
//'no_results_text' => '', // set to replace the default no result text after filtering
//'max_selected_options' => 'Infinity' // set to replace the max selected options
'disable_search_threshold' => 10,
'allow_single_deselect' => true,
),
'require_choice' => false,
'pre_choices' => array()
'functionName' => '', // function to be called on submit
);
$scope = isset($scope) ? $scope : '';
// merge options with defaults
$defaults = array_replace_recursive($defaults_options, $options);
@ -34,13 +35,26 @@
function add_option($name, $param) {
$option_html = '<option';
if (is_array($param)) {
if (isset($param['value'])) {
$option_html .= ' value=' . h($param['value']);
} else {
$option_html .= ' value=' . h($name);
}
if (isset($param['additionalData'])) {
$additionalData = json_encode($param['additionalData']);
} else {
$additionalData = json_encode(array());
}
$option_html .= ' data-additionaldata=' . $additionalData;
if (in_array('disabled', $param)) {
$option_html .= ' disabled';
} else if (in_array('selected', $param)) { // nonsense to pre-select if disabled
$option_html .= ' selected';
}
} else {
$option_html .= ' value=' . h($param);
}
$option_html .= ' value=' . (is_array($param)? h($name) : h($param));
$option_html .= '>';
$option_html .= is_array($param)? h($name) : h($param);
@ -51,19 +65,18 @@
?>
<div class="popover_choice generic_picker">
<legend><?php echo __(h($scope));?></legend>
<select <?php echo h(add_select_params($defaults)); ?>>
<?php
foreach ($items as $name => $param) {
echo add_option($name, $param);
}
?>
</select>
<div class="overlay_spacing" style="margin: 5px; margin-top: 10px;">
<button class="btn btn-primary" onclick="popoverPopup(this, '', 'events', 'genericPicker')">submit</button>
<div>
<select <?php echo h(add_select_params($defaults)); ?>>
<?php
foreach ($items as $name => $param) {
echo add_option($name, $param);
}
?>
</select>
<button class="btn btn-primary" onclick="submitFunction(this, <?php echo $defaults['functionName']; ?>)">submit</button>
</div>
<!-- <div class="overlay_spacing" style="margin: 5px; margin-top: 10px;"> -->
<!-- </div> -->
</div>
<?php
@ -75,4 +88,12 @@
var chosen_options = <?php echo json_encode($defaults['chosen_options']); ?>;
$(".generic_picker select").chosen(chosen_options);
})
function submitFunction(clicked, callback) {
var $clicked = $(clicked);
var $select = $clicked.parent().find('select');
var selected = $select.val();
var additionalData = $select.find(":selected").data('additionaldata');
callback(selected, additionalData);
}
</script>

View File

@ -5,7 +5,7 @@
*/
/** Config **/
$select_threshold = 3;
$select_threshold = 6;
/** Global **/
$use_select = count($choices) > $select_threshold;
@ -26,13 +26,14 @@ $use_select = count($choices) > $select_threshold;
dataType:"html",
async: true,
cache: false,
beforeSend: function() {
var loadingHtml = '<div style="height: 40px; width: 40px; left: 50%; position: relative;"><div class="spinner" style="height: 30px; width: 30px;"></div></div>';
var $arrow = $clicked.closest('div.popover').find('div.arrow');
syncPopoverArrow($arrow, $wrapper, loadingHtml)
},
success:function (data, textStatus) {
var $arrow = $clicked.closest('div.popover').find('div.arrow');
var ar_pos = $arrow.position();
$wrapper.html(data);
// redraw popover
$arrow.css('top', ar_pos.top + 'px');
$arrow.css('left', ar_pos.left + 'px');
syncPopoverArrow($arrow, $wrapper, data)
},
error:function() {
$wrapper.html('<div class="alert alert-error" style="margin-bottom: 0px;">Something went wrong - the queried function returned an exception. Contact your administrator for further details (the exception has been logged).</div>');
@ -41,6 +42,15 @@ $use_select = count($choices) > $select_threshold;
});
}
// Used to keep the popover arrow at the correct place regardless of the popover content
function syncPopoverArrow($arrow, $wrapper, content) {
var ar_pos = $arrow.position();
$wrapper.html(content);
// redraw popover
$arrow.css('top', ar_pos.top + 'px');
$arrow.css('left', ar_pos.left + 'px');
}
<?php if ($use_select): ?>
function setupChosen(id) {
var $elem = $('#'+id);

View File

@ -1,84 +1,16 @@
<div class="popover_choice select_tag">
<legend><?php echo __('Select Tag');?></legend>
<div style="display:none;">
<?php
if ($scope === 'attribute') {
echo $this->Form->create('Attribute', array('url' => '/attributes/addTag/' . $object_id, 'style' => 'margin:0px;'));
} elseif ($scope === 'event') {
echo $this->Form->create('Event', array('url' => '/events/addTag/' . $object_id, 'style' => 'margin:0px;'));
} elseif ($scope === 'tag_collection') {
echo $this->Form->create('TagCollection', array('url' => '/tag_collections/addTag/' . $object_id, 'style' => 'margin:0px;'));
}
echo $this->Form->input('attribute_ids', array('style' => 'display:none;', 'label' => false));
echo $this->Form->input('tag', array('value' => 0));
echo $this->Form->end();
?>
</div>
<div style="text-align:right;width:100%;" class="select_tag_search">
<input id="filterField" style="width:100%;border:0px;padding:0px;" value="<?php echo h($filterData); ?>" placeholder="<?php echo __('search tags…');?>"/>
</div>
<div class="popover_choice_main" id ="popover_choice_main">
<table style="width:100%;">
<div style="display:none;">
<?php
foreach ($options as $k => &$option):
$choice_id = $k;
if ($taxonomy_id === 'collections') {
$choice_id = 'collection_' . $choice_id;
}
if ($scope === 'attribute') {
echo $this->Form->create('Attribute', array('url' => '/attributes/addTag/' . $object_id, 'style' => 'margin:0px;'));
} elseif ($scope === 'event') {
echo $this->Form->create('Event', array('url' => '/events/addTag/' . $object_id, 'style' => 'margin:0px;'));
} elseif ($scope === 'tag_collection') {
echo $this->Form->create('TagCollection', array('url' => '/tag_collections/addTag/' . $object_id, 'style' => 'margin:0px;'));
}
echo $this->Form->input('attribute_ids', array('style' => 'display:none;', 'label' => false));
echo $this->Form->input('tag', array('value' => 0));
echo $this->Form->end();
?>
<tr style="border-top:1px solid black;" class="templateChoiceButton shown" id="field_<?php echo h($choice_id); ?>">
<?php
$onClickForm = 'quickSubmitTagForm';
if ($scope === 'attribute') {
$onClickForm = 'quickSubmitAttributeTagForm';
}
if ($scope === 'tag_collection') {
$onClickForm = 'quickSubmitTagCollectionTagForm';
}
echo '<td style="padding-left:10px;padding-right:10px; text-align:center;width:100%;" onClick="' . $onClickForm .
'(\'' . h($object_id) . '\', \'' . h($choice_id) . '\');" title="' . h($expanded[$k]) . '" role="button" tabindex="0" aria-label="' .
__('Attach tag') . ' ' . h($option) . '">' . h($option) . '</td>';
?>
</tr>
<?php endforeach; ?>
</table>
</div>
<div role="button" tabindex="0" aria-label="<?php echo __('Return to taxonomy selection');?>" class="popover-back useCursorPointer" onClick="getPopup('<?php echo h($object_id) . '/' . h($scope); ?>', 'tags', 'selectTaxonomy');" title="<?php echo __('Select Taxonomy');?>"><?php echo __('Back to Taxonomy Selection');?></div>
<div role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" class="templateChoiceButton templateChoiceButtonLast" onClick="cancelPopoverForm();"><?php echo __('Cancel');?></div>
</div>
<script type="text/javascript">
var tags = <?php echo json_encode($options); ?>;
$(document).ready(function() {
resizePopoverBody();
// Places the cursor at the end of the input before focusing
var filterField = $("#filterField");
var tempValue = filterField.val();
filterField.val('');
filterField.val(tempValue);
filter();
filterField.focus();
});
function filter() {
var filterString = $("#filterField").val().toLowerCase();
$.each(tags, function(index, value) {
if (value.toLowerCase().indexOf(filterString) == -1) {
let element = $('#field_' + index);
element.hide();
element.removeClass('shown');
} else {
let element = $('#field_' + index);
element.show();
element.addClass('shown');
}
});
}
$('#filterField').keyup(filter);
$(window).resize(function() {
resizePopoverBody();
});
</script>
<?php echo $this->Html->script('tag-selection-keyboard-navigation.js'); ?>
<?php echo $this->element('generic_picker'); ?>

View File

@ -1,3 +1,4 @@
<!-- to delete if unused -->
<div class="popover_choice select_tag_source">
<legend><?php echo __('Select Tag Source');?></legend>
<div style="text-align:right;width:100%;" class="select_tag_search">

View File

@ -896,6 +896,12 @@ a.proposal_link_red:hover {
a.pill-pre-picker {
background-color: #0000000f;
font-weight: bold;
border: solid 1px #00000033;
}
.generic_picker {
margin: 10px;
}
.gray_out {

View File

@ -586,8 +586,10 @@ function quickSubmitTagForm(event_id, tag_id) {
return false;
}
function quickSubmitAttributeTagForm(attribute_id, tag_id) {
$('#AttributeTag').val(tag_id);
// function quickSubmitAttributeTagForm(attribute_id, tag_id) {
function quickSubmitAttributeTagForm(selected_tag_ids, addData) {
attribute_id = addData.id;
$('#AttributeTag').val(JSON.stringify(selected_tag_ids));
if (attribute_id == 'selected') {
$('#AttributeAttributeIds').val(getSelected());
}