Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.32% covered (success)
90.32%
28 / 31
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FilePolicyApplier
90.32% covered (success)
90.32%
28 / 31
66.67% covered (warning)
66.67%
4 / 6
17.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 applyAll
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 syncCoreFlowPolicies
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 syncAllPolicies
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 discoverAppliers
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
6.01
 buildFileApplierClassFromProvider
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service\Policy;
10
11use OCA\Libresign\Db\File as FileEntity;
12use OCA\Libresign\Db\FileMapper;
13use OCA\Libresign\Service\FileService;
14use OCA\Libresign\Service\Policy\Contract\IFilePolicyApplier;
15use OCA\Libresign\Service\Policy\Provider\PolicyProviders;
16use OCP\IL10N;
17
18class FilePolicyApplier {
19    /** @var list<IFilePolicyApplier> */
20    private readonly array $appliers;
21
22    public function __construct(
23        private readonly PolicyService $policyService,
24        private readonly FileService $fileService,
25        private readonly IL10N $l10n,
26        private readonly FileMapper $fileMapper,
27    ) {
28        $this->appliers = $this->discoverAppliers();
29    }
30
31    /**
32     * Apply all policies to a freshly built FileEntity before the first insert.
33     */
34    public function applyAll(FileEntity $file, array $data): void {
35        foreach ($this->appliers as $applier) {
36            $applier->apply($file, $data);
37        }
38    }
39
40    /**
41     * Re-evaluate and persist signing-critical file policies on an existing file.
42     * Use this when updating a file located by UUID.
43     */
44    public function syncCoreFlowPolicies(FileEntity $file, array $data): void {
45        foreach ($this->appliers as $applier) {
46            if ($applier->supportsCoreFlowSync()) {
47                $applier->sync($file, $data);
48            }
49        }
50    }
51
52    /**
53     * Re-evaluate and persist all three policies on an existing file.
54     * Use this when updating a file located by node ID.
55     */
56    public function syncAllPolicies(FileEntity $file, array $data): void {
57        foreach ($this->appliers as $applier) {
58            $applier->sync($file, $data);
59        }
60    }
61
62    /** @return list<IFilePolicyApplier> */
63    private function discoverAppliers(): array {
64        $appliers = [];
65        $discoveredApplierClasses = [];
66
67        foreach (PolicyProviders::BY_KEY as $providerClass) {
68            $applierClass = $this->buildFileApplierClassFromProvider($providerClass);
69            if ($applierClass === null || !class_exists($applierClass)) {
70                continue;
71            }
72
73            // A provider that answers for several policy keys is listed once per
74            // key, and one applier owns all of them: building it again per key
75            // would run the same file policy several times on every request.
76            if (isset($discoveredApplierClasses[$applierClass])) {
77                continue;
78            }
79
80            $discoveredApplierClasses[$applierClass] = true;
81
82            // The file mapper is an optional extra: an applier that needs it declares
83            // a fourth constructor parameter, every other applier simply ignores it.
84            $instance = new $applierClass($this->policyService, $this->fileService, $this->l10n, $this->fileMapper);
85            if (!$instance instanceof IFilePolicyApplier) {
86                continue;
87            }
88
89            $appliers[] = $instance;
90        }
91
92        return $appliers;
93    }
94
95    /** @param class-string $providerClass */
96    private function buildFileApplierClassFromProvider(string $providerClass): ?string {
97        $lastSeparator = strrpos($providerClass, '\\');
98        if ($lastSeparator === false) {
99            return null;
100        }
101
102        $namespace = substr($providerClass, 0, $lastSeparator);
103        $shortName = substr($providerClass, $lastSeparator + 1);
104        $baseName = str_ends_with($shortName, 'Policy')
105            ? substr($shortName, 0, -strlen('Policy'))
106            : $shortName;
107
108        return $namespace . '\\FilePolicy\\' . $baseName . 'FilePolicyApplier';
109    }
110}