Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.44% covered (success)
97.44%
38 / 39
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ValidationMetadataNormalizer
97.44% covered (success)
97.44%
38 / 39
83.33% covered (warning)
83.33%
5 / 6
25
0.00% covered (danger)
0.00%
0 / 1
 normalize
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 normalizePageCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 normalizeExtension
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
6
 normalizeOptionalScalarFields
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 normalizeDimensionsField
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 normalizeDimensions
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
9.02
1<?php
2
3declare(strict_types=1);
4
5/**
6 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
7 * SPDX-License-Identifier: AGPL-3.0-or-later
8 */
9
10namespace OCA\Libresign\Service\File;
11
12use OCA\Libresign\Service\FileStatusService;
13
14/**
15 * @psalm-import-type LibresignValidateMetadata from \OCA\Libresign\ResponseDefinitions
16 */
17final class ValidationMetadataNormalizer {
18    private const array OPTIONAL_SCALAR_TYPE_GUARDS = [
19        'original_file_deleted' => 'is_bool',
20        'pdfVersion' => 'is_string',
21        'status_changed_at' => 'is_string',
22    ];
23
24    /**
25     * @param array<string, mixed> $metadata
26     * @psalm-return LibresignValidateMetadata
27     */
28    public static function normalize(array $metadata, string $fileName, int $totalPages): array {
29        $normalized = $metadata;
30        // Internal lifecycle marker, not part of the validation metadata.
31        unset($normalized[FileStatusService::POLICY_SNAPSHOT_FROZEN_AT]);
32        $normalized['p'] = self::normalizePageCount($totalPages);
33        $normalized['extension'] = self::normalizeExtension($normalized, $fileName);
34
35        self::normalizeOptionalScalarFields($normalized);
36        self::normalizeDimensionsField($normalized);
37
38        /**
39         * @var LibresignValidateMetadata $normalized
40         */
41        return $normalized;
42    }
43
44    private static function normalizePageCount(int $totalPages): int {
45        return max(0, $totalPages);
46    }
47
48    /**
49     * @param array<string, mixed> $metadata
50     */
51    private static function normalizeExtension(array $metadata, string $fileName): string {
52        if (isset($metadata['extension']) && is_string($metadata['extension']) && trim($metadata['extension']) !== '') {
53            return $metadata['extension'];
54        }
55
56        $extension = pathinfo($fileName, PATHINFO_EXTENSION);
57        return is_string($extension) && $extension !== '' ? strtolower($extension) : 'pdf';
58    }
59
60    /**
61     * @param array<string, mixed> $metadata
62     */
63    private static function normalizeOptionalScalarFields(array &$metadata): void {
64        foreach (self::OPTIONAL_SCALAR_TYPE_GUARDS as $key => $guard) {
65            if (!array_key_exists($key, $metadata)) {
66                continue;
67            }
68
69            if (!is_callable($guard) || !$guard($metadata[$key])) {
70                unset($metadata[$key]);
71            }
72        }
73    }
74
75    /**
76     * @param array<string, mixed> $metadata
77     */
78    private static function normalizeDimensionsField(array &$metadata): void {
79        if (!array_key_exists('d', $metadata)) {
80            return;
81        }
82
83        $normalizedDimensions = self::normalizeDimensions($metadata['d']);
84        if ($normalizedDimensions === null) {
85            unset($metadata['d']);
86            return;
87        }
88
89        $metadata['d'] = $normalizedDimensions;
90    }
91
92    /**
93     * @return list<array{w: float, h: float}>|null
94     */
95    private static function normalizeDimensions(mixed $dimensions): ?array {
96        if (!is_array($dimensions)) {
97            return null;
98        }
99
100        $normalized = [];
101        foreach ($dimensions as $dimension) {
102            if (!is_array($dimension)
103                || !array_key_exists('w', $dimension)
104                || !array_key_exists('h', $dimension)
105                || !is_numeric($dimension['w'])
106                || !is_numeric($dimension['h'])) {
107                continue;
108            }
109
110            $normalized[] = [
111                'w' => (float)$dimension['w'],
112                'h' => (float)$dimension['h'],
113            ];
114        }
115
116        return $normalized === [] ? null : $normalized;
117    }
118}