From e73d1001a02eb60062845f8f95e8f48daf52a028 Mon Sep 17 00:00:00 2001 From: iglocska Date: Thu, 14 Dec 2023 12:28:13 +0100 Subject: [PATCH 001/113] new: [db] tables added for notes --- app/Model/AppModel.php | 78 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index f5c8b1986..00c987d9e 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -86,7 +86,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 + 117 => false, 118 => false ); const ADVANCED_UPDATES_DESCRIPTION = array( @@ -2003,6 +2003,82 @@ class AppModel extends Model INDEX `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; break; + case 118: + $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, + `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, + `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` + ) 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, + `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 `related_object_uuid` (`related_object_uuid`), + KEY `related_object_type` (`related_object_type`) + ) 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;'; From 8015f76c69bfb6e651998a2e1e554386e1932304 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 20 Dec 2023 14:36:45 +0100 Subject: [PATCH 002/113] new: [analyst-notes:UI] Started UI for analyst notes - WiP --- app/Controller/EventsController.php | 54 ++++ .../genericElements/Analyst_notes/notes.ctp | 243 ++++++++++++++++++ .../SingleViews/Fields/uuidField.ctp | 116 +++++++++ app/View/Events/view.ctp | 2 +- 4 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 app/View/Elements/genericElements/Analyst_notes/notes.ctp diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 67175c2f0..8ca5087e4 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1679,6 +1679,60 @@ class EventsController extends AppController $this->set('sightingsDbEnabled', (bool)Configure::read('Plugin.Sightings_sighting_db_enable')); } + public function getThread($scope, $uuid) + { + $object_uuid = 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a'; + $scope = 'event'; + $notes = [ + [ + 'analyst_note' => 'This is a note', + 'authors' => ['adulau', 'iglocska'], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 1, + 'uuid' => '91bc1aa1-2322-43b9-9aad-c0262e6248b3', + 'note_type' => 0, + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'analyst_note' => 'This is another note', + 'authors' => ['mokaddem',], + 'org_uuid' => ' 1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => ' 1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 2, 'uuid' => ' 1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', 'name' => 'CIRCL'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 0, + 'id' => 2, + 'uuid' => '5a019778-6f0f-4e80-94c5-2e9ec33c9a92', + 'note_type' => 0, + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'analyst_note' => 'This is an opinion', + 'authors' => ['mokaddem',], + 'org_uuid' => ' 1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => ' 1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 3, 'uuid' => ' 5d6d3b30-9db0-44b9-8869-7f56a5e38e14', 'name' => 'Training'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 0, + 'id' => 2, + 'uuid' => '5a019778-6f0f-4e80-94c5-2e9ec33c9a92', + 'note_type' => 1, + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + ]; + + $this->set('notes', $notes); + $this->set('scope', $scope); + $this->set('object_uuid', $object_uuid); + } + public function view($id = null, $continue = false, $fromEvent = null) { if ($this->request->is('head')) { // Just check if event exists diff --git a/app/View/Elements/genericElements/Analyst_notes/notes.ctp b/app/View/Elements/genericElements/Analyst_notes/notes.ctp new file mode 100644 index 000000000..4d5e49005 --- /dev/null +++ b/app/View/Elements/genericElements/Analyst_notes/notes.ctp @@ -0,0 +1,243 @@ + + + + + + + \ No newline at end of file diff --git a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp index a6fd54cb1..f01fa1225 100644 --- a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp +++ b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp @@ -4,3 +4,119 @@ '%s', h($uuid) ); + + $object_uuid = 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a'; + $object_type = 'event'; + $notes = [ + [ + 'analyst_note' => 'This is a note', + 'note_type' => 0, + 'authors' => ['adulau', 'iglocska'], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 1, + 'uuid' => '91bc1aa1-2322-43b9-9aad-c0262e6248b3', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'analyst_note' => 'This is another note', + 'note_type' => 0, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 2, 'uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', 'name' => 'CIRCL'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 2, + 'uuid' => '5a019778-6f0f-4e80-94c5-2e9ec33c9a92', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + 'notes' => [ + [ + 'opinion' => 10, + 'comment' => 'This is analysis is really bad!', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 6, + 'uuid' => 'a3aca875-e5d8-4b74-8a2f-63100f17afe0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + 'notes' => [ + [ + 'opinion' => 100, + 'comment' => 'No! It\'s really good!', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 2, 'uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', 'name' => 'CIRCL'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 7, + 'uuid' => '4d8585ea-bf5a-42c2-876b-02b6c9f470e0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + ] + ], + [ + 'opinion' => 70, + 'comment' => 'After further analysis, it\'s OK.', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 0, + 'id' => 8, + 'uuid' => 'a3aca875-e5d8-4b74-8a2f-63100f17afe0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + ], + ] + ], + [ + 'opinion' => 80, + 'comment' => 'This is a second opinion', + 'note_type' => 1, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 3, 'uuid' => '5d6d3b30-9db0-44b9-8869-7f56a5e38e14', 'name' => 'Training'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 4, + 'uuid' => '41c2ad07-4529-4014-ab8c-0a3f0d6fccc1', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'opinion' => 45, + 'comment' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 'note_type' => 1, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 3, 'uuid' => '5d6d3b30-9db0-44b9-8869-7f56a5e38e14', 'name' => 'Training'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 5, + 'uuid' => '24957461-344c-4b7e-81fe-1321f3e9949a', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + ]; + + if (!empty($notes)) { + echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $notes, 'object_uuid' => $object_uuid, 'object_type' => $object_type]); + } diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index d564266dc..571500aad 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -16,7 +16,7 @@ [ 'key' => 'UUID', 'path' => 'Event.uuid', - 'class' => 'quickSelect', + 'class' => '', 'type' => 'uuid', 'action_buttons' => [ [ From c1c44fa644863f60e1a79c3b6b42b7a13716057d Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Thu, 21 Dec 2023 09:28:43 +0100 Subject: [PATCH 003/113] chg: [analyst-notes:ui] Added support of permissions, callbacks and improved UI - WiP --- .../genericElements/Analyst_notes/notes.ctp | 224 ++++++++++++++++-- .../SingleViews/Fields/uuidField.ctp | 113 +-------- 2 files changed, 206 insertions(+), 131 deletions(-) diff --git a/app/View/Elements/genericElements/Analyst_notes/notes.ctp b/app/View/Elements/genericElements/Analyst_notes/notes.ctp index 4d5e49005..cfc62f57c 100644 --- a/app/View/Elements/genericElements/Analyst_notes/notes.ctp +++ b/app/View/Elements/genericElements/Analyst_notes/notes.ctp @@ -1,7 +1,124 @@ 'This is a note', + 'note_type' => 0, + 'authors' => ['adulau', 'iglocska'], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 1, + 'uuid' => '91bc1aa1-2322-43b9-9aad-c0262e6248b3', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'analyst_note' => 'This is another note', + 'note_type' => 0, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 2, 'uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', 'name' => 'CIRCL'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 2, + 'uuid' => '5a019778-6f0f-4e80-94c5-2e9ec33c9a92', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + 'notes' => [ + [ + 'opinion' => 10, + 'comment' => 'This is analysis is really bad!', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 6, + 'uuid' => 'a3aca875-e5d8-4b74-8a2f-63100f17afe0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + 'notes' => [ + [ + 'opinion' => 100, + 'comment' => 'No! It\'s really good!', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 2, 'uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', 'name' => 'CIRCL'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 2, + 'id' => 7, + 'uuid' => '4d8585ea-bf5a-42c2-876b-02b6c9f470e0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + ] + ], + [ + 'opinion' => 70, + 'comment' => 'After further analysis, it\'s OK.', + 'note_type' => 1, + 'authors' => ['chrisr3d',], + 'org_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'orgc_uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', + 'Organisation' => ['id' => 23, 'uuid' => '27e68e2e-c7a9-4aba-9949-ca3383facb24', 'name' => 'ORG_1'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 0, + 'id' => 8, + 'uuid' => 'a3aca875-e5d8-4b74-8a2f-63100f17afe0', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a', + ], + ] + ], + [ + 'opinion' => 80, + 'comment' => 'This is a second opinion', + 'note_type' => 1, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 3, 'uuid' => '5d6d3b30-9db0-44b9-8869-7f56a5e38e14', 'name' => 'Training'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 4, + 'uuid' => '41c2ad07-4529-4014-ab8c-0a3f0d6fccc1', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], + [ + 'opinion' => 50, + 'comment' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 'note_type' => 1, + 'authors' => ['mokaddem',], + 'org_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'orgc_uuid' => '1646fb8f-6f23-4b51-ae80-c84d1ff8fbe0', + 'Organisation' => ['id' => 3, 'uuid' => '5d6d3b30-9db0-44b9-8869-7f56a5e38e14', 'name' => 'Training'], + 'created' => new DateTime(), + 'modified' => new DateTime(), + 'distribution' => 3, + 'id' => 5, + 'uuid' => '24957461-344c-4b7e-81fe-1321f3e9949a', + 'object_uuid' => 'bf74e1a4-99c2-4fcb-8a5d-a71118effd1a' + ], +]; ?> @@ -16,6 +133,7 @@ $seed = mt_rand(); ') var baseNoteTemplate = doT.template('\
\
\ @@ -30,11 +148,19 @@ $seed = mt_rand(); {{=it.modified_relative}} • {{=it.modified}} \ {{=it.distribution_text}} \ \ - \ - \ - \ - \ - \ + \ + {{? it._permissions.can_add }} \ + \ + {{?}} \ + {{? it._permissions.can_add }} \ + \ + {{?}} \ + {{? it._permissions.can_edit }} \ + \ + {{?}} \ + {{? it._permissions.can_delete }} \ + \ + {{?}} \ \
\
{{=it.content}}
\ @@ -43,7 +169,9 @@ $seed = mt_rand();
\ ') var analystTemplate = doT.template('\ - {{=it.analyst_note}} \ +
\ + {{=it.analyst_note}} \ +
\ ') var opinionStars = '\
\ @@ -70,14 +198,21 @@ $seed = mt_rand(); /100 \ \
\ -
{{=it.comment}}
\ +
\ + {{=it.comment}} \ +
\ ') var replyNoteTemplate = doT.template('\ -
\ + \ + \ + \ +
\ {{=it.notes_html}} \
\ ') - var addNoteButton = '' @@ -89,20 +224,26 @@ $seed = mt_rand(); function openNotes(clicked) { openPopover(clicked, renderedNotes, undefined, undefined, function() { $('.popover').css('top', '150px') + $(clicked).removeClass('have-a-popover') // avoid closing the popover if a confirm popover (like the delete one) is called }) } function renderNotes(notes) { var renderedNotesArray = [] - notes.forEach(function(note) { - var noteHtml = renderNote(note) + if (notes.length == 0) { + var emptyHtml = '' + renderedNotesArray.push(emptyHtml) + } else { + notes.forEach(function(note) { + var noteHtml = renderNote(note) + + if (note.notes) { // The notes has more notes attached + noteHtml += replyNoteTemplate({notes_html: renderNotes(note.notes)}) + } - if (note.notes) { // The notes has more notes attached - noteHtml += replyNoteTemplate({notes_html: renderNotes(note.notes)}) - } - - renderedNotesArray.push(noteHtml) - }); + renderedNotesArray.push(noteHtml) + }); + } return renderedNotesArray.join('') } @@ -114,10 +255,15 @@ $seed = mt_rand(); note.distribution_text = note.distribution != 4 ? shortDist[note.distribution] : note.SharingGroup.name note.distribution_color = note.distribution == 0 ? '#ff0000' : (note.distribution == 4 ? '#0000ff' : '#000') note.authors = Array.isArray(note.authors) ? note.authors.join(', ') : note.authors; + note._permissions = { + can_edit: true, + can_delete: true, + can_add: true, + } if (note.note_type == 0) { // analyst note note.content = analystTemplate(note) } else if (note.note_type == 1) { // opinion - note.opinion_color = note.opinion > 50 ? '#468847' : '#b94a48'; + note.opinion_color = note.opinion == 50 ? '#333' : ( note.opinion > 50 ? '#468847' : '#b94a48'); note.opinion_text = (note.opinion >= 81) ? '' : ((note.opinion >= 61) ? '' : ((note.opinion >= 41) ? '' : ((note.opinion >= 21) ? '' : ''))) note.content = opinionTemplate(note) } else { @@ -131,12 +277,52 @@ $seed = mt_rand(); renderedNotes = nodeContainerTemplate({content: renderNotes(notes) + addNoteButton}) } + function createNewNote(clicked, object_type, object_uuid) { + note_type = 0; + openGenericModal(baseurl + '' + object_type + '/' + object_uuid + '/' + note_type) + } + + function addNote(clicked, note_uuid) { + object_type = 'note'; + note_type = 0; + openGenericModal(baseurl + '' + object_type + '/' + note_uuid + '/' + note_type) + } + + function addOpinion(clicked, note_uuid) { + object_type = 'note'; + note_type = 1; + openGenericModal(baseurl + '' + object_type + '/' + note_uuid + '/' + note_type) + } + + function editNote(clicked, note_id) { + openGenericModal(baseurl + '' + note_id) + } + + function deleteNote(clicked, note_id) { + var deletionSuccessCallback = function(data) { + $(clicked).closest('.analyst-note').remove() + } + popoverConfirm(clicked, '', undefined, deletionSuccessCallback) + } + + function registerListeners() { + } + + $(document).ready(function() { renderAllNotesWithForm() + registerListeners() }) + + \ No newline at end of file From ceda8c3788d348c30578b7c5b849b0462700c082 Mon Sep 17 00:00:00 2001 From: iglocska Date: Fri, 19 Jan 2024 17:54:06 +0100 Subject: [PATCH 017/113] chf: [notes] wip --- app/Controller/Component/CRUDComponent.php | 3 ++- app/Controller/EventsController.php | 4 +++- app/Lib/Tools/JSONConverterTool.php | 4 ++-- app/Model/AppModel.php | 13 +++++++++++-- app/Model/Attribute.php | 3 ++- app/Model/Behavior/AnalystDataBehavior.php | 2 +- app/Model/Behavior/AnalystDataParentBehavior.php | 5 +++-- app/Model/Event.php | 13 +++++++++++++ 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/Controller/Component/CRUDComponent.php b/app/Controller/Component/CRUDComponent.php index 5d654ffda..1491579d2 100644 --- a/app/Controller/Component/CRUDComponent.php +++ b/app/Controller/Component/CRUDComponent.php @@ -25,7 +25,6 @@ class CRUDComponent extends Component } $options['filters'][] = 'quickFilter'; } - $this->Controller->includeAnalystData->{$this->Controller->modelClass}->includeAnalystData = true; $params = $this->Controller->IndexFilter->harvestParameters(empty($options['filters']) ? [] : $options['filters']); $query = []; $query = $this->setFilters($params, $query); @@ -40,6 +39,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'])) { @@ -50,6 +50,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'])) { diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 40ba96115..891545b8d 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -756,6 +756,7 @@ class EventsController extends AppController $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']])) { @@ -1695,7 +1697,7 @@ class EventsController extends AppController } $namedParams = $this->request->params['named']; - $this->Event->includeAnalystData = true; + $conditions['includeAnalystData'] = true; if ($this->_isRest()) { $conditions['includeAttachments'] = isset($namedParams['includeAttachments']) ? $namedParams['includeAttachments'] : true; } else { diff --git a/app/Lib/Tools/JSONConverterTool.php b/app/Lib/Tools/JSONConverterTool.php index 7ede8dd25..a45a72ad3 100644 --- a/app/Lib/Tools/JSONConverterTool.php +++ b/app/Lib/Tools/JSONConverterTool.php @@ -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]; diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index c104112fe..f51011d5e 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -4117,8 +4117,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) diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index feb58d529..52b2cd8b2 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -36,7 +36,8 @@ class Attribute extends AppModel 'Trim', 'Containable', 'Regexp' => array('fields' => array('value')), - 'LightPaginator' + 'LightPaginator', + 'AnalystDataParent' ); public $displayField = 'value'; diff --git a/app/Model/Behavior/AnalystDataBehavior.php b/app/Model/Behavior/AnalystDataBehavior.php index 26fe3c695..550a1ec16 100644 --- a/app/Model/Behavior/AnalystDataBehavior.php +++ b/app/Model/Behavior/AnalystDataBehavior.php @@ -15,7 +15,7 @@ class AnalystDataBehavior extends ModelBehavior } // 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) + public function fetchForUuid(Model $Model, $uuid, $user = null) { $conditions = [ 'object_uuid' => $uuid diff --git a/app/Model/Behavior/AnalystDataParentBehavior.php b/app/Model/Behavior/AnalystDataParentBehavior.php index d0801b194..58e9d27ae 100644 --- a/app/Model/Behavior/AnalystDataParentBehavior.php +++ b/app/Model/Behavior/AnalystDataParentBehavior.php @@ -11,7 +11,7 @@ class AnalystDataParentBehavior extends ModelBehavior - public function attachAnalystData(array $object, array $types = ['Note', 'Opinion', 'Relationship']) + public function attachAnalystData(Model $model, array $object, array $types = ['Note', 'Opinion', 'Relationship']) { // No uuid, nothing to attach if (empty($object['uuid'])) { @@ -36,7 +36,7 @@ class AnalystDataParentBehavior extends ModelBehavior } return $data; } - +/* public function afterFind(Model $model, $results, $primary = false) { if (!empty($model->includeAnalystData)) { @@ -48,4 +48,5 @@ class AnalystDataParentBehavior extends ModelBehavior } return $results; } + */ } diff --git a/app/Model/Event.php b/app/Model/Event.php index 7b2166d36..293cd031d 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -2039,6 +2039,9 @@ class Event extends AppModel if (!empty($options['page'])) { $params['page'] = $options['page']; } + if (!empty($options['includeAnalystData'])) { + $params['includeAnalystData'] = $options['includeAnalystData']; + } if (!empty($options['order'])) { $params['order'] = $this->findOrder( $options['order'], @@ -2199,6 +2202,12 @@ 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) { + $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(); @@ -2249,6 +2258,10 @@ class Event extends AppModel if (isset($tempObjectAttributeContainer[$objectValue['id']])) { $objectValue['Attribute'] = $tempObjectAttributeContainer[$objectValue['id']]; } + if (!empty($options['includeAnalystData'])) { + $analyst_data = $this->Object->attachAnalystData($objectValue); + $objectValue = array_merge($objectValue, $analyst_data); + } } unset($tempObjectAttributeContainer); } From eb03f8fcc0f1a2a49086b8fe154291003cdca4dd Mon Sep 17 00:00:00 2001 From: iglocska Date: Fri, 19 Jan 2024 17:54:54 +0100 Subject: [PATCH 018/113] chg: [uuid field] update --- .../Elements/genericElements/SingleViews/Fields/uuidField.ctp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp index 4d8af0b23..4c3c39cc8 100644 --- a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp +++ b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp @@ -5,7 +5,7 @@ h($uuid) ); - $notes = !empty($notes) ? $notes : []; + $analyst_data = !empty($analyst_data) ? $analyst_data : []; $object_uuid = !empty($object_uuid) ? $object_uuid : null; $object_type = !empty($object_type) ? $object_type : null; - echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $notes, 'object_uuid' => $object_uuid, 'object_type' => $object_type]); + echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $analyst_data, 'object_uuid' => $object_uuid, 'object_type' => $object_type]); From dca913c969d33d90d90c896a3241dbb1003ef7af Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 24 Jan 2024 21:48:53 +0100 Subject: [PATCH 019/113] chg: [analyst-data] Linked CRUD and UI together - WiP - Added dynamic association binding - Recursive notes and opinions injection - few improvements - fixes -> Still need to link CRUD for relationships and UI -> Still need to refactor for performance notes/opinions loading --- app/Controller/AnalystDataController.php | 6 +- app/Controller/Component/CRUDComponent.php | 2 +- app/Controller/EventsController.php | 2 + app/Lib/Tools/JSONConverterTool.php | 23 +- app/Model/AnalystData.php | 80 +++- app/Model/Attribute.php | 3 +- app/Model/Behavior/AnalystDataBehavior.php | 3 +- .../Behavior/AnalystDataParentBehavior.php | 4 +- app/Model/Event.php | 4 + app/Model/Note.php | 1 + app/Model/Opinion.php | 2 + app/Model/Relationship.php | 2 + app/View/AnalystData/add.ctp | 46 +- app/View/AnalystData/index.ctp | 34 +- .../Elements/Events/View/row_attribute.ctp | 24 +- app/View/Elements/Events/View/row_object.ctp | 4 +- .../Elements/Events/View/row_proposal.ctp | 4 +- .../notes.ctp => Analyst_data/generic.ctp} | 436 +++++++++++------- .../Form/Fields/opinionField.ctp | 176 +++++++ .../SingleViews/Fields/uuidField.ctp | 20 +- .../genericElements/shortUuidWithNotes.ctp | 14 +- app/View/Events/view.ctp | 4 + app/webroot/js/misp.js | 14 +- 23 files changed, 703 insertions(+), 205 deletions(-) rename app/View/Elements/genericElements/{Analyst_notes/notes.ctp => Analyst_data/generic.ctp} (66%) create mode 100644 app/View/Elements/genericElements/Form/Fields/opinionField.ctp diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index b6127e819..c1fc94324 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -23,7 +23,7 @@ class AnalystDataController extends AppController 'Relationship' ]; - public $modelSelection = 'Note'; + // public $modelSelection = 'Note'; private function _setViewElements() { @@ -65,7 +65,8 @@ class AnalystDataController extends AppController { $this->__typeSelector($type); $this->set('id', $id); - $params = []; + $params = [ + ]; $this->CRUD->edit($id, $params); if ($this->IndexFilter->isRest()) { return $this->restResponsePayload; @@ -119,6 +120,7 @@ class AnalystDataController extends AppController 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(); diff --git a/app/Controller/Component/CRUDComponent.php b/app/Controller/Component/CRUDComponent.php index 5d654ffda..88dcf72bb 100644 --- a/app/Controller/Component/CRUDComponent.php +++ b/app/Controller/Component/CRUDComponent.php @@ -25,7 +25,7 @@ class CRUDComponent extends Component } $options['filters'][] = 'quickFilter'; } - $this->Controller->includeAnalystData->{$this->Controller->modelClass}->includeAnalystData = true; + $this->Controller->{$this->Controller->modelClass}->includeAnalystData = true; $params = $this->Controller->IndexFilter->harvestParameters(empty($options['filters']) ? [] : $options['filters']); $query = []; $query = $this->setFilters($params, $query); diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 40ba96115..4f01d4392 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1231,6 +1231,8 @@ class EventsController extends AppController } } + $this->Event->Attribute->includeAnalystData = true; + if (isset($filters['focus'])) { $this->set('focus', $filters['focus']); } diff --git a/app/Lib/Tools/JSONConverterTool.php b/app/Lib/Tools/JSONConverterTool.php index 7ede8dd25..9751768e0 100644 --- a/app/Lib/Tools/JSONConverterTool.php +++ b/app/Lib/Tools/JSONConverterTool.php @@ -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)) { diff --git a/app/Model/AnalystData.php b/app/Model/AnalystData.php index 37c32ea9a..1dc52e515 100644 --- a/app/Model/AnalystData.php +++ b/app/Model/AnalystData.php @@ -23,8 +23,45 @@ class AnalystData extends AppModel 'SharingGroup' ]; + const NOTE = 0, + OPINION = 1, + RELATIONSHIP = 2; + public $current_user = null; + public function __construct($id = false, $table = null, $ds = null) + { + parent::__construct($id, $table, $ds); + $this->bindModel([ + 'belongsTo' => [ + 'Organisation' => [ + 'className' => 'Organisation', + 'foreignKey' => false, + 'conditions' => [ + sprintf('%s.orgc_uuid = Organisation.uuid', $this->alias) + ], + ] + ] + ]); + } + + public function afterFind($results, $primary = false) + { + parent::afterFind($results, $primary); + foreach ($results as $i => $v) { + $results[$i][$this->alias]['note_type'] = $this->current_type_id; + $results[$i][$this->alias]['note_type_name'] = $this->current_type; + if (!isset($v['Organisation'])) { + $this->Organisation = ClassRegistry::init('Organisation'); + $results[$i][$this->alias]['Organisation'] = $this->Organisation->find('first', ['condition' => ['uuid' => $v[$this->alias]['orgc_uuid']]])['Organisation']; + } else { + $results[$i][$this->alias]['Organisation'] = $v['Organisation']; + } + unset($results[$i]['Organisation']); + $results[$i][$this->alias] = $this->fetchChildNotesAndOpinions($results[$i][$this->alias]); + } + return $results; + } public function beforeValidate($options = array()) { @@ -39,7 +76,6 @@ class AnalystData extends AppModel $this->data[$this->current_type]['org_uuid'] = $this->current_user['Organisation']['uuid']; $this->data[$this->current_type]['authors'] = $this->current_user['email']; } - debug($this->data); return true; } @@ -57,4 +93,46 @@ class AnalystData extends AppModel } throw new NotFoundException(__('Invalid UUID')); } + + public function fetchChildNotesAndOpinions(array $analystData): array + { + $this->Note = ClassRegistry::init('Note'); + $this->Opinion = ClassRegistry::init('Opinion'); + $paramsNote = [ + 'recursive' => -1, + 'contain' => ['Organisation'], + 'conditions' => [ + 'object_type' => $this->current_type, + 'object_uuid' => $analystData['uuid'], + ] + ]; + $paramsOpinion = [ + 'recursive' => -1, + 'contain' => ['Organisation'], + 'conditions' => [ + 'object_type' => $this->current_type, + 'object_uuid' => $analystData['uuid'], + ] + ]; + + // recursively fetch and include nested notes and opinions + $childNotes = array_map(function ($item) { + $expandedNotes = $this->fetchChildNotesAndOpinions($item[$this->Note->current_type]); + return $expandedNotes; + // return $item[$this->Note->current_type]; + }, $this->Note->find('all', $paramsNote)); + $childOpinions = array_map(function ($item) { + $expandedNotes = $this->fetchChildNotesAndOpinions($item[$this->Opinion->current_type]); + return $expandedNotes; + // return $item[$this->Opinion->current_type]; + }, $this->Opinion->find('all', $paramsOpinion)); + + if (!empty($childNotes)) { + $analystData[$this->Note->current_type] = $childNotes; + } + if (!empty($childOpinions)) { + $analystData[$this->Opinion->current_type] = $childOpinions; + } + return $analystData; + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index feb58d529..7f2205085 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -36,7 +36,8 @@ class Attribute extends AppModel 'Trim', 'Containable', 'Regexp' => array('fields' => array('value')), - 'LightPaginator' + 'LightPaginator', + 'AnalystDataParent', ); public $displayField = 'value'; diff --git a/app/Model/Behavior/AnalystDataBehavior.php b/app/Model/Behavior/AnalystDataBehavior.php index 26fe3c695..a96db9cb6 100644 --- a/app/Model/Behavior/AnalystDataBehavior.php +++ b/app/Model/Behavior/AnalystDataBehavior.php @@ -38,7 +38,8 @@ class AnalystDataBehavior extends ModelBehavior } return $Model->find('all', [ 'recursive' => -1, - 'conditions' => $conditions + 'conditions' => $conditions, + 'contain' => ['Organisation'], ]); } diff --git a/app/Model/Behavior/AnalystDataParentBehavior.php b/app/Model/Behavior/AnalystDataParentBehavior.php index d0801b194..ad800c52b 100644 --- a/app/Model/Behavior/AnalystDataParentBehavior.php +++ b/app/Model/Behavior/AnalystDataParentBehavior.php @@ -11,7 +11,7 @@ class AnalystDataParentBehavior extends ModelBehavior - public function attachAnalystData(array $object, array $types = ['Note', 'Opinion', 'Relationship']) + public function attachAnalystData(Model $Model, array $object, array $types = ['Note', 'Opinion', 'Relationship']) { // No uuid, nothing to attach if (empty($object['uuid'])) { @@ -42,7 +42,7 @@ class AnalystDataParentBehavior extends ModelBehavior if (!empty($model->includeAnalystData)) { foreach ($results as $k => $item) { if (isset($item[$model->alias])) { - $results[$k] = array_merge($results[$k], $this->attachAnalystData($item[$model->alias])); + $results[$k] = array_merge($results[$k], $this->attachAnalystData($model, $item[$model->alias])); } } } diff --git a/app/Model/Event.php b/app/Model/Event.php index 7b2166d36..04fd0d0bc 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -2223,6 +2223,10 @@ class Event extends AppModel unset($attribute['EventTag']); } } + $allAnalystData = $this->Attribute->attachAnalystData($attribute); // afterFind from AnalystDataParentBehavior is not called. Probably because we're in an association + foreach ($allAnalystData as $type => $analystData) { + $attribute[$type] = $analystData; + } // If a shadowattribute can be linked to an attribute, link it to it // This is to differentiate between proposals that were made to an attribute for modification and between proposals for new attributes $attribute['ShadowAttribute'] = $shadowAttributeByOldId[$attribute['id']] ?? []; diff --git a/app/Model/Note.php b/app/Model/Note.php index e10293046..1a2231fa0 100644 --- a/app/Model/Note.php +++ b/app/Model/Note.php @@ -12,6 +12,7 @@ class Note extends AnalystData ); public $current_type = 'Note'; + public $current_type_id = 0; public $validate = array( ); diff --git a/app/Model/Opinion.php b/app/Model/Opinion.php index fe59f1042..a1a6b0466 100644 --- a/app/Model/Opinion.php +++ b/app/Model/Opinion.php @@ -1,5 +1,6 @@ 'opinion', + 'class' => '', + 'type' => 'opinion' + ], + [ + 'field' => 'comment', + 'type' => 'textarea', + 'class' => 'input span6' + ] + ] + ); } else if ($modelSelection === 'Relationship') { @@ -60,7 +74,7 @@ echo $this->element('genericElements/Form/genericForm', [ 'fields' => $fields, 'submit' => [ 'action' => $this->request->params['action'], - 'ajaxSubmit' => 'submitGenericFormInPlace();' + 'ajaxSubmit' => 'submitGenericFormInPlace(analystDataSubmitSuccess, true);' ] ] ]); @@ -68,3 +82,33 @@ echo $this->element('genericElements/Form/genericForm', [ if (!$ajax) { echo $this->element('/genericElements/SideMenu/side_menu', $menuData); } +?> + + \ No newline at end of file diff --git a/app/View/AnalystData/index.ctp b/app/View/AnalystData/index.ctp index 226561f77..a0baa8d0d 100644 --- a/app/View/AnalystData/index.ctp +++ b/app/View/AnalystData/index.ctp @@ -9,6 +9,11 @@ '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', @@ -16,7 +21,7 @@ ], [ 'name' => __('Creator org'), - 'data_path' => $modelSelection . '.orgc_id' + 'data_path' => $modelSelection . '.orgc_uuid' ], [ 'name' => __('Created'), @@ -51,6 +56,10 @@ ] ); } else if ($modelSelection === 'Opinion') { + $fields = array_merge($fields, + [ + ] + ); } else if ($modelSelection === 'Relationship') { @@ -63,6 +72,27 @@ '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'), @@ -91,7 +121,7 @@ ], [ 'onclick' => sprintf( - 'openGenericModal(\'%s/analystData/edit/' . $modelSelection . '/[onclick_params_data_path]\');', + 'openGenericModal(\'%s/analystData/delete/' . $modelSelection . '/[onclick_params_data_path]\');', $baseurl ), 'onclick_params_data_path' => $modelSelection . '.id', diff --git a/app/View/Elements/Events/View/row_attribute.ctp b/app/View/Elements/Events/View/row_attribute.ctp index ee410c20a..5a15580ee 100644 --- a/app/View/Elements/Events/View/row_attribute.ctp +++ b/app/View/Elements/Events/View/row_attribute.ctp @@ -70,14 +70,34 @@ - element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?> + element('genericElements/Analyst_data/generic', [ + 'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships], + 'object_uuid' => $object['uuid'], + 'object_type' => 'Attribute' + ]); + ?> element('/Events/View/seen_field', array('object' => $object)); ?> >Time->date($object['timestamp']) . ($isNew ? '*' : '') ?> - element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?> + element('genericElements/shortUuidWithNotes', [ + 'uuid' => $object['uuid'], + 'object_type' => 'Attribute', + 'notes' => $notes, + 'opinions' => $opinions, + 'relationships' => $relationships, + ]); + ?> - element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?> + element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'Attribute']); ?> element('/Events/View/seen_field', array('object' => $object)); ?> >Time->date($object['timestamp']) . ($isNew ? '*' : '') ?> - element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'object']); ?> + element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'Object']); ?> - element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'attribute']); ?> + element('genericElements/Analyst_notes/notes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'Attribute']); ?> element('/Events/View/seen_field', array('object' => $object)); ?> @@ -59,7 +59,7 @@ ?> - element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'proposals']); ?> + element('/genericElements/shortUuidWithNotes', ['uuid' => $object['uuid'], 'notes' => !empty($object['notes']) ? $object['notes'] : [], 'object_type' => 'Proposals']); ?> 'This is a note', 'note_type' => 0, @@ -185,6 +183,10 @@ $notes = [ ], ]; +$notes = $analyst_data['notes'] ?? []; +$opinions = $analyst_data['opinions'] ?? []; +$relationships = $analyst_data['relationships'] ?? []; + $related_objects = [ 'Event' => [ 'f80a0db2-24bd-4148-929e-7c803ade7ca1' => [ @@ -238,6 +240,8 @@ $related_objects = [ ], ]; +$notes = array_merge($notes, $opinions); + if(!function_exists("countNotes")) { function countNotes($notes) { $notesTotalCount = count($notes); @@ -249,8 +253,14 @@ if(!function_exists("countNotes")) { } else { $notesCount += 1; } - if (!empty($note['notes'])) { - $nestedCounts = countNotes($note['notes']); + if (!empty($note['Note'])) { + $nestedCounts = countNotes($note['Note']); + $notesTotalCount += $nestedCounts['total']; + $notesCount += $nestedCounts['notes']; + $relationsCount += $nestedCounts['relations']; + } + if (!empty($note['Opinion'])) { + $nestedCounts = countNotes($note['Opinion']); $notesTotalCount += $nestedCounts['total']; $notesCount += $nestedCounts['notes']; $relationsCount += $nestedCounts['relations']; @@ -265,9 +275,9 @@ $relationshipsCount = $counts['relations']; ?> - + - + @@ -276,139 +286,208 @@ $relationshipsCount = $counts['relations']; \ No newline at end of file diff --git a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp index 4d8af0b23..19ebcbf09 100644 --- a/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp +++ b/app/View/Elements/genericElements/SingleViews/Fields/uuidField.ctp @@ -5,7 +5,19 @@ h($uuid) ); - $notes = !empty($notes) ? $notes : []; - $object_uuid = !empty($object_uuid) ? $object_uuid : null; - $object_type = !empty($object_type) ? $object_type : null; - echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $notes, 'object_uuid' => $object_uuid, 'object_type' => $object_type]); + 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', ['notes' => $notes, 'object_uuid' => $uuid, 'object_type' => $field['object_type']]); + echo $this->element('genericElements/Analyst_data/generic', [ + 'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships], + 'object_uuid' => $uuid, + 'object_type' => $field['object_type'] + ]); + } else { + debug('Provide object type to access notes for that object'); + } diff --git a/app/View/Elements/genericElements/shortUuidWithNotes.ctp b/app/View/Elements/genericElements/shortUuidWithNotes.ctp index 4e4b865f2..28a66b5d2 100644 --- a/app/View/Elements/genericElements/shortUuidWithNotes.ctp +++ b/app/View/Elements/genericElements/shortUuidWithNotes.ctp @@ -2,7 +2,15 @@ $uuidHalfWidth = 3; $shortUUID = sprintf('%s...%s', substr($uuid, 0, $uuidHalfWidth), substr($uuid, 36-$uuidHalfWidth, $uuidHalfWidth)); - $notes = !empty($notes) ? $notes : []; - $object_type = !empty($object_type) ? $object_type : null; echo sprintf('%s', $uuid, $shortUUID); - echo $this->element('genericElements/Analyst_notes/notes', ['notes' => $notes, 'object_uuid' => $uuid, 'object_type' => $object_type]); + + 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 + ]); + } \ No newline at end of file diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index 571500aad..f1ba0eec6 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -18,6 +18,10 @@ 'path' => 'Event.uuid', 'class' => '', 'type' => 'uuid', + 'object_type' => 'Event', + 'notes_path' => 'Note', + 'opinions_path' => 'Opinion', + 'relationships_path' => 'Relationship', 'action_buttons' => [ [ 'url' => $baseurl . '/events/add/extends:' . h($event['Event']['uuid']), diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index 4ce134526..d30228064 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -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) { @@ -5568,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. @@ -5588,7 +5594,7 @@ function submitGenericFormInPlace(callback) { $('#genericModal').modal(); }, error: xhrFailCallback, - }); + }, ajaxOptions)); } function openIdSelection(clicked, scope, action) { From 6742f9ed42859072a384cf6c528f0b6175a255f9 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Thu, 25 Jan 2024 20:01:04 +0100 Subject: [PATCH 020/113] chg: [analyst-data] Added support of fetching & displaying of related object + refacto + fixes - WiP --- app/Controller/AnalystDataController.php | 7 + app/Model/AppModel.php | 2 + app/Model/Relationship.php | 104 +++++++ app/View/AnalystData/add.ctp | 81 +++++- app/View/AnalystData/index.ctp | 15 +- .../genericElements/Analyst_data/generic.ctp | 253 ++++++++++-------- .../SingleViews/Fields/uuidField.ctp | 1 - .../genericElements/shortUuidWithNotes.ctp | 2 +- app/View/Events/view.ctp | 1 - 9 files changed, 343 insertions(+), 123 deletions(-) diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index c1fc94324..d9791777b 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -116,6 +116,13 @@ class AnalystDataController extends AppController $this->_setViewElements(); } + public function getRelatedElement($type, $uuid) + { + $this->__typeSelector('Relationship'); + $data = $this->AnalystData->getRelatedElement($this->Auth->user(), $type, $uuid); + return $this->RestResponse->viewData($data, 'json'); + } + private function __typeSelector($type) { foreach ($this->__valid_types as $vt) { if ($type === $vt) { diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index c104112fe..682518ec0 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -2070,6 +2070,7 @@ class AppModel extends Model `modified` datetime ON UPDATE CURRENT_TIMESTAMP, `distribution` tinyint(4) NOT NULL, `sharing_group_id` int(10) unsigned, + `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`), @@ -2080,6 +2081,7 @@ class AppModel extends Model 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;"; diff --git a/app/Model/Relationship.php b/app/Model/Relationship.php index 2163db5ec..ca864c86d 100644 --- a/app/Model/Relationship.php +++ b/app/Model/Relationship.php @@ -16,4 +16,108 @@ class Relationship extends AnalystData 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) { + $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 = [ + ]; + $data = $this->Event->fetchSimpleEvent($user, $uuid, $params); + } else if ($type == 'Attribute') { + $this->Attribute = ClassRegistry::init('Attribute'); + $params = [ + 'conditions' => [ + ['Attribute.uuid' => $uuid], + ], + 'contain' => ['Event' => 'Orgc',] + ]; + $data = $this->Attribute->fetchAttributeSimple($user, $params); + $data = $this->rearrangeData($data, 'Attribute'); + $data['Attribute']['Organisation'] = $data['Attribute']['Event']['Orgc']; + $data['Attribute']['orgc_uuid'] = $data['Attribute']['Event']['Orgc']['uuid']; + unset($data['Attribute']['Event']['Orgc']); + } else if ($type == 'Object') { + $this->Object = ClassRegistry::init('MispObject'); + $params = [ + 'conditions' => [ + ['Object.uuid' => $uuid], + ] + ]; + $data = $this->Object->fetchObjectSimple($user, $params); + if (!empty($data)) { + $data = $data[0]; + } + } else if ($type == 'Note') { + $this->Note = ClassRegistry::init('Note'); + $params = [ + + ]; + $data = $this->Note->fetchNote(); + } else if ($type == 'Opinion') { + $this->Opinion = ClassRegistry::init('Opinion'); + $params = [ + + ]; + $data = $this->Opinion->fetchOpinion(); + } else if ($type == 'Relationship') { + $this->Relationship = ClassRegistry::init('Relationship'); + $params = [ + + ]; + $data = $this->Relationship->fetchRelationship(); + } + return $data; + } + + private function rearrangeData(array $data, $objectType): array + { + $models = ['Event', 'Attribute', 'Object', 'Organisation', ]; + if (!empty($data)) { + foreach ($models as $model) { + if ($model == $objectType) { + continue; + } + if (isset($data[$model])) { + $data[$objectType][$model] = $data[$model]; + unset($data[$model]); + } + } + } + return $data; + } } diff --git a/app/View/AnalystData/add.ctp b/app/View/AnalystData/add.ctp index 97833100f..b471c9923 100644 --- a/app/View/AnalystData/add.ctp +++ b/app/View/AnalystData/add.ctp @@ -62,9 +62,27 @@ if ($modelSelection === 'Note') { ] ] ); - } else if ($modelSelection === 'Relationship') { - + $fields = array_merge($fields, + [ + [ + 'field' => 'relationship_type', + 'class' => 'span4', + ], + [ + 'field' => 'related_object_type', + 'class' => 'span2', + 'options' => $dropdownData['valid_targets'], + 'type' => 'dropdown', + 'stayInLine' => 1 + ], + [ + 'field' => 'related_object_uuid', + 'class' => 'span4', + ], + sprintf('
', __('Related Object'), __('- No UUID provided -')) + ] + ); } echo $this->element('genericElements/Form/genericForm', [ 'data' => [ @@ -98,8 +116,13 @@ if (!$ajax) { var noteHTMLID = '#' + data[noteType].note_type_name + '-' + data[noteType].id var $noteToReplace = $(noteHTMLID) if ($noteToReplace.length == 1) { - console.log(data); - var compiledUpdatedNote = renderNote(data[noteType]) + 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(() => { @@ -111,4 +134,52 @@ if (!$ajax) { function addNoteInUI(data) { location.reload() } - \ No newline at end of file + + function displayRelatedObject(data) { + if (Object.keys(data).length == 0) { + $('#related-object-container').html('') + } else { + var parsed = syntaxHighlightJson(data) + $('#related-object-container').html(parsed) + } + } + + function fetchAndDisplayRelatedObject(type, uuid) { + var url = baseurl + '/analystData/getRelatedElement/' + type + '/' + uuid + $.ajax({ + type: "get", + url: url, + headers: { Accept: "application/json" }, + success: function (data) { + console.log(data); + displayRelatedObject(data) + }, + error: function (data, textStatus, errorThrown) { + showMessage('fail', textStatus + ": " + errorThrown); + } + }); + } + + $(document).ready(function() { + $('#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()) + } + }) + }) + + + \ No newline at end of file diff --git a/app/View/AnalystData/index.ctp b/app/View/AnalystData/index.ctp index a0baa8d0d..60bbfb338 100644 --- a/app/View/AnalystData/index.ctp +++ b/app/View/AnalystData/index.ctp @@ -62,7 +62,20 @@ ); } else if ($modelSelection === 'Relationship') { - + $fields = array_merge($fields, + [ + [ + 'name' => __('Related Object Type'), + 'sort' => $modelSelection . '.related_object_type', + 'data_path' => $modelSelection . '.related_object_type' + ], + [ + 'name' => __('Related Object UUID'), + 'sort' => $modelSelection . '.related_object_uuid', + 'data_path' => $modelSelection . '.related_object_uuid' + ], + ] + ); } echo $this->element('genericElements/IndexTable/scaffold', [ diff --git a/app/View/Elements/genericElements/Analyst_data/generic.ctp b/app/View/Elements/genericElements/Analyst_data/generic.ctp index 762fdbe10..a7a47cbc3 100644 --- a/app/View/Elements/genericElements/Analyst_data/generic.ctp +++ b/app/View/Elements/genericElements/Analyst_data/generic.ctp @@ -183,11 +183,7 @@ $notes2 = [ ], ]; -$notes = $analyst_data['notes'] ?? []; -$opinions = $analyst_data['opinions'] ?? []; -$relationships = $analyst_data['relationships'] ?? []; - -$related_objects = [ +$related_objects2 = [ 'Event' => [ 'f80a0db2-24bd-4148-929e-7c803ade7ca1' => [ 'uuid' => 'f80a0db2-24bd-4148-929e-7c803ade7ca1', @@ -240,38 +236,56 @@ $related_objects = [ ], ]; -$notes = array_merge($notes, $opinions); +$notes = $analyst_data['notes'] ?? []; +$opinions = $analyst_data['opinions'] ?? []; +$relationships = $analyst_data['relationships'] ?? []; +$related_objects = [ + 'Attribute' => [], + 'Event' => [], + 'Object' => [], + 'Organisation' => [], + 'GalaxyCluster' => [], + 'Galaxy' => [], + 'Note' => [], + 'Opinion' => [], + 'SharingGroup' => [], +]; +foreach ($relationships as $relationship) { + $related_objects[$relationship['object_type']][$relationship['related_object_uuid']] = $relationship['related_object'][$relationship['object_type']]; +} + +$notesOpinions = array_merge($notes, $opinions); if(!function_exists("countNotes")) { - function countNotes($notes) { - $notesTotalCount = count($notes); + function countNotes($notesOpinions) { + $notesTotalCount = count($notesOpinions); $notesCount = 0; $relationsCount = 0; - foreach ($notes as $note) { - if ($note['note_type'] == 2) { // relationship + foreach ($notesOpinions as $notesOpinion) { + if ($notesOpinion['note_type'] == 2) { // relationship $relationsCount += 1; } else { $notesCount += 1; } - if (!empty($note['Note'])) { - $nestedCounts = countNotes($note['Note']); + if (!empty($notesOpinion['Note'])) { + $nestedCounts = countNotes($notesOpinion['Note']); $notesTotalCount += $nestedCounts['total']; - $notesCount += $nestedCounts['notes']; + $notesCount += $nestedCounts['notesOpinions']; $relationsCount += $nestedCounts['relations']; } - if (!empty($note['Opinion'])) { - $nestedCounts = countNotes($note['Opinion']); + if (!empty($notesOpinion['Opinion'])) { + $nestedCounts = countNotes($notesOpinion['Opinion']); $notesTotalCount += $nestedCounts['total']; - $notesCount += $nestedCounts['notes']; + $notesCount += $nestedCounts['notesOpinions']; $relationsCount += $nestedCounts['relations']; } } - return ['total' => $notesTotalCount, 'notes' => $notesCount, 'relations' => $relationsCount]; + return ['total' => $notesTotalCount, 'notesOpinions' => $notesCount, 'relations' => $relationsCount]; } } -$counts = countNotes($notes); -$notesCount = $counts['notes']; -$relationshipsCount = $counts['relations']; +$counts = countNotes($notesOpinions); +$notesOpinionCount = $counts['notesOpinions']; +$relationshipsCount = count($relationships); ?> @@ -279,7 +293,7 @@ $relationshipsCount = $counts['relations']; - + @@ -288,7 +302,7 @@ $relationshipsCount = $counts['relations']; - - \ No newline at end of file +?> \ No newline at end of file diff --git a/app/View/Elements/genericElements/Analyst_data/thread.ctp b/app/View/Elements/genericElements/Analyst_data/thread.ctp new file mode 100644 index 000000000..45e0c896f --- /dev/null +++ b/app/View/Elements/genericElements/Analyst_data/thread.ctp @@ -0,0 +1,585 @@ +element('genericElements/assetLoader', [ + 'js' => ['doT', 'moment.min'], + ]); + + $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); +?> + + + + + + \ No newline at end of file From 6909e5feaf23cf9abce007da35a75ebb020eeedb Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:00:06 +0100 Subject: [PATCH 037/113] new: [singleView:sidePanels] Added new `html` side panel template to feed any HTML into the view --- .../Elements/genericElements/SidePanels/Templates/html.ctp | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/View/Elements/genericElements/SidePanels/Templates/html.ctp diff --git a/app/View/Elements/genericElements/SidePanels/Templates/html.ctp b/app/View/Elements/genericElements/SidePanels/Templates/html.ctp new file mode 100644 index 000000000..a55db6852 --- /dev/null +++ b/app/View/Elements/genericElements/SidePanels/Templates/html.ctp @@ -0,0 +1,3 @@ + From f534b22582cf9da09d2d941996a89880ea559d07 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:01:06 +0100 Subject: [PATCH 038/113] chg: [analyst-data:sideMenu] Added support of analyst-data in the side menu --- app/Controller/AnalystDataController.php | 3 ++- .../genericElements/SideMenu/side_menu.ctp | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index 77f015a0f..e135983ea 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -95,7 +95,7 @@ class AnalystDataController extends AppController public function view($type = 'Note', $id) { $this->__typeSelector($type); - $this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'edit')); + $this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'view')); $this->CRUD->view($id); if ($this->IndexFilter->isRest()) { return $this->restResponsePayload; @@ -104,6 +104,7 @@ class AnalystDataController extends AppController $this->loadModel('Event'); $this->_setViewElements(); $this->set('distributionLevels', $this->Event->distributionLevels); + $this->set('shortDist', $this->Event->shortDist); $this->render('view'); } diff --git a/app/View/Elements/genericElements/SideMenu/side_menu.ctp b/app/View/Elements/genericElements/SideMenu/side_menu.ctp index 50a632e05..5f2642737 100644 --- a/app/View/Elements/genericElements/SideMenu/side_menu.ctp +++ b/app/View/Elements/genericElements/SideMenu/side_menu.ctp @@ -1801,6 +1801,29 @@ $divider = '
  • '; } } break; + + case 'analyst_data': + echo $this->element('/genericElements/SideMenu/side_menu_link', array( + 'element_id' => 'index', + 'url' => '/analystData/index', + 'text' => __('List Analyst Data') + )); + 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; } ?> From 1975e38d8c9619c985e1ad128c3de28018138544 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:03:16 +0100 Subject: [PATCH 039/113] chg: [galaxyCluster] Added support of analyst-note in the UI --- app/Controller/GalaxyClustersController.php | 2 ++ app/Model/GalaxyCluster.php | 3 ++- app/View/GalaxyClusters/view.ctp | 22 ++++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/Controller/GalaxyClustersController.php b/app/Controller/GalaxyClustersController.php index eac9803e5..b72ba715a 100644 --- a/app/Controller/GalaxyClustersController.php +++ b/app/Controller/GalaxyClustersController.php @@ -173,6 +173,7 @@ class GalaxyClustersController extends AppController */ public function view($id) { + $this->GalaxyCluster->includeAnalystData = 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 +209,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')); } diff --git a/app/Model/GalaxyCluster.php b/app/Model/GalaxyCluster.php index 781d31033..c97e708f1 100644 --- a/app/Model/GalaxyCluster.php +++ b/app/Model/GalaxyCluster.php @@ -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]; diff --git a/app/View/GalaxyClusters/view.ctp b/app/View/GalaxyClusters/view.ctp index 79a969a30..2fa4f9be1 100755 --- a/app/View/GalaxyClusters/view.ctp +++ b/app/View/GalaxyClusters/view.ctp @@ -94,6 +94,9 @@ if (!empty($extendedByHtml)) { element('genericElements/viewMetaTable', array('table_data' => $table_data)); ?>
    +
    +
    +
    @@ -143,4 +146,21 @@ md.disable(['image']) var $md = $('.md'); $md.html(md.render($md.text())); -element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster')); +element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster')); ?> + + '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/Analyst_data/thread', $options); + +?> \ No newline at end of file From 9de54fa208be3c5663aa806325df6a56b9e7d348 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:12:49 +0100 Subject: [PATCH 040/113] fix: [analyst-data:view] Use correct model to access element property --- app/View/AnalystData/view.ctp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/View/AnalystData/view.ctp b/app/View/AnalystData/view.ctp index b9007767b..99502ef9d 100644 --- a/app/View/AnalystData/view.ctp +++ b/app/View/AnalystData/view.ctp @@ -16,15 +16,15 @@ echo $this->element( [ 'key' => __('Target Object'), 'type' => 'custom', - 'function' => function (array $row) use ($baseurl) { - $path = Inflector::pluralize(strtolower($row['Note']['object_type'])); + 'function' => function (array $row) use ($baseurl, $modelSelection) { + $path = Inflector::pluralize(strtolower($row[$modelSelection]['object_type'])); return sprintf( '%s: %s', - h($row['Note']['object_type']), + h($row[$modelSelection]['object_type']), h($baseurl), h($path), - h($row['Note']['object_uuid']), - h($row['Note']['object_uuid']) + h($row[$modelSelection]['object_uuid']), + h($row[$modelSelection]['object_uuid']) ); } From 90ae8739da92ef69db636df649cd6a7236b541d4 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:29:36 +0100 Subject: [PATCH 041/113] chg: [analyst-data:view] Display fields based on note model and slightly improved UI --- app/View/AnalystData/view.ctp | 173 +++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 65 deletions(-) diff --git a/app/View/AnalystData/view.ctp b/app/View/AnalystData/view.ctp index 99502ef9d..0cafb9729 100644 --- a/app/View/AnalystData/view.ctp +++ b/app/View/AnalystData/view.ctp @@ -1,73 +1,107 @@ __('ID'), + 'path' => $modelSelection . '.id' + ], + [ + 'key' => __('UUID'), + 'path' => $modelSelection . '.uuid' + ], + [ + '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( + '%s: %s', + 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_uuid', + 'pathName' => $modelSelection . '.orgc_uuid', + 'type' => 'model', + 'model' => 'organisations' + ], + [ + 'key' => __('Owner org'), + 'path' => $modelSelection . '.org_uuid', + 'pathName' => $modelSelection . '.org_uuid', + 'type' => 'model', + '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 . '.sharing_group_id', + 'type' => 'distribution' + ], +]; + +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' + ]; + +} 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( + '%s: %s', + 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' => __('Note view'), + 'title' => __('%s view', h($modelSelection)), 'data' => $data, - 'fields' => [ - [ - 'key' => __('ID'), - 'path' => $modelSelection . '.id' - ], - [ - 'key' => __('UUID'), - 'path' => $modelSelection . '.uuid' - ], - [ - 'key' => __('Target Object'), - 'type' => 'custom', - 'function' => function (array $row) use ($baseurl, $modelSelection) { - $path = Inflector::pluralize(strtolower($row[$modelSelection]['object_type'])); - return sprintf( - '%s: %s', - 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_uuid', - 'pathName' => $modelSelection . '.orgc_uuid', - 'type' => 'model', - 'model' => 'organisations' - ], - [ - 'key' => __('Owner org'), - 'path' => $modelSelection . '.org_uuid', - 'pathName' => $modelSelection . '.org_uuid', - 'type' => 'model', - '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 . '.sharing_group_id', - 'type' => 'distribution' - ], - [ - 'key' => __('Language'), - 'path' => $modelSelection . '.language' - ], - [ - 'key' => __('Note'), - 'path' => $modelSelection . '.note' - ] - ], + 'fields' => $fields, 'side_panels' => [ [ 'type' => 'html', @@ -94,3 +128,12 @@ if ($modelSelection == 'Note') { } echo $this->element('genericElements/Analyst_data/thread', $options); +?> + + + + \ No newline at end of file From e3b09cd5a5b44f6ae97c62baa317875d78446674 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:36:45 +0100 Subject: [PATCH 042/113] fix: [analyst-data:afterFind] Only rearrange key sharing-group key if they distribution exists --- app/Model/AnalystData.php | 18 ++++++++++-------- app/View/AnalystData/add.ctp | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/Model/AnalystData.php b/app/Model/AnalystData.php index 96b5ef2a6..3aefd343f 100644 --- a/app/Model/AnalystData.php +++ b/app/Model/AnalystData.php @@ -135,16 +135,18 @@ class AnalystData extends AppModel private function rearrangeSharingGroup(array $analystData, array $user): array { - if ($analystData[$this->alias]['distribution'] == 4) { - if (!isset($analystData['SharingGroup'])) { - $this->SharingGroup = ClassRegistry::init('SharingGroup'); - $sg = $this->SharingGroup->fetchSG($analystData[$this->alias]['sharing_group_id'], $user, true); - $analystData[$this->alias]['SharingGroup'] = $sg['SharingGroup']; + if (isset($analystData[$this->alias]['distribution'])) { + if ($analystData[$this->alias]['distribution'] == 4) { + if (!isset($analystData['SharingGroup'])) { + $this->SharingGroup = ClassRegistry::init('SharingGroup'); + $sg = $this->SharingGroup->fetchSG($analystData[$this->alias]['sharing_group_id'], $user, true); + $analystData[$this->alias]['SharingGroup'] = $sg['SharingGroup']; + } else { + $analystData[$this->alias]['SharingGroup'] = $analystData['SharingGroup']; + } } else { - $analystData[$this->alias]['SharingGroup'] = $analystData['SharingGroup']; + unset($analystData['SharingGroup']); } - } else { - unset($analystData['SharingGroup']); } return $analystData; } diff --git a/app/View/AnalystData/add.ctp b/app/View/AnalystData/add.ctp index bf761af30..e42764d3b 100644 --- a/app/View/AnalystData/add.ctp +++ b/app/View/AnalystData/add.ctp @@ -83,7 +83,7 @@ if ($modelSelection === 'Note') { 'field' => 'related_object_uuid', 'class' => 'span4', ], - sprintf('
    ', __('Related Object'), __('- No UUID provided -')) + sprintf('
    ', __('Related Object'), __('- No UUID provided -')) ] ); } From 5664a735e2ae56e17fd2ae79f139ce597c54a81c Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 09:45:51 +0100 Subject: [PATCH 043/113] chg: [analyst-data:ACL] Added ACL rules and fixed side-menu to support ACL --- app/Controller/AnalystDataController.php | 2 +- app/Controller/Component/ACLComponent.php | 8 ++++++++ .../genericElements/SideMenu/side_menu.ctp | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index e135983ea..3638d44e1 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -60,7 +60,7 @@ class AnalystDataController extends AppController if ($type == 'Relationship') { $this->set('existingRelations', $this->AnalystData->getExistingRelationships()); } - $this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'add')); + $this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'add_' . strtolower($type))); $this->render('add'); } diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 631b6b1e1..59336830f 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -19,6 +19,14 @@ class ACLComponent extends Component 'queryACL' => array(), 'restSearch' => array('*'), ), + 'analystData' => [ + 'add' => ['perm_add'], + 'delete' => ['perm_add'], + 'edit' => ['perm_add'], + 'getRelatedElement' => ['*'], + 'index' => ['*'], + 'view' => ['*'], + ], 'api' => [ 'rest' => ['perm_auth'], 'viewDeprecatedFunctionUse' => [], diff --git a/app/View/Elements/genericElements/SideMenu/side_menu.ctp b/app/View/Elements/genericElements/SideMenu/side_menu.ctp index 5f2642737..73e4c7819 100644 --- a/app/View/Elements/genericElements/SideMenu/side_menu.ctp +++ b/app/View/Elements/genericElements/SideMenu/side_menu.ctp @@ -1808,6 +1808,24 @@ $divider = '
  • '; 'url' => '/analystData/index', 'text' => __('List Analyst Data') )); + if ($this->Acl->canAccess('analyst_notes', '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( From caf55c3eecedf31994dcbf84cd392f5480ad14ba Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 10:13:58 +0100 Subject: [PATCH 044/113] chg: [analyst-data:event-report] Added support of analyst-data to event reports --- app/Controller/EventReportsController.php | 3 +++ app/Model/EventReport.php | 1 + .../IndexTable/Fields/shortUUIDWithNotes.ctp | 16 ++++++++++++++++ app/View/EventReports/ajax/indexForEvent.ctp | 11 +++++++++++ app/View/EventReports/index.ctp | 11 +++++++++++ app/View/EventReports/view.ctp | 11 +++++++++++ 6 files changed, 53 insertions(+) create mode 100644 app/View/Elements/genericElements/IndexTable/Fields/shortUUIDWithNotes.ctp diff --git a/app/Controller/EventReportsController.php b/app/Controller/EventReportsController.php index e73b1fd50..9e21e81d2 100644 --- a/app/Controller/EventReportsController.php +++ b/app/Controller/EventReportsController.php @@ -61,6 +61,7 @@ class EventReportsController extends AppController public function view($reportId, $ajax=false) { + $this->EventReport->includeAnalystData = true; $report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId); if ($this->_isRest()) { return $this->RestResponse->viewData($report, $this->response->type()); @@ -175,6 +176,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 +517,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()); } diff --git a/app/Model/EventReport.php b/app/Model/EventReport.php index cdce53419..618813f62 100644 --- a/app/Model/EventReport.php +++ b/app/Model/EventReport.php @@ -17,6 +17,7 @@ class EventReport extends AppModel 'change' => 'full' ), 'Regexp' => array('fields' => array('value')), + 'AnalystDataParent', ); public $validate = array( diff --git a/app/View/Elements/genericElements/IndexTable/Fields/shortUUIDWithNotes.ctp b/app/View/Elements/genericElements/IndexTable/Fields/shortUUIDWithNotes.ctp new file mode 100644 index 000000000..795ea0e62 --- /dev/null +++ b/app/View/Elements/genericElements/IndexTable/Fields/shortUUIDWithNotes.ctp @@ -0,0 +1,16 @@ +element('genericElements/shortUuidWithNotes', [ + 'uuid' => $uuid, + 'object_type' => $field['object_type'], + 'notes' => $notes, + 'opinions' => $opinions, + 'relationships' => $relationships, + ]); \ No newline at end of file diff --git a/app/View/EventReports/ajax/indexForEvent.ctp b/app/View/EventReports/ajax/indexForEvent.ctp index 2a92ef984..c6c4bf8fd 100644 --- a/app/View/EventReports/ajax/indexForEvent.ctp +++ b/app/View/EventReports/ajax/indexForEvent.ctp @@ -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', diff --git a/app/View/EventReports/index.ctp b/app/View/EventReports/index.ctp index 4eedf32bc..783180df8 100644 --- a/app/View/EventReports/index.ctp +++ b/app/View/EventReports/index.ctp @@ -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', diff --git a/app/View/EventReports/view.ctp b/app/View/EventReports/view.ctp index 3d2f38d31..87f3eae05 100644 --- a/app/View/EventReports/view.ctp +++ b/app/View/EventReports/view.ctp @@ -23,6 +23,17 @@ 'key' => __('Last update'), 'html' => $this->Time->time($report['EventReport']['timestamp']), ); + $table_data[] = array( + 'key' => __('Analyst Data'), + 'element' => 'genericElements/shortUuidWithNotes', + 'element_params' => [ + 'uuid' => $report['EventReport']['uuid'], + 'object_type' => 'EventReport', + 'notes' => $report['Note'] ?? [], + 'opinions' => $report['Opinion'] ?? [], + 'relationships' => $report['Relationship'] ?? [], + ], + ); if ($report['EventReport']['deleted']) { $table_data[] = array( 'key' => __('Deleted'), From 9feed62a5d0fe03dfd5c9001d540025661b37b90 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 10:14:27 +0100 Subject: [PATCH 045/113] fix: [analyst-data:ui-generic] Make sure to always show analyst-data --- app/View/Elements/genericElements/Analyst_data/generic.ctp | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/View/Elements/genericElements/Analyst_data/generic.ctp b/app/View/Elements/genericElements/Analyst_data/generic.ctp index 8dfac5c04..be41a365e 100644 --- a/app/View/Elements/genericElements/Analyst_data/generic.ctp +++ b/app/View/Elements/genericElements/Analyst_data/generic.ctp @@ -74,7 +74,6 @@ $(document).ready(function() { element('genericElements/Analyst_data/thread', [ 'seed' => $seed, 'notes' => $notes, @@ -84,5 +83,4 @@ if (!empty($notesOpinions) || !empty($relationshipsCount)) { 'object_uuid' => $object_uuid, 'shortDist' => $shortDist, ]); -} ?> \ No newline at end of file From d702535a76a0efc9b0ffb7e3fe134cdc82b7becb Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 30 Jan 2024 10:51:31 +0100 Subject: [PATCH 046/113] chg: [analyst-data:UI] Improved UI, better support of opinions in CRUD views and added single/index fields for opinion scale --- app/View/AnalystData/index.ctp | 8 ++- app/View/AnalystData/view.ctp | 5 ++ .../Analyst_data/opinion_scale.ctp | 55 +++++++++++++++++++ .../genericElements/Analyst_data/thread.ctp | 33 +---------- .../Form/Fields/opinionField.ctp | 25 ++------- .../IndexTable/Fields/opinion_scale.ctp | 6 ++ .../SingleViews/Fields/opinion_scaleField.ctp | 7 +++ app/webroot/css/analyst-data.css | 31 +++++++++++ 8 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 app/View/Elements/genericElements/Analyst_data/opinion_scale.ctp create mode 100644 app/View/Elements/genericElements/IndexTable/Fields/opinion_scale.ctp create mode 100644 app/View/Elements/genericElements/SingleViews/Fields/opinion_scaleField.ctp create mode 100644 app/webroot/css/analyst-data.css diff --git a/app/View/AnalystData/index.ctp b/app/View/AnalystData/index.ctp index 6e3486203..7cbdcd307 100644 --- a/app/View/AnalystData/index.ctp +++ b/app/View/AnalystData/index.ctp @@ -62,6 +62,11 @@ 'name' => __('Comment'), 'data_path' => $modelSelection . '.comment' ], + [ + 'name' => __('Opinion'), + 'data_path' => $modelSelection . '.opinion', + 'element' => 'opinion_scale', + ], ] ); @@ -130,7 +135,8 @@ [ 'url' => $baseurl . '/analystData/view/' . $modelSelection, 'url_params_data_paths' => [$modelSelection . '.id'], - 'icon' => 'eye' + 'icon' => 'eye', + 'dbclickAction' => true, ], [ 'onclick' => sprintf( diff --git a/app/View/AnalystData/view.ctp b/app/View/AnalystData/view.ctp index 0cafb9729..3327854cf 100644 --- a/app/View/AnalystData/view.ctp +++ b/app/View/AnalystData/view.ctp @@ -73,6 +73,11 @@ if ($modelSelection === 'Note') { 'key' => __('Comment'), 'path' => $modelSelection . '.comment' ]; + $fields[] = [ + 'key' => __('Opinion'), + 'path' => $modelSelection . '.opinion', + 'type' => 'opinion_scale', + ]; } else if ($modelSelection === 'Relationship') { $fields[] = [ diff --git a/app/View/Elements/genericElements/Analyst_data/opinion_scale.ctp b/app/View/Elements/genericElements/Analyst_data/opinion_scale.ctp new file mode 100644 index 000000000..fd72a6435 --- /dev/null +++ b/app/View/Elements/genericElements/Analyst_data/opinion_scale.ctp @@ -0,0 +1,55 @@ +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'); +?> + +
    +
    + +
    +
    +
    + + + + /100 + +
    + + \ No newline at end of file diff --git a/app/View/Elements/genericElements/Analyst_data/thread.ctp b/app/View/Elements/genericElements/Analyst_data/thread.ctp index 45e0c896f..5617c403a 100644 --- a/app/View/Elements/genericElements/Analyst_data/thread.ctp +++ b/app/View/Elements/genericElements/Analyst_data/thread.ctp @@ -1,6 +1,7 @@ element('genericElements/assetLoader', [ 'js' => ['doT', 'moment.min'], + 'css' => ['analyst-data',], ]); $URL_ADD = '/analystData/add/'; @@ -442,38 +443,6 @@ function filterNotes(clicked, filter) { transform: rotate(180deg); } - .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; - } - - Form->input($fieldData['field'], $params); + +echo $this->element('genericElements/assetLoader', [ + 'css' => ['analyst-data',], +]); ?>