- Add SettingsCatalogCategoryResolver service with 3-tier caching
- Add SettingsCatalogCategory model and migration
- Add warm-cache commands for definitions and categories
- Update PolicyNormalizer to display categories in settings table
- Fix extraction of nested children in choiceSettingValue
- Add category inheritance from parent settings
- Skip template IDs with {tenantid} placeholder in Graph API calls
- Update Livewire table with Category, Data Type, and Description columns
Related tests updated and passing.
184 lines
6.3 KiB
PHP
184 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Policy;
|
|
use App\Services\Intune\SettingsCatalogDefinitionResolver;
|
|
use Illuminate\Console\Command;
|
|
|
|
class WarmSettingsCatalogDefinitionsCache extends Command
|
|
{
|
|
protected $signature = 'intune:warm-definitions-cache
|
|
{--policy= : Specific policy ID to warm cache for}
|
|
{--all : Warm cache for all Settings Catalog policies}
|
|
{--force : Force re-fetch even if cached}';
|
|
|
|
protected $description = 'Warm the Settings Catalog definitions cache by fetching display names from Graph API';
|
|
|
|
public function handle(SettingsCatalogDefinitionResolver $resolver): int
|
|
{
|
|
if ($this->option('policy')) {
|
|
return $this->warmForPolicy($this->option('policy'), $resolver);
|
|
}
|
|
|
|
if ($this->option('all')) {
|
|
return $this->warmForAllPolicies($resolver);
|
|
}
|
|
|
|
$this->error('Please specify either --policy=ID or --all');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
private function warmForPolicy(string $policyId, SettingsCatalogDefinitionResolver $resolver): int
|
|
{
|
|
$policy = Policy::find($policyId);
|
|
|
|
if (! $policy) {
|
|
$this->error("Policy {$policyId} not found");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if ($policy->policy_type !== 'settingsCatalog' && $policy->policy_type !== 'settingsCatalogPolicy') {
|
|
$this->error("Policy {$policyId} is not a Settings Catalog policy");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info("Warming cache for policy: {$policy->display_name} ({$policy->id})");
|
|
|
|
$snapshot = $policy->versions()->latest('version_number')->first()?->snapshot ?? [];
|
|
$definitionIds = $this->extractDefinitionIds($snapshot);
|
|
|
|
if (empty($definitionIds)) {
|
|
$this->warn('No definition IDs found in policy snapshot');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->info('Found '.count($definitionIds).' definition IDs');
|
|
$this->newLine();
|
|
|
|
$progressBar = $this->output->createProgressBar(count($definitionIds));
|
|
$progressBar->start();
|
|
|
|
$success = 0;
|
|
$failed = 0;
|
|
$cached = 0;
|
|
|
|
foreach ($definitionIds as $definitionId) {
|
|
try {
|
|
$definition = $resolver->resolveOne($definitionId);
|
|
|
|
if ($definition) {
|
|
if (isset($definition['displayName']) && ! str_contains($definition['displayName'], 'Device Vendor Msft')) {
|
|
$success++;
|
|
$this->line("\n✓ {$definitionId} → {$definition['displayName']}", 'info');
|
|
} else {
|
|
$cached++;
|
|
$this->line("\n⚠ {$definitionId} → (fallback: {$definition['displayName']})", 'comment');
|
|
}
|
|
} else {
|
|
$failed++;
|
|
$this->line("\n✗ {$definitionId} → Failed to resolve", 'error');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$failed++;
|
|
$this->line("\n✗ {$definitionId} → Error: {$e->getMessage()}", 'error');
|
|
}
|
|
|
|
$progressBar->advance();
|
|
}
|
|
|
|
$progressBar->finish();
|
|
$this->newLine(2);
|
|
|
|
$this->table(
|
|
['Status', 'Count'],
|
|
[
|
|
['✓ Successfully fetched from Graph', $success],
|
|
['⚠ Using fallback (not in Graph)', $cached],
|
|
['✗ Failed', $failed],
|
|
['Total', count($definitionIds)],
|
|
]
|
|
);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function warmForAllPolicies(SettingsCatalogDefinitionResolver $resolver): int
|
|
{
|
|
$policies = Policy::where(function ($query) {
|
|
$query->where('policy_type', 'settingsCatalog')
|
|
->orWhere('policy_type', 'settingsCatalogPolicy');
|
|
})->get();
|
|
|
|
if ($policies->isEmpty()) {
|
|
$this->warn('No Settings Catalog policies found');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->info("Found {$policies->count()} Settings Catalog policies");
|
|
$this->newLine();
|
|
|
|
foreach ($policies as $policy) {
|
|
$this->warmForPolicy((string) $policy->id, $resolver);
|
|
$this->newLine();
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function extractDefinitionIds(array $snapshot): array
|
|
{
|
|
$ids = [];
|
|
$settings = $snapshot['settings'] ?? [];
|
|
|
|
$walk = function (array $nodes) use (&$walk, &$ids): void {
|
|
foreach ($nodes as $node) {
|
|
if (! is_array($node)) {
|
|
continue;
|
|
}
|
|
|
|
// Top-level settings have settingInstance wrapper
|
|
if (isset($node['settingInstance']['settingDefinitionId'])) {
|
|
$ids[] = $node['settingInstance']['settingDefinitionId'];
|
|
$instance = $node['settingInstance'];
|
|
}
|
|
// Nested children have settingDefinitionId directly
|
|
elseif (isset($node['settingDefinitionId'])) {
|
|
$ids[] = $node['settingDefinitionId'];
|
|
$instance = $node;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
// Handle nested children in choice setting value
|
|
if (isset($instance['choiceSettingValue']['children']) && is_array($instance['choiceSettingValue']['children'])) {
|
|
$walk($instance['choiceSettingValue']['children']);
|
|
}
|
|
|
|
// Handle nested children in group collections
|
|
if (isset($instance['groupSettingCollectionValue'])) {
|
|
foreach ($instance['groupSettingCollectionValue'] as $group) {
|
|
if (isset($group['children']) && is_array($group['children'])) {
|
|
$walk($group['children']);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle nested children in group setting value
|
|
if (isset($instance['groupSettingValue']['children']) && is_array($instance['groupSettingValue']['children'])) {
|
|
$walk($instance['groupSettingValue']['children']);
|
|
}
|
|
}
|
|
};
|
|
|
|
$walk($settings);
|
|
|
|
return array_unique($ids);
|
|
}
|
|
}
|