> */ private array $templateCache = []; /** * @var array,reason:?string}>> */ private array $familyCache = []; /** * @var array,reason:?string}>> */ private array $templateDefinitionCache = []; public function __construct( private readonly GraphClientInterface $graphClient, ) {} /** * @param array $templateReference * @param array $graphOptions * @return array{success:bool,template_id:?string,template_reference:?array,reason:?string,warnings:array} */ public function resolveTemplateReference(Tenant $tenant, array $templateReference, array $graphOptions = []): array { $warnings = []; $templateId = $this->extractString($templateReference, ['templateId', 'TemplateId']); $templateFamily = $this->extractString($templateReference, ['templateFamily', 'TemplateFamily']); $templateDisplayName = $this->extractString($templateReference, ['templateDisplayName', 'TemplateDisplayName']); $templateDisplayVersion = $this->extractString($templateReference, ['templateDisplayVersion', 'TemplateDisplayVersion']); if ($templateId !== null) { $templateOutcome = $this->getTemplate($tenant, $templateId, $graphOptions); if ($templateOutcome['success']) { return [ 'success' => true, 'template_id' => $templateId, 'template_reference' => $templateReference, 'reason' => null, 'warnings' => $warnings, ]; } if ($templateFamily === null) { return [ 'success' => false, 'template_id' => null, 'template_reference' => null, 'reason' => $templateOutcome['reason'] ?? "Template '{$templateId}' is not available in the tenant.", 'warnings' => $warnings, ]; } } if ($templateFamily === null) { return [ 'success' => false, 'template_id' => null, 'template_reference' => null, 'reason' => 'Template reference is missing templateFamily and cannot be resolved.', 'warnings' => $warnings, ]; } $listOutcome = $this->listTemplatesByFamily($tenant, $templateFamily, $graphOptions); if (! $listOutcome['success']) { return [ 'success' => false, 'template_id' => null, 'template_reference' => null, 'reason' => $listOutcome['reason'] ?? "Unable to list templates for family '{$templateFamily}'.", 'warnings' => $warnings, ]; } $candidates = $this->chooseTemplateCandidate( templates: $listOutcome['templates'], templateDisplayName: $templateDisplayName, templateDisplayVersion: $templateDisplayVersion, ); if (count($candidates) !== 1) { $reason = count($candidates) === 0 ? "No templates found for family '{$templateFamily}'." : "Multiple templates found for family '{$templateFamily}' (cannot resolve automatically)."; return [ 'success' => false, 'template_id' => null, 'template_reference' => null, 'reason' => $reason, 'warnings' => $warnings, ]; } $candidate = $candidates[0]; $resolvedId = is_array($candidate) ? ($candidate['id'] ?? null) : null; if (! is_string($resolvedId) || $resolvedId === '') { return [ 'success' => false, 'template_id' => null, 'template_reference' => null, 'reason' => "Template candidate for family '{$templateFamily}' is missing an id.", 'warnings' => $warnings, ]; } if ($templateId !== null && $templateId !== $resolvedId) { $warnings[] = sprintf("TemplateId '%s' not found; mapped to '%s' via templateFamily.", $templateId, $resolvedId); } $templateReference['templateId'] = $resolvedId; if (! isset($templateReference['templateDisplayName']) && isset($candidate['displayName'])) { $templateReference['templateDisplayName'] = $candidate['displayName']; } if (! isset($templateReference['templateDisplayVersion']) && isset($candidate['displayVersion'])) { $templateReference['templateDisplayVersion'] = $candidate['displayVersion']; } return [ 'success' => true, 'template_id' => $resolvedId, 'template_reference' => $templateReference, 'reason' => null, 'warnings' => $warnings, ]; } /** * @param array $graphOptions * @return array{success:bool,template:?array,reason:?string} */ public function getTemplate(Tenant $tenant, string $templateId, array $graphOptions = []): array { $tenantKey = $this->tenantKey($tenant, $graphOptions); if (isset($this->templateCache[$tenantKey][$templateId])) { return $this->templateCache[$tenantKey][$templateId]; } $context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform'])); $path = sprintf('/deviceManagement/configurationPolicyTemplates/%s', urlencode($templateId)); $response = $this->graphClient->request('GET', $path, $context); if ($response->failed()) { return $this->templateCache[$tenantKey][$templateId] = [ 'success' => false, 'template' => null, 'reason' => $response->meta['error_message'] ?? 'Template lookup failed.', ]; } return $this->templateCache[$tenantKey][$templateId] = [ 'success' => true, 'template' => $response->data, 'reason' => null, ]; } /** * @param array $graphOptions * @return array{success:bool,templates:array,reason:?string} */ public function listTemplatesByFamily(Tenant $tenant, string $templateFamily, array $graphOptions = []): array { $tenantKey = $this->tenantKey($tenant, $graphOptions); $cacheKey = strtolower($templateFamily); if (isset($this->familyCache[$tenantKey][$cacheKey])) { return $this->familyCache[$tenantKey][$cacheKey]; } $escapedFamily = str_replace("'", "''", $templateFamily); $context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform']), [ 'query' => [ '$filter' => "templateFamily eq '{$escapedFamily}'", '$top' => 999, ], ]); $response = $this->graphClient->request('GET', '/deviceManagement/configurationPolicyTemplates', $context); if ($response->failed()) { return $this->familyCache[$tenantKey][$cacheKey] = [ 'success' => false, 'templates' => [], 'reason' => $response->meta['error_message'] ?? 'Template list failed.', ]; } $value = $response->data['value'] ?? []; $templates = is_array($value) ? array_values(array_filter($value, static fn ($item) => is_array($item))) : []; return $this->familyCache[$tenantKey][$cacheKey] = [ 'success' => true, 'templates' => $templates, 'reason' => null, ]; } /** * @param array $graphOptions * @return array{success:bool,definition_ids:array,reason:?string} */ public function fetchTemplateSettingDefinitionIds(Tenant $tenant, string $templateId, array $graphOptions = []): array { $tenantKey = $this->tenantKey($tenant, $graphOptions); if (isset($this->templateDefinitionCache[$tenantKey][$templateId])) { return $this->templateDefinitionCache[$tenantKey][$templateId]; } $context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform']), [ 'query' => [ '$expand' => 'settingDefinitions', '$top' => 999, ], ]); $path = sprintf('/deviceManagement/configurationPolicyTemplates/%s/settingTemplates', urlencode($templateId)); $response = $this->graphClient->request('GET', $path, $context); if ($response->failed()) { return $this->templateDefinitionCache[$tenantKey][$templateId] = [ 'success' => false, 'definition_ids' => [], 'reason' => $response->meta['error_message'] ?? 'Template definitions lookup failed.', ]; } $value = $response->data['value'] ?? []; $templates = is_array($value) ? $value : []; $definitionIds = []; foreach ($templates as $settingTemplate) { if (! is_array($settingTemplate)) { continue; } $definitions = $settingTemplate['settingDefinitions'] ?? null; if (! is_array($definitions)) { continue; } foreach ($definitions as $definition) { if (! is_array($definition)) { continue; } $id = $definition['id'] ?? null; if (is_string($id) && $id !== '') { $definitionIds[] = $id; } } } $definitionIds = array_values(array_unique($definitionIds)); return $this->templateDefinitionCache[$tenantKey][$templateId] = [ 'success' => true, 'definition_ids' => $definitionIds, 'reason' => null, ]; } /** * @param array $settings * @return array */ public function extractSettingDefinitionIds(array $settings): array { $ids = []; $walk = function (mixed $node) use (&$walk, &$ids): void { if (! is_array($node)) { return; } foreach ($node as $key => $value) { if (is_string($key) && strtolower($key) === 'settingdefinitionid' && is_string($value) && $value !== '') { $ids[] = $value; } $walk($value); } }; $walk($settings); return array_values(array_unique($ids)); } /** * @param array $templates * @return array */ private function chooseTemplateCandidate(array $templates, ?string $templateDisplayName, ?string $templateDisplayVersion): array { $candidates = $templates; $active = array_values(array_filter($candidates, static function (array $template): bool { $state = $template['lifecycleState'] ?? null; return is_string($state) && strtolower($state) === 'active'; })); if ($active !== []) { $candidates = $active; } if ($templateDisplayVersion !== null) { $byVersion = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayVersion): bool { $version = $template['displayVersion'] ?? null; return is_string($version) && $version === $templateDisplayVersion; })); if ($byVersion !== []) { $candidates = $byVersion; } } if ($templateDisplayName !== null) { $byName = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayName): bool { $name = $template['displayName'] ?? null; return is_string($name) && $name === $templateDisplayName; })); if ($byName !== []) { $candidates = $byName; } } return $candidates; } /** * @param array $payload * @param array $keys */ private function extractString(array $payload, array $keys): ?string { $normalized = array_map('strtolower', $keys); foreach ($payload as $key => $value) { if (! is_string($key) || ! in_array(strtolower($key), $normalized, true)) { continue; } if (is_string($value) && trim($value) !== '') { return $value; } } return null; } /** * @param array $graphOptions */ private function tenantKey(Tenant $tenant, array $graphOptions): string { $tenantId = $graphOptions['tenant'] ?? $tenant->graphTenantId() ?? (string) $tenant->getKey(); return (string) $tenantId; } }