From 6bd7e5550aaab1d78c2e776a0fa26b7100da2448 Mon Sep 17 00:00:00 2001 From: Ruslan Baidan Date: Fri, 8 May 2026 19:54:19 +0200 Subject: [PATCH 1/4] Added the risks sources support. --- config/module.config.php | 15 +++ src/Controller/ApiRiskSourcesController.php | 125 ++++++++++++++++++++ view/layout/layout.phtml | 1 + 3 files changed, 141 insertions(+) create mode 100644 src/Controller/ApiRiskSourcesController.php diff --git a/config/module.config.php b/config/module.config.php index ca6f32c..e6502e0 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -316,6 +316,19 @@ ], ], + 'monarc_api_risk_sources' => [ + 'type' => 'segment', + 'options' => [ + 'route' => '/api/risk-sources[/:id]', + 'constraints' => [ + 'id' => '[0-9]+', + ], + 'defaults' => [ + 'controller' => Controller\ApiRiskSourcesController::class, + ], + ], + ], + 'monarc_api_measures' => [ 'type' => 'segment', 'options' => [ @@ -856,6 +869,7 @@ Controller\ApiQuestionsController::class => AutowireFactory::class, Controller\ApiQuestionsChoicesController::class => AutowireFactory::class, Controller\ApiReferentialsController::class => AutowireFactory::class, + Controller\ApiRiskSourcesController::class => AutowireFactory::class, Controller\ApiRolfRisksController::class => AutowireFactory::class, Controller\ApiRolfTagsController::class => AutowireFactory::class, Controller\ApiAnrRisksController::class => AutowireFactory::class, @@ -989,6 +1003,7 @@ 'monarc_api_anr/library_category', 'monarc_api_anr/objects', 'monarc_api_referentials', + 'monarc_api_risk_sources', 'monarc_api_measures', 'monarc_api_measuremeasure', 'monarc_api_questions', diff --git a/src/Controller/ApiRiskSourcesController.php b/src/Controller/ApiRiskSourcesController.php new file mode 100644 index 0000000..5be5015 --- /dev/null +++ b/src/Controller/ApiRiskSourcesController.php @@ -0,0 +1,125 @@ +getFormattedInputParams($this->getRiskSourcesInputFormatter); + $riskSources = $this->riskSourceService->getList($formattedParams); + $displayLabelsByRiskSourceId = $this->riskSourceService->getDisplayLabelsByRiskSourceId($riskSources); + + return $this->getPreparedJsonResponse([ + 'count' => $this->riskSourceService->getCount($formattedParams), + 'riskSources' => array_map( + fn (RiskSource $riskSource): array => $this->prepareRiskSourceData( + $riskSource, + false, + $displayLabelsByRiskSourceId[$riskSource->getId()] ?? $riskSource->getLabel() + ), + $riskSources + ), + ]); + } + + public function get($id) + { + return $this->getPreparedJsonResponse( + $this->prepareRiskSourceData($this->riskSourceService->get((int)$id), true) + ); + } + + public function create($data) + { + $this->validatePostParams($this->postRiskSourceDataInputValidator, $data); + $riskSourceData = $this->prepareRiskSourcePayload($data, $this->postRiskSourceDataInputValidator->getValidData()); + + return $this->getSuccessfulJsonResponse($this->prepareRiskSourceData( + $this->riskSourceService->create($riskSourceData), + true + )); + } + + public function update($id, $data) + { + $this->validatePostParams($this->patchRiskSourceDataInputValidator, $data); + $riskSourceData = $this->prepareRiskSourcePayload($data, $this->patchRiskSourceDataInputValidator->getValidData()); + + return $this->getSuccessfulJsonResponse($this->prepareRiskSourceData( + $this->riskSourceService->update((int)$id, $riskSourceData), + true + )); + } + + public function delete($id) + { + $this->riskSourceService->delete((int)$id); + + return $this->getSuccessfulJsonResponse(); + } + + private function prepareRiskSourceData( + RiskSource $riskSource, + bool $includeLabels = false, + ?string $displayLabel = null + ): array + { + $riskSourceData = [ + 'id' => $riskSource->getId(), + 'label' => $displayLabel ?? $this->riskSourceService->getDisplayLabel($riskSource), + 'isDefault' => $riskSource->isDefault(), + 'isActive' => $riskSource->isActive(), + ]; + + if ($includeLabels) { + $riskSourceData['labels'] = $this->riskSourceService->getLabels($riskSource); + } + + return $riskSourceData; + } + + private function prepareRiskSourcePayload(array $sourceData, array $validatedData): array + { + if (!isset($sourceData['labels']) || !is_array($sourceData['labels'])) { + return $validatedData; + } + + $labels = []; + foreach ($sourceData['labels'] as $languageCode => $label) { + $trimmedLabel = trim((string)$label); + if ($trimmedLabel !== '') { + $labels[(string)$languageCode] = $trimmedLabel; + } + } + + if ($labels !== []) { + $validatedData['labels'] = $labels; + } + + return $validatedData; + } +} diff --git a/view/layout/layout.phtml b/view/layout/layout.phtml index c077bb6..a0e7265 100644 --- a/view/layout/layout.phtml +++ b/view/layout/layout.phtml @@ -45,6 +45,7 @@ ->appendFile('js/anr/ThreatService.js') ->appendFile('js/anr/VulnService.js') ->appendFile('js/anr/RiskService.js') + ->appendFile('js/anr/RiskSourceService.js') ->appendFile('js/anr/AmvService.js') ->appendFile('js/anr/CategoryService.js') ->appendFile('js/anr/ToolsAnrService.js') From af4adb0324b66863aecf6ff9d352ac47b30203cb Mon Sep 17 00:00:00 2001 From: Ruslan Baidan Date: Wed, 13 May 2026 17:14:01 +0200 Subject: [PATCH 2/4] Added the reassessment trigger criteria functionality. --- config/module.config.php | 14 ++ .../ApiReassessmentTriggersController.php | 162 ++++++++++++++++++ view/layout/layout.phtml | 2 + 3 files changed, 178 insertions(+) create mode 100644 src/Controller/ApiReassessmentTriggersController.php diff --git a/config/module.config.php b/config/module.config.php index e6502e0..cebe189 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -328,6 +328,18 @@ ], ], ], + 'monarc_api_reassessment_triggers' => [ + 'type' => 'segment', + 'options' => [ + 'route' => '/api/reassessment-triggers[/:id]', + 'constraints' => [ + 'id' => '[0-9]+', + ], + 'defaults' => [ + 'controller' => Controller\ApiReassessmentTriggersController::class, + ], + ], + ], 'monarc_api_measures' => [ 'type' => 'segment', @@ -868,6 +880,7 @@ Controller\ApiOperationalRisksScalesCommentsController::class => AutowireFactory::class, Controller\ApiQuestionsController::class => AutowireFactory::class, Controller\ApiQuestionsChoicesController::class => AutowireFactory::class, + Controller\ApiReassessmentTriggersController::class => AutowireFactory::class, Controller\ApiReferentialsController::class => AutowireFactory::class, Controller\ApiRiskSourcesController::class => AutowireFactory::class, Controller\ApiRolfRisksController::class => AutowireFactory::class, @@ -1002,6 +1015,7 @@ 'monarc_api_anr/library', 'monarc_api_anr/library_category', 'monarc_api_anr/objects', + 'monarc_api_reassessment_triggers', 'monarc_api_referentials', 'monarc_api_risk_sources', 'monarc_api_measures', diff --git a/src/Controller/ApiReassessmentTriggersController.php b/src/Controller/ApiReassessmentTriggersController.php new file mode 100644 index 0000000..669e5a6 --- /dev/null +++ b/src/Controller/ApiReassessmentTriggersController.php @@ -0,0 +1,162 @@ +getFormattedInputParams($this->getReassessmentTriggersInputFormatter); + $reassessmentTriggers = $this->reassessmentTriggerService->getList($formattedParams); + + return $this->getPreparedJsonResponse([ + 'count' => $this->reassessmentTriggerService->getCount($formattedParams), + 'reassessmentTriggers' => array_map( + fn (ReassessmentTrigger $reassessmentTrigger): array => $this->prepareReassessmentTriggerData( + $reassessmentTrigger, + false + ), + $reassessmentTriggers + ), + ]); + } + + public function get($id) + { + return $this->getPreparedJsonResponse( + $this->prepareReassessmentTriggerData($this->reassessmentTriggerService->get((int)$id), true) + ); + } + + public function create($data) + { + $payload = $this->prepareReassessmentTriggerPayload($data); + $this->validatePostParams($this->postReassessmentTriggerDataInputValidator, $payload); + + return $this->getSuccessfulJsonResponse($this->prepareReassessmentTriggerData( + $this->reassessmentTriggerService->create($payload), + true + )); + } + + public function update($id, $data) + { + $payload = $this->prepareReassessmentTriggerPayload($data); + $this->validatePostParams($this->patchReassessmentTriggerDataInputValidator, $payload); + + return $this->getSuccessfulJsonResponse($this->prepareReassessmentTriggerData( + $this->reassessmentTriggerService->update((int)$id, $payload), + true + )); + } + + public function delete($id) + { + $this->reassessmentTriggerService->delete((int)$id); + + return $this->getSuccessfulJsonResponse(); + } + + private function prepareReassessmentTriggerData( + ReassessmentTrigger $reassessmentTrigger, + bool $includeTranslations + ): array { + $reassessmentTriggerData = [ + 'id' => $reassessmentTrigger->getId(), + 'triggerType' => $this->reassessmentTriggerService->getDisplayTriggerType($reassessmentTrigger), + 'description' => $this->reassessmentTriggerService->getDisplayDescription($reassessmentTrigger), + 'isActive' => $reassessmentTrigger->isActive(), + 'position' => $reassessmentTrigger->getPosition(), + ]; + + if ($includeTranslations) { + $reassessmentTriggerData['triggerTypes'] = $this->reassessmentTriggerService + ->getTriggerTypes($reassessmentTrigger); + $reassessmentTriggerData['descriptions'] = $this->reassessmentTriggerService + ->getDescriptions($reassessmentTrigger); + } + + return $reassessmentTriggerData; + } + + private function prepareReassessmentTriggerPayload(array $sourceData): array + { + $payload = []; + if (array_key_exists('id', $sourceData)) { + $payload['id'] = (int)$sourceData['id']; + } + if (array_key_exists('triggerType', $sourceData)) { + $payload['triggerType'] = trim((string)$sourceData['triggerType']); + } + if (array_key_exists('description', $sourceData)) { + $payload['description'] = trim((string)$sourceData['description']); + } + if (array_key_exists('isActive', $sourceData)) { + $payload['isActive'] = $sourceData['isActive']; + } + if (array_key_exists('position', $sourceData)) { + $payload['position'] = $sourceData['position']; + } + + $triggerTypes = $this->normalizeTranslations($sourceData['triggerTypes'] ?? null); + if ($triggerTypes !== []) { + $payload['triggerTypes'] = $triggerTypes; + if (empty($payload['triggerType'])) { + $payload['triggerType'] = (string)reset($triggerTypes); + } + } + + $descriptions = $this->normalizeTranslations($sourceData['descriptions'] ?? null); + if ($descriptions !== []) { + $payload['descriptions'] = $descriptions; + if (empty($payload['description'])) { + $payload['description'] = (string)reset($descriptions); + } + } + + return $payload; + } + + /** + * @return array + */ + private function normalizeTranslations(mixed $translations): array + { + if (!is_array($translations)) { + return []; + } + + $normalizedTranslations = []; + foreach ($translations as $languageCode => $value) { + $trimmedValue = trim((string)$value); + if ($trimmedValue !== '') { + $normalizedTranslations[(string)$languageCode] = $trimmedValue; + } + } + + return $normalizedTranslations; + } +} diff --git a/view/layout/layout.phtml b/view/layout/layout.phtml index a0e7265..682403c 100644 --- a/view/layout/layout.phtml +++ b/view/layout/layout.phtml @@ -46,6 +46,7 @@ ->appendFile('js/anr/VulnService.js') ->appendFile('js/anr/RiskService.js') ->appendFile('js/anr/RiskSourceService.js') + ->appendFile('js/anr/ReassessmentTriggerService.js') ->appendFile('js/anr/AmvService.js') ->appendFile('js/anr/CategoryService.js') ->appendFile('js/anr/ToolsAnrService.js') @@ -73,6 +74,7 @@ ->appendFile('js/kb/rolf/BackofficeKbOpRiskCtrl.js') // KB Models ->appendFile('js/kb/models/BackofficeKbModelsCtrl.js') + ->appendFile('js/kb/reassessment/BackofficeKbReassessmentTriggerCtrl.js') // Other KB sections ->appendFile('js/kb/BackofficeQuestionsCtrl.js') ->appendFile('js/kb/BackofficeAnalysisGuidesCtrl.js') From 91a1f5059a55af1570388a7d8cc72f8ed1c85b98 Mon Sep 17 00:00:00 2001 From: Ruslan Baidan Date: Mon, 18 May 2026 18:41:35 +0200 Subject: [PATCH 3/4] Corrected the multi-languages fields saving. --- .../ApiAnrInstancesRisksController.php | 4 + .../ApiReassessmentTriggersController.php | 78 ++++--------------- 2 files changed, 18 insertions(+), 64 deletions(-) diff --git a/src/Controller/ApiAnrInstancesRisksController.php b/src/Controller/ApiAnrInstancesRisksController.php index f127789..029454b 100755 --- a/src/Controller/ApiAnrInstancesRisksController.php +++ b/src/Controller/ApiAnrInstancesRisksController.php @@ -42,6 +42,10 @@ public function update($id, $data) return $this->getPreparedJsonResponse([ 'id' => $instanceRisk->getId(), + 'riskSourceId' => $instanceRisk->getRiskSource()?->getId(), + 'riskSourceLabel' => $instanceRisk->getRiskSource()?->getLabel() ?? '', + 'lastReviewDate' => $instanceRisk->getLastReviewDate()?->format('Y-m-d'), + 'reviewFrequency' => $instanceRisk->getReviewFrequency(), 'threatRate' => $instanceRisk->getThreatRate(), 'vulnerabilityRate' => $instanceRisk->getVulnerabilityRate(), 'reductionAmount' => $instanceRisk->getReductionAmount(), diff --git a/src/Controller/ApiReassessmentTriggersController.php b/src/Controller/ApiReassessmentTriggersController.php index 669e5a6..7e26b0b 100644 --- a/src/Controller/ApiReassessmentTriggersController.php +++ b/src/Controller/ApiReassessmentTriggersController.php @@ -53,22 +53,25 @@ public function get($id) public function create($data) { - $payload = $this->prepareReassessmentTriggerPayload($data); - $this->validatePostParams($this->postReassessmentTriggerDataInputValidator, $payload); + $this->validatePostParams($this->postReassessmentTriggerDataInputValidator, $data); return $this->getSuccessfulJsonResponse($this->prepareReassessmentTriggerData( - $this->reassessmentTriggerService->create($payload), + $this->reassessmentTriggerService->create( + $this->postReassessmentTriggerDataInputValidator->getValidData() + ), true )); } public function update($id, $data) { - $payload = $this->prepareReassessmentTriggerPayload($data); - $this->validatePostParams($this->patchReassessmentTriggerDataInputValidator, $payload); + $this->validatePostParams($this->patchReassessmentTriggerDataInputValidator, $data); return $this->getSuccessfulJsonResponse($this->prepareReassessmentTriggerData( - $this->reassessmentTriggerService->update((int)$id, $payload), + $this->reassessmentTriggerService->update( + (int)$id, + $this->patchReassessmentTriggerDataInputValidator->getValidData() + ), true )); } @@ -88,6 +91,9 @@ private function prepareReassessmentTriggerData( 'id' => $reassessmentTrigger->getId(), 'triggerType' => $this->reassessmentTriggerService->getDisplayTriggerType($reassessmentTrigger), 'description' => $this->reassessmentTriggerService->getDisplayDescription($reassessmentTrigger), + 'monitoringApproach' => $this->reassessmentTriggerService->getDisplayMonitoringApproach( + $reassessmentTrigger + ), 'isActive' => $reassessmentTrigger->isActive(), 'position' => $reassessmentTrigger->getPosition(), ]; @@ -97,66 +103,10 @@ private function prepareReassessmentTriggerData( ->getTriggerTypes($reassessmentTrigger); $reassessmentTriggerData['descriptions'] = $this->reassessmentTriggerService ->getDescriptions($reassessmentTrigger); + $reassessmentTriggerData['monitoringApproaches'] = $this->reassessmentTriggerService + ->getMonitoringApproaches($reassessmentTrigger); } return $reassessmentTriggerData; } - - private function prepareReassessmentTriggerPayload(array $sourceData): array - { - $payload = []; - if (array_key_exists('id', $sourceData)) { - $payload['id'] = (int)$sourceData['id']; - } - if (array_key_exists('triggerType', $sourceData)) { - $payload['triggerType'] = trim((string)$sourceData['triggerType']); - } - if (array_key_exists('description', $sourceData)) { - $payload['description'] = trim((string)$sourceData['description']); - } - if (array_key_exists('isActive', $sourceData)) { - $payload['isActive'] = $sourceData['isActive']; - } - if (array_key_exists('position', $sourceData)) { - $payload['position'] = $sourceData['position']; - } - - $triggerTypes = $this->normalizeTranslations($sourceData['triggerTypes'] ?? null); - if ($triggerTypes !== []) { - $payload['triggerTypes'] = $triggerTypes; - if (empty($payload['triggerType'])) { - $payload['triggerType'] = (string)reset($triggerTypes); - } - } - - $descriptions = $this->normalizeTranslations($sourceData['descriptions'] ?? null); - if ($descriptions !== []) { - $payload['descriptions'] = $descriptions; - if (empty($payload['description'])) { - $payload['description'] = (string)reset($descriptions); - } - } - - return $payload; - } - - /** - * @return array - */ - private function normalizeTranslations(mixed $translations): array - { - if (!is_array($translations)) { - return []; - } - - $normalizedTranslations = []; - foreach ($translations as $languageCode => $value) { - $trimmedValue = trim((string)$value); - if ($trimmedValue !== '') { - $normalizedTranslations[(string)$languageCode] = $trimmedValue; - } - } - - return $normalizedTranslations; - } } From d7a9011c12383a46c8a20e9da53d63e98ee91734 Mon Sep 17 00:00:00 2001 From: Ruslan Baidan Date: Thu, 28 May 2026 15:03:18 +0200 Subject: [PATCH 4/4] Removed the unused response keys and added the missing service (just to avoid an error). --- src/Controller/ApiAnrInstancesRisksController.php | 4 ---- view/layout/layout.phtml | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Controller/ApiAnrInstancesRisksController.php b/src/Controller/ApiAnrInstancesRisksController.php index 029454b..f127789 100755 --- a/src/Controller/ApiAnrInstancesRisksController.php +++ b/src/Controller/ApiAnrInstancesRisksController.php @@ -42,10 +42,6 @@ public function update($id, $data) return $this->getPreparedJsonResponse([ 'id' => $instanceRisk->getId(), - 'riskSourceId' => $instanceRisk->getRiskSource()?->getId(), - 'riskSourceLabel' => $instanceRisk->getRiskSource()?->getLabel() ?? '', - 'lastReviewDate' => $instanceRisk->getLastReviewDate()?->format('Y-m-d'), - 'reviewFrequency' => $instanceRisk->getReviewFrequency(), 'threatRate' => $instanceRisk->getThreatRate(), 'vulnerabilityRate' => $instanceRisk->getVulnerabilityRate(), 'reductionAmount' => $instanceRisk->getReductionAmount(), diff --git a/view/layout/layout.phtml b/view/layout/layout.phtml index 682403c..73a0d23 100644 --- a/view/layout/layout.phtml +++ b/view/layout/layout.phtml @@ -46,6 +46,7 @@ ->appendFile('js/anr/VulnService.js') ->appendFile('js/anr/RiskService.js') ->appendFile('js/anr/RiskSourceService.js') + ->appendFile('js/anr/InterestedPartyService.js') ->appendFile('js/anr/ReassessmentTriggerService.js') ->appendFile('js/anr/AmvService.js') ->appendFile('js/anr/CategoryService.js')