$definitionIds * @return array Map of definitionId => metadata */ public function resolve(array $definitionIds): array { if (empty($definitionIds)) { return []; } $definitions = []; $missingIds = []; // Step 1: Check memory cache foreach ($definitionIds as $id) { $cached = Cache::get(self::MEMORY_CACHE_PREFIX.$id); if ($cached) { $definitions[$id] = $cached; } else { $missingIds[] = $id; } } if (empty($missingIds)) { return $definitions; } // Step 2: Check database cache $dbDefinitions = SettingsCatalogDefinition::findByDefinitionIds($missingIds); foreach ($dbDefinitions as $definitionId => $dbDef) { $metadata = $this->transformToMetadata($dbDef); $definitions[$definitionId] = $metadata; // Cache in memory Cache::put( self::MEMORY_CACHE_PREFIX.$definitionId, $metadata, now()->addSeconds(self::CACHE_TTL) ); $missingIds = array_diff($missingIds, [$definitionId]); } if (empty($missingIds)) { return $definitions; } // Step 3: Fetch from Graph API try { $graphDefinitions = $this->fetchFromGraph($missingIds); foreach ($graphDefinitions as $definitionId => $metadata) { // Store in database $this->storeInDatabase($definitionId, $metadata); // Cache in memory Cache::put( self::MEMORY_CACHE_PREFIX.$definitionId, $metadata, now()->addSeconds(self::CACHE_TTL) ); $definitions[$definitionId] = $metadata; } } catch (\Exception $e) { Log::error('Failed to fetch setting definitions from Graph API', [ 'definition_ids' => $missingIds, 'error' => $e->getMessage(), ]); } // Step 4: Fallback for still missing definitions foreach ($missingIds as $id) { if (! isset($definitions[$id])) { $fallback = $this->getFallbackMetadata($id); $definitions[$id] = $fallback; // Cache fallback in memory too (short TTL since it's not real data) Cache::put( self::MEMORY_CACHE_PREFIX.$id, $fallback, now()->addMinutes(5) // Shorter TTL for fallbacks ); } } return $definitions; } /** * Resolve a single definition ID. */ public function resolveOne(string $definitionId): ?array { $result = $this->resolve([$definitionId]); return $result[$definitionId] ?? null; } /** * Warm cache for definition IDs without returning data. * Non-blocking: catches and logs errors. */ public function warmCache(array $definitionIds): void { try { $this->resolve($definitionIds); } catch (\Exception $e) { Log::warning('Failed to warm cache for setting definitions', [ 'definition_ids' => $definitionIds, 'error' => $e->getMessage(), ]); } } /** * Clear cache for a specific definition or all definitions. */ public function clearCache(?string $definitionId = null): void { if ($definitionId) { Cache::forget(self::MEMORY_CACHE_PREFIX.$definitionId); SettingsCatalogDefinition::where('definition_id', $definitionId)->delete(); } else { // Clear all memory cache (prefix-based) Cache::flush(); SettingsCatalogDefinition::truncate(); } } /** * Fetch definitions from Graph API. * * @param array $definitionIds * @return array */ private function fetchFromGraph(array $definitionIds): array { $definitions = []; // Note: Microsoft Graph API does not support "in" operator for $filter. // We fetch each definition individually. // Endpoint: /deviceManagement/configurationSettings/{definitionId} foreach ($definitionIds as $definitionId) { try { $response = $this->graphClient->request( 'GET', "/deviceManagement/configurationSettings/{$definitionId}" ); if ($response->successful() && isset($response->data)) { $item = $response->data; $definitions[$definitionId] = [ 'displayName' => $item['displayName'] ?? $this->prettifyDefinitionId($definitionId), 'description' => $item['description'] ?? null, 'helpText' => $item['helpText'] ?? null, 'categoryId' => $item['categoryId'] ?? null, 'uxBehavior' => $item['uxBehavior'] ?? null, 'raw' => $item, ]; } } catch (\Exception $e) { Log::warning('Failed to fetch definition from Graph API', [ 'definitionId' => $definitionId, 'error' => $e->getMessage(), ]); // Continue with other definitions } } return $definitions; } /** * Store definition in database. */ private function storeInDatabase(string $definitionId, array $metadata): void { SettingsCatalogDefinition::updateOrCreate( ['definition_id' => $definitionId], [ 'display_name' => $metadata['displayName'], 'description' => $metadata['description'], 'help_text' => $metadata['helpText'], 'category_id' => $metadata['categoryId'], 'ux_behavior' => $metadata['uxBehavior'], 'raw' => $metadata['raw'], ] ); } /** * Transform database model to metadata array. */ private function transformToMetadata(SettingsCatalogDefinition $definition): array { return [ 'displayName' => $definition->display_name, 'description' => $definition->description, 'helpText' => $definition->help_text, 'categoryId' => $definition->category_id, 'uxBehavior' => $definition->ux_behavior, 'raw' => $definition->raw, ]; } /** * Get fallback metadata for unknown definition. */ private function getFallbackMetadata(string $definitionId): array { return [ 'displayName' => $this->prettifyDefinitionId($definitionId), 'description' => null, 'helpText' => null, 'categoryId' => null, 'uxBehavior' => null, 'raw' => null, 'isFallback' => true, ]; } /** * Prettify definition ID for fallback display. * Example: "device_vendor_msft_policy_name" → "Device Vendor Msft Policy Name" * Special handling for {tenantid} placeholders (Microsoft template definitions). */ private function prettifyDefinitionId(string $definitionId): string { // Remove {tenantid} placeholder - it's a Microsoft template variable, not part of the name $cleaned = str_replace(['{tenantid}', '_tenantid_', '_{tenantid}_'], ['', '_', '_'], $definitionId); // Clean up consecutive underscores $cleaned = preg_replace('/_+/', '_', $cleaned); $cleaned = trim($cleaned, '_'); // Convert to title case $prettified = Str::title(str_replace('_', ' ', $cleaned)); // Remove redundant prefixes to shorten labels $prettified = preg_replace('/^Device Vendor Msft Passportforwork Policies\s+/', '', $prettified); $prettified = preg_replace('/^Device Vendor Msft Passportforwork\s+/', 'Windows Hello - ', $prettified); // Shorten common terms $prettified = str_replace('Pincomplexity', 'PIN', $prettified); $prettified = str_replace('Usepassportforwork', 'Enable Windows Hello', $prettified); return $prettified; } }