306 lines
10 KiB
PHP
306 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\TenantConfiguration;
|
|
|
|
final class ExchangeTeamsRenderableSummaryBuilder
|
|
{
|
|
public function __construct(
|
|
private readonly ExchangeTeamsComparablePayloadNormalizer $normalizer,
|
|
) {}
|
|
|
|
public function supports(string $canonicalType): bool
|
|
{
|
|
return $this->normalizer->supports($canonicalType);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function canBuild(string $canonicalType, array $payload): bool
|
|
{
|
|
if (! $this->supports($canonicalType)) {
|
|
return false;
|
|
}
|
|
|
|
$normalized = $this->normalizer->normalize($canonicalType, $payload);
|
|
|
|
return ($normalized['supported'] ?? false) === true;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
public function build(string $canonicalType, array $payload, array $context = []): ?array
|
|
{
|
|
if (! $this->supports($canonicalType)) {
|
|
return null;
|
|
}
|
|
|
|
$normalized = $this->normalizer->normalize($canonicalType, $payload);
|
|
|
|
if (($normalized['supported'] ?? false) !== true) {
|
|
return null;
|
|
}
|
|
|
|
return match ($canonicalType) {
|
|
'transportRule' => $this->transportRuleSummary($normalized, $context),
|
|
'acceptedDomain' => $this->acceptedDomainSummary($normalized, $context),
|
|
'appPermissionPolicy' => $this->appPermissionPolicySummary($normalized, $context),
|
|
'meetingPolicy' => $this->meetingPolicySummary($normalized, $context),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function transportRuleSummary(array $normalized, array $context): array
|
|
{
|
|
return $this->baseSummary('Transport rule', $normalized, $context, [
|
|
['label' => 'Display name', 'value' => $normalized['display_name'] ?? 'Unnamed transport rule'],
|
|
['label' => 'Enabled/state', 'value' => $normalized['enabled_state'] ?? null],
|
|
['label' => 'Priority / order', 'value' => $normalized['priority_order'] ?? null],
|
|
['label' => 'Mode / enforcement', 'value' => $normalized['mode'] ?? null],
|
|
['label' => 'Conditions', 'value' => $this->settingSummary($normalized['conditions'] ?? [])],
|
|
['label' => 'Actions', 'value' => $this->settingSummary($normalized['actions'] ?? [])],
|
|
['label' => 'Exceptions', 'value' => $this->settingSummary($normalized['exceptions'] ?? [])],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function acceptedDomainSummary(array $normalized, array $context): array
|
|
{
|
|
return $this->baseSummary('Accepted domain', $normalized, $context, [
|
|
['label' => 'Domain name', 'value' => $normalized['domain_name'] ?? 'Unnamed accepted domain'],
|
|
['label' => 'Domain type', 'value' => $normalized['domain_type'] ?? null],
|
|
['label' => 'Default domain', 'value' => $normalized['is_default'] ?? null],
|
|
['label' => 'State', 'value' => $normalized['state'] ?? null],
|
|
], displayName: $normalized['domain_name'] ?? null, state: $normalized['state'] ?? null);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function appPermissionPolicySummary(array $normalized, array $context): array
|
|
{
|
|
return $this->baseSummary('Teams app permission policy', $normalized, $context, [
|
|
['label' => 'Display name', 'value' => $normalized['display_name'] ?? 'Unnamed app permission policy'],
|
|
['label' => 'Policy mode', 'value' => $normalized['policy_mode'] ?? null],
|
|
['label' => 'Allowed apps', 'value' => $this->appSummary($normalized['allowed_apps'] ?? [])],
|
|
['label' => 'Blocked apps', 'value' => $this->appSummary($normalized['blocked_apps'] ?? [])],
|
|
['label' => 'Assignments / targets', 'value' => $this->settingSummary($normalized['targets'] ?? [])],
|
|
], state: $normalized['policy_mode'] ?? null);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function meetingPolicySummary(array $normalized, array $context): array
|
|
{
|
|
return $this->baseSummary('Teams meeting policy', $normalized, $context, [
|
|
['label' => 'Display name', 'value' => $normalized['display_name'] ?? 'Unnamed meeting policy'],
|
|
['label' => 'State', 'value' => $normalized['state'] ?? null],
|
|
['label' => 'External / anonymous access', 'value' => $this->settingSummary($normalized['external_access'] ?? [])],
|
|
['label' => 'Recording / transcription', 'value' => $this->settingSummary($normalized['recording_transcription'] ?? [])],
|
|
['label' => 'Lobby / admission', 'value' => $this->settingSummary($normalized['lobby_admission'] ?? [])],
|
|
['label' => 'Content sharing', 'value' => $this->settingSummary($normalized['content_sharing'] ?? [])],
|
|
], state: $normalized['state'] ?? null);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
* @param array<string, mixed> $context
|
|
* @param list<array{label: string, value: mixed}> $fields
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function baseSummary(
|
|
string $resourceType,
|
|
array $normalized,
|
|
array $context,
|
|
array $fields,
|
|
?string $displayName = null,
|
|
?string $state = null,
|
|
): array {
|
|
return [
|
|
'resource_type' => $resourceType,
|
|
'display_name' => $displayName ?? $this->displayName($normalized, $resourceType),
|
|
'state' => $state,
|
|
'summary_fields' => array_values(array_filter(
|
|
array_map(fn (array $field): array => [
|
|
'label' => $field['label'],
|
|
'value' => $this->summaryValue($field['value']),
|
|
], $fields),
|
|
static fn (array $field): bool => filled($field['value'] ?? null),
|
|
)),
|
|
'targets' => [],
|
|
'conditions' => [],
|
|
'claim_state' => $this->stringContext($context, 'claim_state'),
|
|
'identity_state' => $this->stringContext($context, 'identity_state'),
|
|
'last_captured' => $this->stringContext($context, 'last_captured'),
|
|
'unsupported_fields' => data_get($normalized, 'diagnostics.unsupported_fields', []),
|
|
'redacted_fields' => data_get($normalized, 'diagnostics.redacted_fields', []),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $normalized
|
|
*/
|
|
private function displayName(array $normalized, string $fallback): string
|
|
{
|
|
foreach (['display_name', 'domain_name'] as $key) {
|
|
$value = $normalized[$key] ?? null;
|
|
|
|
if (is_scalar($value) && trim((string) $value) !== '') {
|
|
return trim((string) $value);
|
|
}
|
|
}
|
|
|
|
return 'Unnamed '.$fallback;
|
|
}
|
|
|
|
private function summaryValue(mixed $value): ?string
|
|
{
|
|
if ($value === null || $value === '' || $value === []) {
|
|
return null;
|
|
}
|
|
|
|
if (is_bool($value)) {
|
|
return $value ? 'yes' : 'no';
|
|
}
|
|
|
|
if (is_scalar($value)) {
|
|
$value = trim((string) $value);
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
return $this->stringify($value);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $settings
|
|
*/
|
|
private function settingSummary(array $settings): ?string
|
|
{
|
|
if ($settings === []) {
|
|
return null;
|
|
}
|
|
|
|
$parts = [];
|
|
|
|
foreach ($settings as $key => $value) {
|
|
if ($this->containsRedacted($value)) {
|
|
continue;
|
|
}
|
|
|
|
$value = $this->summaryValue($value);
|
|
|
|
if ($value === null) {
|
|
continue;
|
|
}
|
|
|
|
$parts[] = str((string) $key)->replace('_', ' ')->headline()->toString().': '.$value;
|
|
}
|
|
|
|
return $parts === [] ? null : implode('; ', $parts);
|
|
}
|
|
|
|
private function containsRedacted(mixed $value): bool
|
|
{
|
|
if ($value === '[redacted]') {
|
|
return true;
|
|
}
|
|
|
|
if (! is_array($value)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($value as $nestedValue) {
|
|
if ($this->containsRedacted($nestedValue)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $apps
|
|
*/
|
|
private function appSummary(array $apps): ?string
|
|
{
|
|
if ($apps === []) {
|
|
return null;
|
|
}
|
|
|
|
$parts = [];
|
|
|
|
foreach ($apps as $app) {
|
|
if (! is_array($app)) {
|
|
continue;
|
|
}
|
|
|
|
$display = $this->summaryValue($app['display_name'] ?? null);
|
|
|
|
if ($display !== null) {
|
|
$parts[] = $display;
|
|
}
|
|
}
|
|
|
|
$parts = array_values(array_filter($parts));
|
|
|
|
return $parts === [] ? null : implode(', ', $parts);
|
|
}
|
|
|
|
private function stringify(mixed $value): string
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value ? 'yes' : 'no';
|
|
}
|
|
|
|
if (is_scalar($value)) {
|
|
return (string) $value;
|
|
}
|
|
|
|
return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $context
|
|
*/
|
|
private function stringContext(array $context, string $key): ?string
|
|
{
|
|
$value = $context[$key] ?? null;
|
|
|
|
if ($value instanceof \BackedEnum) {
|
|
return (string) $value->value;
|
|
}
|
|
|
|
if (! is_scalar($value)) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim((string) $value);
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
}
|