diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index 85d226039..940de2e4d 100755 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -761,6 +761,90 @@ class AttributesController extends AppController { $this->set('typeDefinitions', $this->Attribute->typeDefinitions); $this->set('categoryDefinitions', $this->Attribute->categoryDefinitions); } + + // ajax edit - post a single edited field and this method will attempt to save it and return a json with the validation errors if they occur. + public function editField($id) { + if ((!$this->request->is('post') && !$this->request->is('put')) || !$this->request->is('ajax')) throw new MethodNotAllowedException(); + $this->Attribute->id = $id; + if (!$this->Attribute->exists()) { + throw new NotFoundException(__('Invalid attribute')); + } + $this->Attribute->recursive = -1; + $this->Attribute->contain('Event'); + $attribute = $this->Attribute->read(); + + if (!$this->_isSiteAdmin()) { + // + if ($this->Attribute->data['Event']['orgc'] == $this->Auth->user('org') + && (($this->userRole['perm_modify'] && $this->Attribute->data['Event']['user_id'] != $this->Auth->user('id')) + || $this->userRole['perm_modify_org'])) { + // Allow the edit + } else { + $this->Session->setFlash(__('Invalid attribute.')); + $this->redirect(array('controller' => 'events', 'action' => 'index')); + } + } + + foreach ($this->request->data['Attribute'] as $changedKey => $changedField) { + if ($attribute['Attribute'][$changedKey] == $changedField) { + $this->autoRender = false; + return new CakeResponse(array('body'=> json_encode('nochange'),'status'=>200)); + } + $attribute['Attribute'][$changedKey] = $changedField; + } + $date = new DateTime(); + $attribute['Attribute']['timestamp'] = $date->getTimestamp(); + if ($this->Attribute->save($attribute)) { + $event = $this->Attribute->Event->find('first', array( + 'recursive' => -1, + 'fields' => array('id', 'published', 'timestamp', 'info'), + 'conditions' => array( + 'id' => $attribute['Attribute']['event_id'], + ))); + $event['Event']['timestamp'] = $date->getTimestamp(); + $event['Event']['published'] = 0; + $res = $this->Attribute->Event->save($event, array('fieldList' => array('published', 'timestamp', 'info'))); + file_put_contents('/tmp/event.txt', serialize($res)); + $this->autoRender = false; + return new CakeResponse(array('body'=> json_encode('saved'),'status'=>200)); + } else { + $this->autoRender = false; + return new CakeResponse(array('body'=> json_encode('fail'),'status'=>400)); + } + } + + public function view($id, $hasChildren = 0) { + $this->Attribute->id = $id; + if (!$this->Attribute->exists()) { + throw new NotFoundException('Invalid attribute'); + } + $this->Attribute->recursive = -1; + $this->Attribute->contain('Event'); + $attribute = $this->Attribute->read(); + if (!$this->_isSiteAdmin()) { + // + if ($this->Attribute->data['Event']['org'] == $this->Auth->user('org') || (($this->Attribute->data['Event']['distribution'] > 0) && $this->Attribute->data['Attribute']['distribution'] > 0)) { + throw new MethodNotAllowed('Invalid attribute'); + } + } + $eventRelations = $this->Attribute->Event->getRelatedAttributes($this->Auth->user(), $this->_isSiteAdmin(), $attribute['Attribute']['event_id']); + $attribute['Attribute']['relations'] = array(); + if (isset($eventRelations[$id])) { + foreach ($eventRelations[$id] as $relations) { + $attribute['Attribute']['relations'][] = array($relations['id'], $relations['info'], $relations['org']); + } + } + $object = $attribute['Attribute']; + $object['objectType'] = 0; + $object['hasChildren'] = $hasChildren; + $this->set('object', $object); + $this->set('distributionLevels', $this->Attribute->Event->distributionLevels); + /* + $this->autoRender = false; + $responseObject = array(); + return new CakeResponse(array('body'=> json_encode($attribute['Attribute']),'status'=>200)); + */ + } /** * delete method diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 498403c09..2ac27b62d 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -341,6 +341,13 @@ class EventsController extends AppController { $types = $this->_arrayToValuesIndexArray($types); $this->set('types', $types); $this->set('categoryDefinitions', $this->Event->Attribute->categoryDefinitions); + $typeCategory = array(); + foreach ($this->Attribute->categoryDefinitions as $k => $category) { + foreach ($category['types'] as $type) { + $typeCategory[$type][] = $k; + } + } + $this->set('typeCategory', $typeCategory); $this->request->data['Attribute']['event_id'] = $id; // Show the discussion diff --git a/app/View/Attributes/add.ctp b/app/View/Attributes/add.ctp index 453f2bdee..94571afef 100755 --- a/app/View/Attributes/add.ctp +++ b/app/View/Attributes/add.ctp @@ -1,5 +1,8 @@ +Html->script('ajaxification');?>
-Form->create('Attribute', array('id'));?> +Form->create('Attribute', array('id')); +?>
diff --git a/app/View/Elements/eventattribute.ctp b/app/View/Elements/eventattribute.ctp index 628a2ec19..663341b7c 100644 --- a/app/View/Elements/eventattribute.ctp +++ b/app/View/Elements/eventattribute.ctp @@ -56,93 +56,7 @@ $object): - $extra = ''; - $extra2 = ''; - if ($object['objectType'] == 0 ) { - if ($object['hasChildren'] == 1) $extra = 'highlight1'; - } else $extra = 'highlight2'; - if ($object['objectType'] == 1) $extra2 = '1'; - ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - Html->link('', array('controller' => 'attributes', 'action' => 'edit', $object['id']), array('class' => 'icon-edit', 'title' => 'Edit')); - echo $this->Form->postLink('', array('controller' => 'attributes', 'action' => 'delete', $object['id']), array('class' => 'icon-trash', 'title' => 'Delete'), __('Are you sure you want to delete this attribute? Keep in mind that this will also delete this attribute on remote MISP instances.')); - } else { - echo $this->Html->link('', array('controller' => 'shadow_attributes', 'action' => 'edit', $object['id']), array('class' => 'icon-edit', 'title' => 'Propose Edit')); - } - } else { - if (($event['Event']['orgc'] == $me['org'] && $mayModify) || $isSiteAdmin) { - echo $this->Form->postLink('', array('controller' => 'shadow_attributes', 'action' => 'accept', $object['id']), array('class' => 'icon-ok', 'title' => 'Accept'), 'Are you sure you want to accept this proposal?'); - } - if (($event['Event']['orgc'] == $me['org'] && $mayModify) || $isSiteAdmin || ($object['org'] == $me['org'])) { - echo $this->Form->postLink('', array('controller' => 'shadow_attributes', 'action' => 'discard', $object['id']), array('class' => 'icon-trash', 'title' => 'Discard'), 'Are you sure you want to discard this proposal?'); - } - } - ?> - - - element('eventattributerow', array('object' => $object)); endforeach; ?> @@ -237,33 +151,6 @@ ) ); } -?> - -Js->writeBuffer(); ?> \ No newline at end of file diff --git a/app/View/Elements/eventattributerow.ctp b/app/View/Elements/eventattributerow.ctp new file mode 100644 index 000000000..1939087f8 --- /dev/null +++ b/app/View/Elements/eventattributerow.ctp @@ -0,0 +1,216 @@ + + + + + + + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_category_form', 'action' => 'editField', 'onSubmit=\'activateField("' . $currentType . '", "' . $object['id'] . '", "' . $event['Event']['id'] . '")\'')); + ?> +
+
+
+ Form->input('category', array( + 'options' => array(array_combine($typeCategory[$object['type']], $typeCategory[$object['type']])), + 'label' => false, + 'selected' => $object['category'], + 'error' => array('escape' => false), + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_category_field', + 'div' => false + )); + echo $this->Form->end(); + ?> +
+
+ +
+ + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_type_form', 'action' => 'editField', 'onSubmit=\'activateField("' . $currentType . '", "' . $object['id'] . '", "' . $event['Event']['id'] . '")\'')); + ?> +
+
+
+ Form->input('type', array( + 'options' => array(array_combine($categoryDefinitions[$object['category']]['types'], $categoryDefinitions[$object['category']]['types'])), + 'label' => false, + 'selected' => $object['type'], + 'error' => array('escape' => false), + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_type_field', + 'div' => false + )); + echo $this->Form->end(); + ?> +
+
+ +
+ + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_value_form', 'action' => 'editField', 'default' => false)); + ?> +
+
+
+ Form->input('value', array( + 'type' => 'textarea', + 'label' => false, + 'value' => h($object['value']), + 'error' => array('escape' => false), + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_value_field', + 'div' => false + )); + ?> +
+ Form->end(); + ?> +
+ +
+ + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_comment_form', 'action' => 'editField')); + ?> +
+
+
+ Form->input('comment', array( + 'type' => 'textarea', + 'label' => false, + 'value' => h($object['comment']), + 'error' => array('escape' => false), + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_comment_field', + 'div' => false + )); + echo $this->Form->end(); + ?> +
+
+   +
+ + + + + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_ids_form', 'action' => 'editField')); + ?> +
+
+
+ Form->input('to_ids', array( + 'options' => array(0 => 'No', 1 => 'Yes'), + 'label' => false, + 'selected' => $current, + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_ids_field', + 'div' => false + )); + echo $this->Form->end(); + ?> +
+
+ +
+ + + Form->create($currentType, array('class' => 'inline-form inline-field-form', 'id' => $currentType . '_' . $object['id'] . '_distribution_form', 'action' => 'editField')); + ?> +
+
+
+ Form->input('distribution', array( + 'options' => array($distributionLevels), + 'label' => false, + 'selected' => $object['distribution'], + 'error' => array('escape' => false), + 'class' => 'inline-input', + 'id' => $currentType . '_' . $object['id'] . '_distribution_field', + 'div' => false + )); + echo $this->Form->end(); + ?> +
+
+   +
+ + + Form->create('Attribute', array('class' => 'inline-delete', 'id' => $currentType . '_' . $object['id'] . '_delete', 'action' => 'delete')); + echo $this->Form->end(); + ?> + + + Form->end(); + } else { + echo $this->Html->link('', array('controller' => 'shadow_attributes', 'action' => 'edit', $object['id']), array('class' => 'icon-edit', 'title' => 'Propose Edit')); + } + } else { + if (($event['Event']['orgc'] == $me['org'] && $mayModify) || $isSiteAdmin) { + echo $this->Form->postLink('', array('controller' => 'shadow_attributes', 'action' => 'accept', $object['id']), array('class' => 'icon-ok', 'title' => 'Accept'), 'Are you sure you want to accept this proposal?'); + } + if (($event['Event']['orgc'] == $me['org'] && $mayModify) || $isSiteAdmin || ($object['org'] == $me['org'])) { + echo $this->Form->postLink('', array('controller' => 'shadow_attributes', 'action' => 'discard', $object['id']), array('class' => 'icon-trash', 'title' => 'Discard'), 'Are you sure you want to discard this proposal?'); + } + } + ?> + + \ No newline at end of file diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index 8e2cfa905..a327b5821 100755 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -3,6 +3,7 @@ $mayModify = (($isAclModify && $event['Event']['user_id'] == $me['id'] && $event $mayPublish = ($isAclPublish && $event['Event']['orgc'] == $me['org']); ?> Html->script('ajaxification'); echo $this->element('side_menu', array('menuList' => 'event', 'menuItem' => 'viewEvent', 'mayModify' => $mayModify, 'mayPublish' => $mayPublish)); ?>
diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 3f72b2dcf..c6b13a3d2 100755 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -747,6 +747,61 @@ a.proposal_link_red:hover { width:665px; } +.inline-input { + width:100% !important; + margin-bottom:0px !important; + padding: 0px 0px !important; + font-size: 12px !important; + position:relative; +} + +.inline-input textarea{ + padding-left: 3px !important; +} + +.inline-input-button { + width:19px; + height:17px; + bottom:-17px; + background-color: #fff; + position:absolute; + z-index:100; +} + +.inline-input-active { + border-bottom: 1px solid rgba(82, 168, 236, 0.8); + border-right: 1px solid rgba(82, 168, 236, 0.8); + border-left: 1px solid rgba(82, 168, 236, 0.8); +} + +.inline-input-passive { + border-bottom: 1px solid #cccccc; + border-right: 1px solid #cccccc; + border-left: 1px solid #cccccc; +} + +.inline-input-button span { + display:block; + margin: 0 auto; +} + +.inline-input-accept { + right:32px; +} + +.inline-input-decline { + right:10px; +} + +.inline-form { + display:none; + margin-bottom:0px !important; +} + +.inline-delete { + margin-bottom:0px !important; +} + @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(359deg);} diff --git a/app/webroot/js/ajaxification.js b/app/webroot/js/ajaxification.js new file mode 100644 index 000000000..7e261e80d --- /dev/null +++ b/app/webroot/js/ajaxification.js @@ -0,0 +1,131 @@ +function deleteObject(type, id, event) { + if (confirm("Are you sure you want to delete Attribute #" + id + "?")) { + var name = '#Attribute' + '_' + id + '_delete'; + var formData = $(name).serialize(); + $.ajax({ + data: formData, + success:function (data, textStatus) { + updateAttributeIndexOnSuccess(event); + }, + type:"post", + url:"/" + type + "/delete/" + id, + }); + } +} + +function updateAttributeIndexOnSuccess(event) { + $.ajax({ + beforeSend: function (XMLHttpRequest) { + $(".loading").show(); + }, + dataType:"html", + success:function (data, textStatus) { + $(".loading").hide(); + $("#attributes_div").html(data); + }, + url:"/events/view/" + event + "/attributesPage:1", + }); +} + +// if someone clicks an inactive field, replace it with the hidden form field. Also, focus it and bind a focusout event, so that it gets saved if the user clicks away. +// If a user presses enter, submit the form +function activateField(type, id, field, event) { + resetForms(); + var name = '#' + type + '_' + id + '_' + field; + $(name + '_form').show(); + $(name + '_field').focus(); + inputFieldButtonActive(name + '_field'); + if (field == 'value' || field == 'comment') { + $(name + '_field').on('keyup mouseover', function () { + autoresize(this); + }); + } + $(name + '_form').submit(function(e){ + e.preventDefault(); + submitForm(type, id, field, event); + return false; + }); + + $(name + '_form').bind("focusout", function() { + inputFieldButtonPassive(name + '_field'); + }); + + $(name + '_form').bind("focusin", function(){ + inputFieldButtonActive(name + '_field'); + }); + + $(name + '_form').bind("keydown", function(e) { + if (e.ctrlKey && (e.keyCode == 13 || e.keyCode == 10)) { + submitForm(type, id, field, event); + } + }); + + $(name + '_field').closest('.inline-input-container').children('.inline-input-accept').bind('click', function() { + submitForm(type, id, field, event); + }); + + $(name + '_field').closest('.inline-input-container').children('.inline-input-decline').bind('click', function() { + resetForms(); + }); + + $(name + '_solid').hide(); +} + +function resetForms() { + $('.inline-field-solid').show(); + $('.inline-field-form').hide(); +} + +function inputFieldButtonActive(selector) { + $(selector).closest('.inline-input-container').children('.inline-input-accept').removeClass('inline-input-passive').addClass('inline-input-active'); + $(selector).closest('.inline-input-container').children('.inline-input-decline').removeClass('inline-input-passive').addClass('inline-input-active'); +} + +function inputFieldButtonPassive(selector) { + $(selector).closest('.inline-input-container').children('.inline-input-accept').addClass('inline-input-passive').removeClass('inline-input-active'); + $(selector).closest('.inline-input-container').children('.inline-input-decline').addClass('inline-input-passive').removeClass('inline-input-active'); +} + +function autoresize(textarea) { + textarea.style.height = '20px'; + textarea.style.height = (textarea.scrollHeight) + 'px'; +} + +// submit the form - this can be triggered by unfocusing the activated form field or by submitting the form (hitting enter) +// after the form is submitted, intercept the response and act on it +function submitForm(type, id, field, event) { + var name = '#' + type + '_' + id + '_' + field; + $.ajax({ + data: $(name + '_field').closest("form").serialize(), + success:function (data, textStatus) { + handleAjaxEditResponse(data, event); + }, + error:function() { + alert('Request failed. This may be caused by the CSRF protection blocking your request. The forms will now be refreshed to resolve the issue.'); + updateAttributeIndexOnSuccess(event); + }, + type:"post", + url:"/attributes/editField/" + id + }); + $(name + '_field').unbind("keyup"); + $(name + '_form').unbind("focusout"); + return false; +}; + +function handleAjaxEditResponse(data, event) { + if (data == "\"saved\"") updateAttributeIndexOnSuccess(event); + else { + updateAttributeIndexOnSuccess(event); + } +} + +$(function(){ + $('a:contains("Delete")').removeAttr('onclick'); + $('a:contains("Delete")').click(function(e){ + e.preventDefault(); + var form = $(this).prev(); + url = $(form).attr("action"); + $.post(url); + return false; + }); +}); \ No newline at end of file