From 846c130fa30a97903c74aa3fd446ec96eeffafd7 Mon Sep 17 00:00:00 2001 From: iglocska Date: Sun, 28 Jan 2024 18:05:29 +0100 Subject: [PATCH] new: [collections] feature added. Still missing sync integration - WiP --- .../CollectionElementsController.php | 143 ++++++++++++ app/Controller/CollectionsController.php | 171 +++++++++++++++ app/Controller/Component/ACLComponent.php | 13 ++ app/Model/Collection.php | 143 ++++++++++++ app/Model/CollectionElement.php | 205 ++++++++++++++++++ app/View/CollectionElements/add.ctp | 37 ++++ .../add_element_to_collection.ctp | 28 +++ app/View/CollectionElements/index.ctp | 61 ++++++ app/View/Collections/add.ctp | 51 +++++ app/View/Collections/index.ctp | 96 ++++++++ app/View/Collections/view.ctp | 64 ++++++ .../IndexTable/Fields/model.ctp | 23 +- .../genericElements/SideMenu/side_menu.ctp | 77 ++++++- app/View/Elements/global_menu.ctp | 7 + 14 files changed, 1116 insertions(+), 3 deletions(-) create mode 100644 app/Controller/CollectionElementsController.php create mode 100644 app/Controller/CollectionsController.php create mode 100644 app/Model/Collection.php create mode 100644 app/Model/CollectionElement.php create mode 100644 app/View/CollectionElements/add.ctp create mode 100644 app/View/CollectionElements/add_element_to_collection.ctp create mode 100644 app/View/CollectionElements/index.ctp create mode 100644 app/View/Collections/add.ctp create mode 100644 app/View/Collections/index.ctp create mode 100644 app/View/Collections/view.ctp diff --git a/app/Controller/CollectionElementsController.php b/app/Controller/CollectionElementsController.php new file mode 100644 index 000000000..2bd569316 --- /dev/null +++ b/app/Controller/CollectionElementsController.php @@ -0,0 +1,143 @@ + 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)) { + 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)); + } + } + } + } +} diff --git a/app/Controller/CollectionsController.php b/app/Controller/CollectionsController.php new file mode 100644 index 000000000..a547c5c62 --- /dev/null +++ b/app/Controller/CollectionsController.php @@ -0,0 +1,171 @@ + 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; + } + } +} diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index e86e3e642..631b6b1e1 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -89,6 +89,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' => [], diff --git a/app/Model/Collection.php b/app/Model/Collection.php new file mode 100644 index 000000000..0256eeb6c --- /dev/null +++ b/app/Model/Collection.php @@ -0,0 +1,143 @@ + 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' + ]; + + 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; + } +} diff --git a/app/Model/CollectionElement.php b/app/Model/CollectionElement.php new file mode 100644 index 000000000..1667eb86e --- /dev/null +++ b/app/Model/CollectionElement.php @@ -0,0 +1,205 @@ + 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; + } +} diff --git a/app/View/CollectionElements/add.ctp b/app/View/CollectionElements/add.ctp new file mode 100644 index 000000000..c92fd639f --- /dev/null +++ b/app/View/CollectionElements/add.ctp @@ -0,0 +1,37 @@ +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); +} diff --git a/app/View/CollectionElements/add_element_to_collection.ctp b/app/View/CollectionElements/add_element_to_collection.ctp new file mode 100644 index 000000000..ec1b2486c --- /dev/null +++ b/app/View/CollectionElements/add_element_to_collection.ctp @@ -0,0 +1,28 @@ + '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();' + ] + ] +]); + diff --git a/app/View/CollectionElements/index.ctp b/app/View/CollectionElements/index.ctp new file mode 100644 index 000000000..af4802ba2 --- /dev/null +++ b/app/View/CollectionElements/index.ctp @@ -0,0 +1,61 @@ + __('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' + ] + ] + ] + ] + ]); + +?> diff --git a/app/View/Collections/add.ctp b/app/View/Collections/add.ctp new file mode 100644 index 000000000..e5850bdb1 --- /dev/null +++ b/app/View/Collections/add.ctp @@ -0,0 +1,51 @@ +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); +} diff --git a/app/View/Collections/index.ctp b/app/View/Collections/index.ctp new file mode 100644 index 000000000..454e64620 --- /dev/null +++ b/app/View/Collections/index.ctp @@ -0,0 +1,96 @@ + __('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' + ] + ] + ] + ] + ]); + +?> diff --git a/app/View/Collections/view.ctp b/app/View/Collections/view.ctp new file mode 100644 index 000000000..4a5113f67 --- /dev/null +++ b/app/View/Collections/view.ctp @@ -0,0 +1,64 @@ +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' + ] + ] + ] +); diff --git a/app/View/Elements/genericElements/IndexTable/Fields/model.ctp b/app/View/Elements/genericElements/IndexTable/Fields/model.ctp index 52d0eaeda..51b46946c 100644 --- a/app/View/Elements/genericElements/IndexTable/Fields/model.ctp +++ b/app/View/Elements/genericElements/IndexTable/Fields/model.ctp @@ -1,3 +1,22 @@ %s (%s)', + $baseurl, + h($model_path), + h($model_id), + h($model_name), + h($model_id) + ); + +} + + diff --git a/app/View/Elements/genericElements/SideMenu/side_menu.ctp b/app/View/Elements/genericElements/SideMenu/side_menu.ctp index d0f664e18..50a632e05 100644 --- a/app/View/Elements/genericElements/SideMenu/side_menu.ctp +++ b/app/View/Elements/genericElements/SideMenu/side_menu.ctp @@ -265,6 +265,22 @@ $divider = '
  • '; '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 = '
  • '; ); } 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 = '
  • '; '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; diff --git a/app/View/Elements/global_menu.ctp b/app/View/Elements/global_menu.ctp index 4a8e50968..d909df771 100755 --- a/app/View/Elements/global_menu.ctp +++ b/app/View/Elements/global_menu.ctp @@ -31,6 +31,13 @@ array( 'type' => 'separator' ), + array( + 'text' => __('List Collections'), + 'url' => $baseurl . '/collections/index' + ), + [ + 'type' => 'separator' + ], array( 'text' => __('View Proposals'), 'url' => $baseurl . '/shadow_attributes/index/all:0'