Lines 100.00% 39 / 39
Methods 100.00% 8 / 8
Classes 100.00% 1 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 getAttachment 100.00% 1 / 1 100.00% 1 / 1 1
 canDownloadAttachment 100.00% 6 / 6 100.00% 1 / 1 4
 checkGroupPermission 100.00% 12 / 12 100.00% 1 / 1 6
 checkUserPermission 100.00% 4 / 4 100.00% 1 / 1 2
 getUserRights 100.00% 12 / 12 100.00% 1 / 1 5
 getAttachmentErrorMessage 100.00% 1 / 1 100.00% 1 / 1 1
 getGenericErrorMessage 100.00% 2 / 2 100.00% 1 / 1 2
31final readonly class AttachmentService
32{
33    public function __construct(
34        private Configuration $configuration,
35        private CurrentUser $currentUser,
36        private Permission $faqPermission,
37    ) {
38    }
39
40    /**
41     * Retrieves an attachment by ID.
42     *
43     * @throws AttachmentException
44     */
45    public function getAttachment(int $attachmentId): File
46    {
47        return AttachmentFactory::create($attachmentId);
48    }
49
50    /**
51     * Checks if the current user has permission to download an attachment.
52     */
53    public function canDownloadAttachment(AbstractAttachment $attachment): bool
54    {
55        // Allow downloads for guests if configured
56        if ($this->configuration->get('records.allowDownloadsForGuests')) {
57            return true;
58        }
59
60        // Check group and user permissions
61        $hasGroupPermission = $this->checkGroupPermission($attachment);
62        $hasUserPermission = $this->checkUserPermission($attachment);
63        $userRights = $this->getUserRights();
64
65        return $hasGroupPermission && $hasUserPermission && ($userRights['dlattachment'] ?? false);
66    }
67
68    /**
69     * Checks group permission for an attachment.
70     */
71    private function checkGroupPermission(AbstractAttachment $attachment): bool
72    {
73        if (!$this->currentUser->perm instanceof MediumPermission) {
74            return true;
75        }
76
77        $groupPermission = $this->faqPermission->get(Permission::GROUP, $attachment->getRecordId());
78
79        if ($groupPermission === []) {
80            return false;
81        }
82
83        // -1 means "all groups"
84        if (in_array(-1, $groupPermission, strict: true)) {
85            return true;
86        }
87
88        foreach ($this->currentUser->perm->getUserGroups($this->currentUser->getUserId()) as $userGroup) {
89            if (!in_array($userGroup, $groupPermission, strict: true)) {
90                continue;
91            }
92
93            return true;
94        }
95
96        return false;
97    }
98
99    /**
100     * Checks user permission for an attachment.
101     */
102    private function checkUserPermission(AbstractAttachment $attachment): bool
103    {
104        $userPermission = $this->faqPermission->get(Permission::USER, $attachment->getRecordId());
105
106        // -1 means "all users"
107        if (in_array(-1, $userPermission, strict: true)) {
108            return true;
109        }
110
111        return in_array($this->currentUser->getUserId(), $userPermission, strict: true);
112    }
113
114    /**
115     * Gets all user rights.
116     *
117     * @return array<string, bool>
118     */
119    private function getUserRights(): array
120    {
121        $permission = [];
122
123        if (!$this->currentUser->isLoggedIn()) {
124            return $permission;
125        }
126
127        // Read all rights, set false
128        $allRights = $this->currentUser->perm->getAllRightsData();
129        foreach ($allRights as $right) {
130            $permission[(string) $right['name']] = false;
131        }
132
133        // Check user rights, set true
134        $allUserRights = $this->currentUser->perm->getAllUserRights($this->currentUser->getUserId());
135        foreach ($allRights as $allRight) {
136            if (!in_array($allRight['right_id'], $allUserRights, strict: true)) {
137                continue;
138            }
139
140            $permission[(string) $allRight['name']] = true;
141        }
142
143        return $permission;
144    }
145
146    /**
147     * Gets an error message for attachment exceptions.
148     */
149    public function getAttachmentErrorMessage(AttachmentException $attachmentException): string
150    {
151        return Translation::getString(key: 'msgAttachmentInvalid') . ' (' . $attachmentException->getMessage() . ')';
152    }
153
154    /**
155     * Gets generic attachment error message.
156     */
157    public function getGenericErrorMessage(): string
158    {
159        $message = Translation::get(key: 'msgAttachmentInvalid');
160
161        return is_string($message) ? $message : '';
162    }
163}