Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 63 additions & 43 deletions lib/Service/ArchiMateImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1955,6 +1955,48 @@ private function getCurrentOrganisation(): string
}
}//end getCurrentOrganisation()

/**
* Read a configured id, failing closed on the empty default.
*
* The legacy fallback used to read every id with `''` as its default, so
* an unconfigured instance produced a config array full of empty STRINGS.
* Its consumers guard with `=== null` — `getAmefRegisterId()` and
* `getAmefSchemaIdForType()` reject them with `is_numeric()` + `> 0` — and `'' === null` is
* false. Today nothing reaches a query only because that fallback writes
* PLURAL key names (`views_schema`) while every consumer reads SINGULAR
* ones (`view_schema`), so the lookups miss and fall back to `null`. That
* is an accident of naming, not a defence: adding the singular keys — the
* obvious "cleanup" — would send `register => ''` straight into
* `searchObjects()` as an UNPINNED query, and an unpinned query returns
* rows, which reads exactly like a correct result.
*
* Returning null instead of `''` makes `?? null` downstream yield null,
* which is what every consumer already checks for, and the warning names
* the missing key so a misconfigured import stops reporting "0 objects"
* with no explanation.
*
* @param string $key The app-config key holding the id.
*
* @return string|null The configured id, or null when it is unset.
*
* @spec openspec/specs/archimate-import/spec.md
*/
private function resolveConfiguredId(string $key): ?string
{
$value = $this->config->getValueString('softwarecatalog', $key, '');
if (trim($value) === '') {
$this->logger->warning(
'ArchiMate configuration is incomplete — this id is not configured, so it is omitted rather than passed on as an empty string',
['key' => $key]
);

return null;
}

return $value;

}//end resolveConfiguredId()

/**
* Get AMEF configuration from app config
*
Expand All @@ -1971,49 +2013,27 @@ public function getAmefConfig(): array
$decoded = json_decode($config, true);

if (is_array($decoded) === false) {
// Fallback to individual config values for backward compatibility.
$decoded = [
'register_id' => $this->config->getValueString(
'softwarecatalog',
'amef_register',
''
),
'model_schema_id' => $this->config->getValueString(
'softwarecatalog',
'amef_model_schema',
''
),
'elements_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_elements_schema',
''
),
'relationships_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_relationships_schema',
''
),
'views_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_views_schema',
''
),
'organizations_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_organizations_schema',
''
),
'folders_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_folders_schema',
''
),
'property_definitions_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_property_definitions_schema',
''
),
];
// Fallback to individual config values for backward
// compatibility. Every id is read through
// resolveConfiguredId(), which guards the empty default at the
// point of the read and omits the key entirely when it is
// unset — so `?? null` downstream yields null, which is what
// the consumers already check for.
$decoded = array_filter(
[
'register_id' => $this->resolveConfiguredId(key: 'amef_register'),
'model_schema_id' => $this->resolveConfiguredId(key: 'amef_model_schema'),
'elements_schema' => $this->resolveConfiguredId(key: 'amef_elements_schema'),
'relationships_schema' => $this->resolveConfiguredId(key: 'amef_relationships_schema'),
'views_schema' => $this->resolveConfiguredId(key: 'amef_views_schema'),
'organizations_schema' => $this->resolveConfiguredId(key: 'amef_organizations_schema'),
'folders_schema' => $this->resolveConfiguredId(key: 'amef_folders_schema'),
'property_definitions_schema' => $this->resolveConfiguredId(key: 'amef_property_definitions_schema'),
],
static function ($value) {
return $value !== null;
}
);
}//end if

return $decoded;
Expand Down
102 changes: 63 additions & 39 deletions lib/Service/ArchiMateService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,48 @@ private function createTempFile(string $content): string
return $tempFile;
}//end createTempFile()

/**
* Read a configured id, failing closed on the empty default.
*
* The legacy fallback used to read every id with `''` as its default, so
* an unconfigured instance produced a config array full of empty STRINGS.
* Its consumers guard with `=== null` — `ViewService::getViews()` and
* `getView()` throw only when a value `=== null` — and `'' === null` is
* false. Today nothing reaches a query only because that fallback writes
* PLURAL key names (`views_schema`) while every consumer reads SINGULAR
* ones (`view_schema`), so the lookups miss and fall back to `null`. That
* is an accident of naming, not a defence: adding the singular keys — the
* obvious "cleanup" — would send `register => ''` straight into
* `searchObjects()` as an UNPINNED query, and an unpinned query returns
* rows, which reads exactly like a correct result.
*
* Returning null instead of `''` makes `?? null` downstream yield null,
* which is what every consumer already checks for, and the warning names
* the missing key so a misconfigured import stops reporting "0 objects"
* with no explanation.
*
* @param string $key The app-config key holding the id.
*
* @return string|null The configured id, or null when it is unset.
*
* @spec openspec/specs/archimate-import/spec.md
*/
private function resolveConfiguredId(string $key): ?string
{
$value = $this->config->getValueString('softwarecatalog', $key, '');
if (trim($value) === '') {
$this->logger->warning(
'ArchiMate configuration is incomplete — this id is not configured, so it is omitted rather than passed on as an empty string',
['key' => $key]
);

return null;
}

return $value;

}//end resolveConfiguredId()

/**
* Get AMEF configuration from app config
*
Expand All @@ -1637,45 +1679,27 @@ public function getAmefConfig(): array
$decoded = json_decode($config, true);

if (is_array($decoded) === false) {
// Fallback to individual config values for backward compatibility.
$decoded = [
'register_id' => $this->config->getValueString('softwarecatalog', 'amef_register', ''),
'model_schema_id' => $this->config->getValueString(
'softwarecatalog',
'amef_model_schema',
''
),
'elements_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_elements_schema',
''
),
'relationships_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_relationships_schema',
''
),
'views_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_views_schema',
''
),
'organizations_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_organizations_schema',
''
),
'folders_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_folders_schema',
''
),
'property_definitions_schema' => $this->config->getValueString(
'softwarecatalog',
'amef_property_definitions_schema',
''
),
];
// Fallback to individual config values for backward
// compatibility. Every id is read through
// resolveConfiguredId(), which guards the empty default at the
// point of the read and omits the key entirely when it is
// unset — so `?? null` downstream yields null, which is what
// the consumers already check for.
$decoded = array_filter(
[
'register_id' => $this->resolveConfiguredId(key: 'amef_register'),
'model_schema_id' => $this->resolveConfiguredId(key: 'amef_model_schema'),
'elements_schema' => $this->resolveConfiguredId(key: 'amef_elements_schema'),
'relationships_schema' => $this->resolveConfiguredId(key: 'amef_relationships_schema'),
'views_schema' => $this->resolveConfiguredId(key: 'amef_views_schema'),
'organizations_schema' => $this->resolveConfiguredId(key: 'amef_organizations_schema'),
'folders_schema' => $this->resolveConfiguredId(key: 'amef_folders_schema'),
'property_definitions_schema' => $this->resolveConfiguredId(key: 'amef_property_definitions_schema'),
],
static function ($value) {
return $value !== null;
}
);
}//end if

return $decoded;
Expand Down
18 changes: 17 additions & 1 deletion lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,22 @@ private function getModulesData(): array
$amefConfig = $this->settingsService->getAmefConfig();
$registerId = $amefConfig['register_id'] ?? null;

// Fail closed on an unconfigured register. This used to be the one
// read of `register_id` with no guard at all: the loop below only
// checks the SCHEMA, so an empty register would have been pinned
// into `@self` and OpenRegister asked for "any register" — an
// unpinned query returns rows, which reads exactly like a correct
// result. `empty()` rather than `=== null` because the legacy
// config fallback resolves unset ids to `''`, and `'' === null` is
// false.
if (empty($registerId) === true) {
$this->logger->warning(
'ViewService: AMEF register is not configured; skipping the module lookup rather than issuing an unpinned query'
);

return [];
}

// Modules could be in various schemas - check common ones.
$moduleSchemas = [
$amefConfig['module_schema'] ?? null,
Expand All @@ -721,7 +737,7 @@ private function getModulesData(): array
$allModules = [];

foreach ($moduleSchemas as $schemaId) {
if ($schemaId === null) {
if (empty($schemaId) === true) {
continue;
}

Expand Down
Loading
Loading