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

LibreSign / libresign / 20660742393

02 Jan 2026 03:15PM UTC coverage: 44.876%. First build
20660742393

Pull #6302

github

web-flow
Merge 928956222 into cfd974a7d
Pull Request #6302: feat: envelope custom path

77 of 126 new or added lines in 6 files covered. (61.11%)

6660 of 14841 relevant lines covered (44.88%)

5.07 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✔
NEW
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
                if ($this->getUserId()) {
×
87

88
                        $file = $this->root->getUserFolder($this->getUserId())->getFirstNodeById($nodeId);
×
89
                        if ($file instanceof File) {
×
90
                                return $file;
×
91
                        }
92
                }
93
                $path = $this->getLibreSignDefaultPath();
×
94
                $containerFolder = $this->getContainerFolder();
×
95
                try {
96
                        /** @var Folder $folder */
97
                        $folder = $containerFolder->get($path);
×
98
                } catch (NotFoundException) {
×
99
                        throw new NotFoundException('Invalid node');
×
100
                }
101
                $file = $folder->getFirstNodeById($nodeId);
×
102
                if (!$file instanceof File) {
×
103
                        throw new NotFoundException('Invalid node');
×
104
                }
105
                return $file;
×
106
        }
107

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

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

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

137
                return $path;
18✔
138
        }
139

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

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

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

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

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

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

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

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

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

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

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

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

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

260
                return $folder;
4✔
261
        }
262
}
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