$manifest * @return array */ public static function normalize(array $manifest): array { $normalized = $manifest; $name = trim((string) ($manifest['name'] ?? '')); $normalized['name'] = $name; $normalized['display_name'] = self::normalizeNullableString($manifest['display_name'] ?? null) ?? self::normalizeNullableString($manifest['title'] ?? null) ?? $name; $normalized['description'] = self::normalizeNullableString($manifest['description'] ?? null); $normalized['version'] = self::normalizeNullableString($manifest['version'] ?? null) ?? '0.0.0'; $normalized['author'] = self::normalizeNullableString($manifest['author'] ?? null); $normalized['license'] = self::normalizeNullableString($manifest['license'] ?? null) ?? 'free'; $normalized['category'] = self::normalizeNullableString($manifest['category'] ?? null); $normalized['dependencies'] = self::normalizeStringList($manifest['dependencies'] ?? []); $normalized['optional_dependencies'] = self::normalizeStringList($manifest['optional_dependencies'] ?? []); $normalized['hooks'] = self::normalizeStringList($manifest['hooks'] ?? []); $normalized['features'] = self::normalizeStringList($manifest['features'] ?? []); $normalized['permissions'] = self::normalizePermissions($manifest['permissions'] ?? []); $normalized['menu_items'] = self::normalizeMenuItems($manifest['menu_items'] ?? []); $normalized['requirements'] = is_array($manifest['requirements'] ?? null) ? $manifest['requirements'] : []; $normalized['config'] = is_array($manifest['config'] ?? null) ? $manifest['config'] : []; $normalized['manifest_version'] = (int) ($manifest['manifest_version'] ?? 1); return $normalized; } /** * @param mixed $value */ private static function normalizeNullableString(mixed $value): ?string { if (! is_scalar($value)) { return null; } $normalized = trim((string) $value); return $normalized !== '' ? $normalized : null; } /** * @param mixed $value * @return array */ private static function normalizeStringList(mixed $value): array { if (! is_array($value)) { return []; } $normalized = []; foreach ($value as $item) { $item = self::normalizeNullableString($item); if ($item !== null) { $normalized[] = $item; } } return array_values(array_unique($normalized)); } /** * @param mixed $value * @return array */ private static function normalizePermissions(mixed $value): array { if (! is_array($value)) { return []; } $permissions = []; if (self::isAssociative($value)) { foreach ($value as $name => $description) { $permissionName = self::normalizeNullableString($name); if ($permissionName === null) { continue; } $display = self::normalizeNullableString($description) ?? self::humanizePermissionName($permissionName); $permissions[] = [ 'name' => $permissionName, 'display_name' => $display, 'description' => self::normalizeNullableString($description) ?? $display, 'guard_name' => 'web', ]; } return $permissions; } foreach ($value as $permission) { if (is_string($permission)) { $permissionName = self::normalizeNullableString($permission); if ($permissionName === null) { continue; } $permissions[] = [ 'name' => $permissionName, 'display_name' => self::humanizePermissionName($permissionName), 'description' => null, 'guard_name' => 'web', ]; continue; } if (! is_array($permission)) { continue; } $permissionName = self::normalizeNullableString($permission['name'] ?? null); if ($permissionName === null) { continue; } $permissions[] = [ 'name' => $permissionName, 'display_name' => self::normalizeNullableString($permission['display_name'] ?? null) ?? self::humanizePermissionName($permissionName), 'description' => self::normalizeNullableString($permission['description'] ?? null), 'guard_name' => self::normalizeNullableString($permission['guard_name'] ?? null) ?? 'web', ]; } return $permissions; } /** * @param mixed $value * @return array> */ private static function normalizeMenuItems(mixed $value): array { if (! is_array($value)) { return []; } $menuItems = []; foreach ($value as $item) { if (! is_array($item)) { continue; } $title = self::normalizeNullableString($item['title'] ?? null); $route = self::normalizeNullableString($item['route'] ?? null); if ($title === null || $route === null) { continue; } $menuItems[] = [ 'title' => $title, 'route' => $route, 'icon' => self::normalizeNullableString($item['icon'] ?? null), 'permission' => self::normalizeNullableString($item['permission'] ?? null), 'parent' => self::normalizeNullableString($item['parent'] ?? null), 'description' => self::normalizeNullableString($item['description'] ?? null), 'order' => isset($item['order']) && is_numeric($item['order']) ? (int) $item['order'] : 0, 'badge' => $item['badge'] ?? null, ]; } usort($menuItems, static fn(array $left, array $right): int => ($left['order'] ?? 0) <=> ($right['order'] ?? 0)); return $menuItems; } /** * @param array $value */ private static function isAssociative(array $value): bool { return array_keys($value) !== range(0, count($value) - 1); } private static function humanizePermissionName(string $permissionName): string { $label = str_replace(['.', '_', '-'], ' ', $permissionName); return ucfirst(trim(preg_replace('/\s+/', ' ', $label) ?? $label)); } }