• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 21970093306

13 Feb 2026 12:43AM UTC coverage: 48.792%. First build
21970093306

Pull #6825

github

web-flow
Merge bab93e99a into 34f32c1fd
Pull Request #6825: chore: bump dependencies

39 of 69 new or added lines in 4 files covered. (56.52%)

8522 of 17466 relevant lines covered (48.79%)

5.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.85
/lib/Service/FolderService.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service;
10

11
use OCA\Libresign\Exception\LibresignException;
12
use OCP\AppFramework\Services\IAppConfig;
13
use OCP\Files\AppData\IAppDataFactory;
14
use OCP\Files\File;
15
use OCP\Files\Folder;
16
use OCP\Files\IAppData;
17
use OCP\Files\IRootFolder;
18
use OCP\Files\Node;
19
use OCP\Files\NotFoundException;
20
use OCP\Files\NotPermittedException;
21
use OCP\IGroupManager;
22
use OCP\IL10N;
23
use OCP\IUser;
24
use OCP\Lock\LockedException;
25

26
class FolderService {
27
        protected IAppData $appData;
28
        public function __construct(
29
                private IRootFolder $root,
30
                protected IAppDataFactory $appDataFactory,
31
                protected IGroupManager $groupManager,
32
                private IAppConfig $appConfig,
33
                private IL10N $l10n,
34
                private ?string $userId,
35
        ) {
36
                $this->userId = $userId;
68✔
37
                $this->appData = $appDataFactory->get('libresign');
68✔
38
        }
39

40
        public function setUserId(string $userId): void {
41
                $this->userId = $userId;
13✔
42
        }
43

44
        public function getUserId(): ?string {
45
                return $this->userId;
19✔
46
        }
47

48
        /**
49
         * Get the user's root folder (full home), not the LibreSign container.
50
         *
51
         * @throws LibresignException
52
         */
53
        public function getUserRootFolder(): Folder {
54
                if (!$this->userId) {
1✔
55
                        throw new LibresignException('Invalid user to resolve folder');
×
56
                }
57

58
                return $this->root->getUserFolder($this->userId);
1✔
59
        }
60

61
        /**
62
         * Get folder for user and creates it if non-existent
63
         *
64
         * @psalm-suppress MixedReturnStatement
65
         * @throws NotFoundException
66
         * @throws NotPermittedException
67
         * @throws LockedException
68
         */
69
        public function getFolder(): Folder {
70
                $path = $this->getLibreSignDefaultPath();
18✔
71
                $containerFolder = $this->getContainerFolder();
18✔
72
                try {
73
                        /** @var Folder $folder */
74
                        $folder = $containerFolder->get($path);
18✔
75
                } catch (NotFoundException) {
16✔
76
                        /** @var Folder $folder */
77
                        $folder = $containerFolder->newFolder($path);
16✔
78
                }
79
                return $folder;
18✔
80
        }
81

82
        /**
83
         * @throws NotFoundException
84
         */
85
        public function getFileByNodeId(?int $nodeId = null): File {
86
                // For guests, files are stored in appdata, not in user folder
87
                // Skip getUserFolder search for guests to avoid false positives
NEW
88
                if ($this->getUserId() && !$this->groupManager->isInGroup($this->getUserId(), 'guest_app')) {
×
89
                        $file = $this->root->getUserFolder($this->getUserId())->getFirstNodeById($nodeId);
×
90
                        if ($file instanceof File) {
×
91
                                return $file;
×
92
                        }
93
                }
94
                $path = $this->getLibreSignDefaultPath();
×
95
                $containerFolder = $this->getContainerFolder();
×
96
                try {
97
                        /** @var Folder $folder */
98
                        $folder = $containerFolder->get($path);
×
99
                } catch (NotFoundException) {
×
100
                        throw new NotFoundException('Invalid node');
×
101
                }
102
                $file = $folder->getFirstNodeById($nodeId);
×
103
                if (!$file instanceof File) {
×
104
                        throw new NotFoundException('Invalid node');
×
105
                }
106
                return $file;
×
107
        }
108

109
        protected function getContainerFolder(): Folder {
110
                if ($this->getUserId() && !$this->groupManager->isInGroup($this->getUserId(), 'guest_app')) {
19✔
111
                        $containerFolder = $this->root->getUserFolder($this->getUserId());
18✔
112
                        if ($containerFolder->isUpdateable()) {
18✔
113
                                return $containerFolder;
18✔
114
                        }
115
                }
116
                $containerFolder = $this->appData->getFolder('/');
1✔
117
                $reflection = new \ReflectionClass($containerFolder);
1✔
118
                $reflectionProperty = $reflection->getProperty('folder');
1✔
119
                return $reflectionProperty->getValue($containerFolder);
1✔
120
        }
121

122
        private function getLibreSignDefaultPath(): string {
123
                if (!$this->userId) {
18✔
124
                        return 'unauthenticated';
×
125
                }
126
                // TODO: retrieve guest group name from app once exposed
127
                if ($this->groupManager->isInGroup($this->getUserId(), 'guest_app')) {
18✔
128
                        return 'guest_app/' . $this->getUserId();
×
129
                }
130
                $path = $this->appConfig->getUserValue($this->userId, 'folder');
18✔
131

132
                if (empty($path)) {
18✔
133
                        $defaultFolder = $this->appConfig->getAppValueString('default_user_folder', 'LibreSign');
16✔
134
                        $path = '/' . $defaultFolder;
16✔
135
                        $this->appConfig->setUserValue($this->userId, 'folder', $path);
16✔
136
                }
137

138
                return $path;
18✔
139
        }
140

141
        /**
142
         * Get or create the folder where a file should be stored
143
         *
144
         * @param array $data Must contain 'settings' and optionally 'name', 'userManager'
145
         * @param mixed $identifier User or string identifier
146
         * @return Folder The folder where files should be created
147
         * @throws LibresignException
148
         */
149
        public function getFolderForFile(array $data, $identifier): Folder {
150
                $userFolder = $this->getFolder();
17✔
151

152
                if (isset($data['settings']['envelopeFolderId'])) {
17✔
153
                        $envelopeFolder = $userFolder->getFirstNodeById($data['settings']['envelopeFolderId']);
1✔
154
                        if ($envelopeFolder === null || !$envelopeFolder instanceof Folder) {
1✔
155
                                throw new LibresignException($this->l10n->t('Envelope folder not found'));
×
156
                        }
157
                        return $envelopeFolder;
1✔
158
                }
159

160
                $folderName = $this->getFolderName($data, $identifier);
16✔
161
                return $userFolder->newFolder($folderName);
16✔
162
        }
163

164
        /**
165
         * @param array{settings: array, name: string} $data
166
         * @param IUser $owner
167
         */
168
        public function getFolderName(array $data, $identifier): string {
169
                if (isset($data['settings']['folderName'])) {
28✔
170
                        return $data['settings']['folderName'];
1✔
171
                }
172

173
                if (!isset($data['settings']['folderPatterns'])) {
27✔
174
                        $data['settings']['separator'] = '_';
7✔
175
                        $data['settings']['folderPatterns'][] = [
7✔
176
                                'name' => 'date',
7✔
177
                                'setting' => 'Y-m-d\TH-i-s-u'
7✔
178
                        ];
7✔
179
                        $data['settings']['folderPatterns'][] = [
7✔
180
                                'name' => 'name'
7✔
181
                        ];
7✔
182
                        $data['settings']['folderPatterns'][] = [
7✔
183
                                'name' => 'userId'
7✔
184
                        ];
7✔
185
                }
186
                $folderName = [];
27✔
187
                foreach ($data['settings']['folderPatterns'] as $pattern) {
27✔
188
                        switch ($pattern['name']) {
27✔
189
                                case 'date':
27✔
190
                                        $folderName[] = (new \DateTime('now', new \DateTimeZone('UTC')))->format($pattern['setting']);
23✔
191
                                        break;
23✔
192
                                case 'name':
26✔
193
                                        if (!empty($data['name'])) {
25✔
194
                                                $folderName[] = $data['name'];
23✔
195
                                        }
196
                                        break;
25✔
197
                                case 'userId':
25✔
198
                                        if ($identifier instanceof \OCP\IUser) {
24✔
199
                                                $folderName[] = $identifier->getUID();
24✔
200
                                        } else {
201
                                                $folderName[] = $identifier;
×
202
                                        }
203
                                        break;
24✔
204
                        }
205
                }
206
                return implode($data['settings']['separator'], $folderName);
27✔
207
        }
208

209
        public function getFileByPath(string $path): Node {
210
                $userFolder = $this->root->getUserFolder($this->getUserId());
×
211
                try {
212
                        return $userFolder->get($path);
×
213
                } catch (NotFoundException) {
×
214
                        throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
215
                }
216
        }
217

218
        /**
219
         * Ensure a folder exists at a given absolute user path, creating missing segments.
220
         * If the final folder already exists, it must be empty.
221
         *
222
         * @throws LibresignException
223
         */
224
        public function getOrCreateFolderByAbsolutePath(string $path): Folder {
225
                if (!$this->userId) {
5✔
226
                        throw new LibresignException('Invalid user to create envelope folder');
×
227
                }
228

229
                $cleanPath = ltrim($path, '/');
5✔
230
                $userFolder = $this->root->getUserFolder($this->userId);
5✔
231

232
                if ($cleanPath === '') {
5✔
233
                        return $userFolder;
×
234
                }
235

236
                $segments = array_filter(explode('/', $cleanPath), static fn (string $segment) => $segment !== '');
5✔
237
                $folder = $userFolder;
5✔
238
                $isLastSegment = false;
5✔
239

240
                foreach ($segments as $index => $segment) {
5✔
241
                        $isLastSegment = ($index === count($segments) - 1);
5✔
242

243
                        try {
244
                                $node = $folder->get($segment);
5✔
245
                                if (!$node instanceof Folder) {
3✔
246
                                        throw new LibresignException('Invalid folder path');
×
247
                                }
248
                                $folder = $node;
3✔
249

250
                                if ($isLastSegment) {
3✔
251
                                        $contents = $folder->getDirectoryListing();
2✔
252
                                        if (count($contents) > 0) {
2✔
253
                                                throw new LibresignException($this->l10n->t('Folder already exists and is not empty: %s', [$path]));
3✔
254
                                        }
255
                                }
256
                        } catch (NotFoundException) {
4✔
257
                                $folder = $folder->newFolder($segment);
3✔
258
                        }
259
                }
260

261
                return $folder;
4✔
262
        }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc