Passed
Push — master ( 199aaf...e6a61c )
by Pol
02:37 queued 45s
created

Wopi::unlockAndRelock()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
nc 1
nop 5
dl 0
loc 5
ccs 0
cts 3
cp 0
c 2
b 0
f 0
cc 1
crap 2
rs 10
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 */
7
8
declare(strict_types=1);
9
10
namespace ChampsLibres\WopiBundle\Service;
11
12
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
13
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
14
use loophp\psr17\Psr17Interface;
15
use Psr\Cache\CacheItemPoolInterface;
16
use Psr\Http\Message\RequestInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Symfony\Component\Routing\RouterInterface;
19
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
20
21
use function strlen;
22
23
final class Wopi implements WopiInterface
24
{
25
    private CacheItemPoolInterface $cache;
26
27
    private DocumentManagerInterface $documentManager;
28
29
    private Psr17Interface $psr17;
30
31
    private RouterInterface $router;
32
33
    private TokenStorageInterface $tokenStorage;
34
35
    public function __construct(
36
        CacheItemPoolInterface $cache,
37
        DocumentManagerInterface $documentManager,
38
        Psr17Interface $psr17,
39
        RouterInterface $router,
40
        TokenStorageInterface $tokenStorage
41
    ) {
42
        $this->cache = $cache;
43
        $this->documentManager = $documentManager;
44
        $this->psr17 = $psr17;
45
        $this->router = $router;
46
        $this->tokenStorage = $tokenStorage;
47
    }
48
49
    public function checkFileInfo(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
50
    {
51
        $document = $this->documentManager->findByDocumentId($fileId);
52
        $userIdentifier = $this->tokenStorage->getToken()->getUser()->getUserIdentifier();
0 ignored issues
show
Bug introduced by
The method getUserIdentifier() does not exist on Stringable. It seems like you code against a sub-type of Stringable such as Symfony\Component\Securi...n\UserNotFoundException or Symfony\Component\Securi...n\UserNotFoundException. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

52
        $userIdentifier = $this->tokenStorage->getToken()->getUser()->/** @scrutinizer ignore-call */ getUserIdentifier();
Loading history...
53
        $userCacheKey = sprintf('wopi_putUserInfo_%s', $this->tokenStorage->getToken()->getUser()->getUserIdentifier());
54
55
        return $this
56
            ->psr17
57
            ->createResponse()
58
            ->withHeader('Content-Type', 'application/json')
59
            ->withBody($this->psr17->createStream((string) json_encode(
60
                [
61
                    'BaseFileName' => $document->getBasename(),
62
                    'OwnerId' => 'Symfony',
63
                    'Size' => (int) $document->getSize(),
64
                    'UserId' => $userIdentifier,
65
                    'ReadOnly' => false,
66
                    'UserCanAttend' => true,
67
                    'UserCanPresent' => true,
68
                    'UserCanRename' => true,
69
                    'UserCanWrite' => true,
70
                    'UserCanNotWriteRelative' => false,
71
                    'SupportsUserInfo' => true,
72
                    'SupportsDeleteFile' => true,
73
                    'SupportsLocks' => true,
74
                    'SupportsGetLock' => true,
75
                    'SupportsExtendedLockLength' => true,
76
                    'UserFriendlyName' => $userIdentifier,
77
                    'SupportsUpdate' => true,
78
                    'SupportsRename' => true,
79
                    'DisablePrint' => false,
80
                    'AllowExternalMarketplace' => true,
81
                    'SupportedShareUrlTypes' => [
82
                        'ReadOnly',
83
                    ],
84
                    'SHA256' => $document->getSha256(),
85
                    'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(),
86
                ]
87
            )));
88
    }
89
90
    public function deleteFile(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
91
    {
92
        $this->documentManager->remove($this->documentManager->findByDocumentId($fileId));
0 ignored issues
show
Bug introduced by
It seems like $this->documentManager->findByDocumentId($fileId) can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...agerInterface::remove() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
        $this->documentManager->remove(/** @scrutinizer ignore-type */ $this->documentManager->findByDocumentId($fileId));
Loading history...
93
94
        // @TODO Check if the file is properly deleted.
95
96
        return $this
97
            ->psr17
98
            ->createResponse(200);
99
    }
100
101
    public function enumerateAncestors(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
102
    {
103
        return $this
104
            ->psr17
105
            ->createResponse(501);
106
    }
107
108
    public function getFile(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
109
    {
110
        $document = $this->documentManager->findByDocumentId($fileId);
111
        $revision = $this->documentManager->getVersion($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...Interface::getVersion() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
        $revision = $this->documentManager->getVersion(/** @scrutinizer ignore-type */ $document);
Loading history...
112
113
        $content = (null === $contentResource = $document->getContent()) ?
114
            $this->psr17->createStream('') :
115
            $this->psr17->createStreamFromResource($contentResource);
116
117
        return $this
118
            ->psr17
119
            ->createResponse()
120
            ->withHeader(
121
                WopiInterface::HEADER_ITEM_VERSION,
122
                sprintf('v%s', $revision)
123
            )
124
            ->withHeader(
125
                'Content-Type',
126
                'application/octet-stream',
127
            )
128
            ->withHeader(
129
                'Content-Length',
130
                $document->getSize()
131
            )
132
            ->withHeader(
133
                'Content-Disposition',
134
                sprintf('attachment; filename=%s', $document->getBasename())
135
            )
136
            ->withBody($content);
137
    }
138
139
    public function getLock(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
140
    {
141
        $document = $this->documentManager->findByDocumentId($fileId);
142
143
        if ($this->documentManager->hasLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::hasLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
        if ($this->documentManager->hasLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
144
            return $this
145
                ->psr17
146
                ->createResponse()
147
                ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::getLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
                ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock(/** @scrutinizer ignore-type */ $document));
Loading history...
148
        }
149
150
        return $this
151
            ->psr17
152
            ->createResponse(404)
153
            ->withHeader(WopiInterface::HEADER_LOCK, '');
154
    }
155
156
    public function getShareUrl(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
157
    {
158
        return $this
159
            ->psr17
160
            ->createResponse(501);
161
    }
162
163
    public function lock(string $fileId, string $accessToken, string $xWopiLock, RequestInterface $request): ResponseInterface
164
    {
165
        $document = $this->documentManager->findByDocumentId($fileId);
166
        $version = $this->documentManager->getVersion($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...Interface::getVersion() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
        $version = $this->documentManager->getVersion(/** @scrutinizer ignore-type */ $document);
Loading history...
167
168
        if ($this->documentManager->hasLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::hasLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
        if ($this->documentManager->hasLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
169
            if ($xWopiLock === $currentLock = $this->documentManager->getLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::getLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
            if ($xWopiLock === $currentLock = $this->documentManager->getLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
170
                return $this->refreshLock($fileId, $accessToken, $xWopiLock, $request);
171
            }
172
173
            return $this
174
                ->psr17
175
                ->createResponse(409)
176
                ->withHeader(WopiInterface::HEADER_LOCK, $currentLock)
177
                ->withHeader(
178
                    WopiInterface::HEADER_ITEM_VERSION,
179
                    sprintf('v%s', $version)
180
                );
181
        }
182
183
        $this->documentManager->lock($document, $xWopiLock);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...anagerInterface::lock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
        $this->documentManager->lock(/** @scrutinizer ignore-type */ $document, $xWopiLock);
Loading history...
184
185
        return $this
186
            ->psr17
187
            ->createResponse()
188
            ->withHeader(
189
                WopiInterface::HEADER_ITEM_VERSION,
190
                sprintf('v%s', $version)
191
            );
192
    }
193
194
    public function putFile(string $fileId, string $accessToken, string $xWopiLock, string $xWopiEditors, RequestInterface $request): ResponseInterface
195
    {
196
        $document = $this->documentManager->findByDocumentId($fileId);
197
        $version = $this->documentManager->getVersion($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...Interface::getVersion() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
        $version = $this->documentManager->getVersion(/** @scrutinizer ignore-type */ $document);
Loading history...
198
199
        // File is unlocked
200
        if (false === $this->documentManager->hasLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::hasLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

200
        if (false === $this->documentManager->hasLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
201
            if ('0' !== $document->getSize()) {
202
                return $this
203
                    ->psr17
204
                    ->createResponse(409)
205
                    ->withHeader(
206
                        WopiInterface::HEADER_ITEM_VERSION,
207
                        sprintf('v%s', $version)
208
                    );
209
            }
210
        }
211
212
        // File is locked
213
        if ($this->documentManager->hasLock($document)) {
214
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::getLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

214
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
215
                return $this
216
                    ->psr17
217
                    ->createResponse(409)
218
                    ->withHeader(
219
                        WopiInterface::HEADER_LOCK,
220
                        $currentLock
221
                    )
222
                    ->withHeader(
223
                        WopiInterface::HEADER_ITEM_VERSION,
224
                        sprintf('v%s', $version)
225
                    );
226
            }
227
        }
228
229
        $body = (string) $request->getBody();
230
231
        $document->setContent($body);
232
        $document->setSize((string) strlen($body));
233
234
        $this->documentManager->write($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...nagerInterface::write() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
        $this->documentManager->write(/** @scrutinizer ignore-type */ $document);
Loading history...
235
        $version = $this->documentManager->getVersion($document);
236
237
        return $this
238
            ->psr17
239
            ->createResponse()
240
            ->withHeader(
241
                WopiInterface::HEADER_LOCK,
242
                $xWopiLock
243
            )
244
            ->withHeader(
245
                WopiInterface::HEADER_ITEM_VERSION,
246
                sprintf('v%s', $version)
247
            );
248
    }
249
250
    public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface
251
    {
252
        if ((null !== $suggestedTarget) && (null !== $relativeTarget)) {
253
            return $this
254
                ->psr17
255
                ->createResponse(400);
256
        }
257
258
        if (null !== $suggestedTarget) {
259
            // If it starts with a dot...
260
            if (0 === strpos($suggestedTarget, '.', 0)) {
261
                $document = $this->documentManager->findByDocumentId($fileId);
262
263
                $suggestedTarget = sprintf('%s%s', $document->getFilename(), $suggestedTarget);
264
            }
265
266
            $target = $suggestedTarget;
267
        }
268
269
        if (null !== $relativeTarget) {
270
            $document = $this->documentManager->findByDocumentFilename($relativeTarget);
271
272
            /**
273
             * If a file with the specified name already exists,
274
             * the host must respond with a 409 Conflict,
275
             * unless the X-WOPI-OverwriteRelativeTarget request header is set to true.
276
             *
277
             * When responding with a 409 Conflict for this reason,
278
             * the host may include an X-WOPI-ValidRelativeTarget specifying a file name that is valid.
279
             *
280
             * If the X-WOPI-OverwriteRelativeTarget request header is set to true
281
             * and a file with the specified name already exists and is locked,
282
             * the host must respond with a 409 Conflict and include an
283
             * X-WOPI-Lock response header containing the value of the current lock on the file.
284
             */
285
            if (null !== $document) {
286
                if (false === $overwriteRelativeTarget) {
287
                    return $this
288
                        ->psr17
289
                        ->createResponse(409)
290
                        ->withHeader('Content-Type', 'application/json')
291
                        ->withHeader(WopiInterface::HEADER_VALID_RELATIVE_TARGET, sprintf('%s.%s', uniqid(), $document->getExtension()));
292
                }
293
294
                if ($this->documentManager->hasLock($document)) {
295
                    return $this
296
                        ->psr17
297
                        ->createResponse(409)
298
                        ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
299
                }
300
            }
301
302
            $target = $relativeTarget;
303
        }
304
305
        $pathInfo = pathinfo($target);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $target does not seem to be defined for all execution paths leading up to this point.
Loading history...
306
307
        $new = $this
308
            ->documentManager
309
            ->create([
310
                'name' => $pathInfo['filename'],
311
                'extension' => $pathInfo['extension'],
312
                'content' => (string) $request->getBody(),
313
                'size' => $request->getHeaderLine(WopiInterface::HEADER_SIZE),
314
            ]);
315
316
        $this->documentManager->write($new);
317
318
        $uri = $this
319
            ->psr17
320
            ->createUri(
321
                $this
322
                    ->router
323
                    ->generate(
324
                        'checkFileInfo',
325
                        [
326
                            'fileId' => $new->getFileId(),
327
                        ],
328
                        RouterInterface::ABSOLUTE_URL
329
                    )
330
            )
331
            ->withQuery(http_build_query([
332
                'access_token' => $accessToken,
333
            ]));
334
335
        $properties = [
336
            'Name' => $new->getBasename(),
337
            'Url' => (string) $uri,
338
            'HostEditUrl' => $new->getId(),
339
            'HostViewUrl' => $new->getId(),
340
        ];
341
342
        return $this
343
            ->psr17
344
            ->createResponse()
345
            ->withHeader('Content-Type', 'application/json')
346
            ->withBody($this->psr17->createStream((string) json_encode($properties)));
347
    }
348
349
    public function putUserInfo(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
350
    {
351
        $userCacheKey = sprintf('wopi_putUserInfo_%s', $this->tokenStorage->getToken()->getUser()->getUserIdentifier());
352
353
        $cacheItem = $this->cache->getItem($userCacheKey);
354
        $cacheItem->set((string) $request->getBody());
355
        $this->cache->save($cacheItem);
356
357
        return $this
358
            ->psr17
359
            ->createResponse();
360
    }
361
362
    public function refreshLock(string $fileId, string $accessToken, string $xWopiLock, RequestInterface $request): ResponseInterface
363
    {
364
        $this->unlock($fileId, $accessToken, $xWopiLock, $request);
365
366
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
367
    }
368
369
    public function renameFile(string $fileId, string $accessToken, string $xWopiLock, string $xWopiRequestedName, RequestInterface $request): ResponseInterface
370
    {
371
        $document = $this->documentManager->findByDocumentId($fileId);
372
373
        if (null === $document) {
374
            return $this
375
                ->psr17
376
                ->createResponse(404);
377
        }
378
379
        if ($this->documentManager->hasLock($document)) {
380
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
381
                return $this
382
                    ->psr17
383
                    ->createResponse(409)
384
                    ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
385
            }
386
        }
387
388
        $document->setFilename($xWopiRequestedName);
389
        $this->documentManager->write($document);
390
391
        $data = [
392
            'Name' => $xWopiRequestedName,
393
        ];
394
395
        return $this
396
            ->psr17
397
            ->createResponse(200)
398
            ->withHeader('Content-Type', 'application/json')
399
            ->withBody(
400
                $this->psr17->createStream((string) json_encode($data))
401
            );
402
    }
403
404
    public function unlock(string $fileId, string $accessToken, string $xWopiLock, RequestInterface $request): ResponseInterface
405
    {
406
        $document = $this->documentManager->findByDocumentId($fileId);
407
        $version = $this->documentManager->getVersion($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...Interface::getVersion() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

407
        $version = $this->documentManager->getVersion(/** @scrutinizer ignore-type */ $document);
Loading history...
408
409
        if (!$this->documentManager->hasLock($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::hasLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

409
        if (!$this->documentManager->hasLock(/** @scrutinizer ignore-type */ $document)) {
Loading history...
410
            return $this
411
                ->psr17
412
                ->createResponse(409)
413
                ->withHeader(WopiInterface::HEADER_LOCK, '');
414
        }
415
416
        $currentLock = $this->documentManager->getLock($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...gerInterface::getLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

416
        $currentLock = $this->documentManager->getLock(/** @scrutinizer ignore-type */ $document);
Loading history...
417
418
        if ($currentLock !== $xWopiLock) {
419
            return $this
420
                ->psr17
421
                ->createResponse(409)
422
                ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
423
        }
424
425
        $this->documentManager->deleteLock($document);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type null; however, parameter $document of ChampsLibres\WopiLib\Con...Interface::deleteLock() does only seem to accept ChampsLibres\WopiLib\Contract\Entity\Document, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
        $this->documentManager->deleteLock(/** @scrutinizer ignore-type */ $document);
Loading history...
426
427
        return $this
428
            ->psr17
429
            ->createResponse()
430
            ->withHeader(WopiInterface::HEADER_LOCK, '')
431
            ->withHeader(
432
                WopiInterface::HEADER_ITEM_VERSION,
433
                sprintf('v%s', $version)
434
            );
435
    }
436
437
    public function unlockAndRelock(string $fileId, string $accessToken, string $xWopiLock, string $xWopiOldLock, RequestInterface $request): ResponseInterface
438
    {
439
        $this->unlock($fileId, $accessToken, $xWopiOldLock, $request);
440
441
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
442
    }
443
}
444