Passed
Pull Request — master (#34)
by
unknown
25:42 queued 10:38
created

Wopi::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 1
eloc 8
c 5
b 1
f 0
nc 1
nop 8
dl 0
loc 18
rs 10
ccs 0
cts 12
cp 0
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\WopiBundle\Service\Wopi\PutFile;
13
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
14
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
15
use DateTimeInterface;
16
use Psr\Cache\CacheItemPoolInterface;
17
use Psr\Http\Message\RequestInterface;
18
19
use Psr\Http\Message\ResponseFactoryInterface;
20
use Psr\Http\Message\ResponseInterface;
21
22
use Psr\Http\Message\StreamFactoryInterface;
23
use Psr\Http\Message\UriFactoryInterface;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\Routing\RouterInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
27
28
use const PATHINFO_EXTENSION;
29
use const PATHINFO_FILENAME;
30
31
final class Wopi implements WopiInterface
32
{
33
    private CacheItemPoolInterface $cache;
34
35
    private DocumentManagerInterface $documentManager;
36
37
    private PutFile $putFileExecutor;
38
39
    private ResponseFactoryInterface $responseFactory;
40
41
    private RouterInterface $router;
42
43
    private StreamFactoryInterface $streamFactory;
44
45
    private TokenStorageInterface $tokenStorage;
46
47
    private UriFactoryInterface $uriFactory;
48
49
    public function __construct(
50
        CacheItemPoolInterface $cache,
51
        DocumentManagerInterface $documentManager,
52
        ResponseFactoryInterface $responseFactory,
53
        RouterInterface $router,
54
        StreamFactoryInterface $streamFactory,
55
        TokenStorageInterface $tokenStorage,
56
        UriFactoryInterface $uriFactory,
57
        PutFile $putFile
58
    ) {
59
        $this->cache = $cache;
60
        $this->documentManager = $documentManager;
61
        $this->responseFactory = $responseFactory;
62
        $this->streamFactory = $streamFactory;
63
        $this->router = $router;
64
        $this->tokenStorage = $tokenStorage;
65
        $this->uriFactory = $uriFactory;
66
        $this->putFileExecutor = $putFile;
67
    }
68
69
    public function checkFileInfo(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
70
    {
71
        $document = $this->documentManager->findByDocumentId($fileId);
72
73
        if (null === $document) {
74
            return $this->makeDocumentNotFoundResponse($fileId);
75
        }
76
77
        $userIdentifier = $this->tokenStorage->getToken()->getUser()->getUserIdentifier();
78
        $userCacheKey = sprintf('wopi_putUserInfo_%s', $this->tokenStorage->getToken()->getUser()->getUserIdentifier());
79
80
        return $this
81
            ->responseFactory
82
            ->createResponse()
83
            ->withHeader('Content-Type', 'application/json')
84
            ->withBody($this->streamFactory->createStream((string) json_encode(
85
                [
86
                    'BaseFileName' => $this->documentManager->getBasename($document),
87
                    'OwnerId' => 'Symfony',
88
                    'Size' => $this->documentManager->getSize($document),
89
                    'UserId' => $userIdentifier,
90
                    'ReadOnly' => false,
91
                    'UserCanAttend' => true,
92
                    'UserCanPresent' => true,
93
                    'UserCanRename' => true,
94
                    'UserCanWrite' => true,
95
                    'UserCanNotWriteRelative' => false,
96
                    'SupportsUserInfo' => true,
97
                    'SupportsDeleteFile' => true,
98
                    'SupportsLocks' => true,
99
                    'SupportsGetLock' => true,
100
                    'SupportsExtendedLockLength' => true,
101
                    'UserFriendlyName' => $userIdentifier,
102
                    'SupportsUpdate' => true,
103
                    'SupportsRename' => true,
104
                    'DisablePrint' => false,
105
                    'AllowExternalMarketplace' => true,
106
                    'SupportedShareUrlTypes' => [
107
                        'ReadOnly',
108
                    ],
109
                    'SHA256' => $this->documentManager->getSha256($document),
110
                    'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(),
111
                    'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)
112
                        ->format(DateTimeInterface::ATOM),
113
                ]
114
            )));
115
    }
116
117
    public function deleteFile(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
118
    {
119
        $document = $this->documentManager->findByDocumentId($fileId);
120
121
        if (null === $document) {
122
            return $this->makeDocumentNotFoundResponse($fileId);
123
        }
124
125
        $this->documentManager->remove($document);
126
127
        return $this
128
            ->responseFactory
129
            ->createResponse(200);
130
    }
131
132
    public function enumerateAncestors(
133
        string $fileId,
134
        string $accessToken,
135
        RequestInterface $request
136
    ): ResponseInterface {
137
        return $this
138
            ->responseFactory
139
            ->createResponse(501);
140
    }
141
142
    public function getFile(
143
        string $fileId,
144
        string $accessToken,
145
        RequestInterface $request
146
    ): ResponseInterface {
147
        $document = $this->documentManager->findByDocumentId($fileId);
148
149
        if (null === $document) {
150
            return $this->makeDocumentNotFoundResponse($fileId);
151
        }
152
153
        $revision = $this->documentManager->getVersion($document);
154
        $content = $this->documentManager->read($document);
155
156
        return $this
157
            ->responseFactory
158
            ->createResponse()
159
            ->withHeader(
160
                WopiInterface::HEADER_ITEM_VERSION,
161
                sprintf('v%s', $revision)
162
            )
163
            ->withHeader(
164
                'Content-Type',
165
                'application/octet-stream',
166
            )
167
            ->withHeader(
168
                'Content-Length',
169
                (string) $this->documentManager->getSize($document)
170
            )
171
            ->withHeader(
172
                'Content-Disposition',
173
                sprintf('attachment; filename=%s', $this->documentManager->getBasename($document))
174
            )
175
            ->withBody($content);
176
    }
177
178
    public function getLock(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
179
    {
180
        $document = $this->documentManager->findByDocumentId($fileId);
181
182
        if (null === $document) {
183
            return $this->makeDocumentNotFoundResponse($fileId);
184
        }
185
186
        if ($this->documentManager->hasLock($document)) {
187
            return $this
188
                ->responseFactory
189
                ->createResponse()
190
                ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
191
        }
192
193
        return $this
194
            ->responseFactory
195
            ->createResponse(404)
196
            ->withHeader(WopiInterface::HEADER_LOCK, '');
197
    }
198
199
    public function getShareUrl(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
200
    {
201
        return $this
202
            ->responseFactory
203
            ->createResponse(501);
204
    }
205
206
    public function lock(
207
        string $fileId,
208
        string $accessToken,
209
        string $xWopiLock,
210
        RequestInterface $request
211
    ): ResponseInterface {
212
        $document = $this->documentManager->findByDocumentId($fileId);
213
214
        if (null === $document) {
215
            return $this->makeDocumentNotFoundResponse($fileId);
216
        }
217
218
        $version = $this->documentManager->getVersion($document);
219
220
        if ($this->documentManager->hasLock($document)) {
221
            if ($xWopiLock === $currentLock = $this->documentManager->getLock($document)) {
222
                return $this->refreshLock($fileId, $accessToken, $xWopiLock, $request);
223
            }
224
225
            return $this
226
                ->responseFactory
227
                ->createResponse(409)
228
                ->withHeader(WopiInterface::HEADER_LOCK, $currentLock)
229
                ->withHeader(
230
                    WopiInterface::HEADER_ITEM_VERSION,
231
                    sprintf('v%s', $version)
232
                );
233
        }
234
235
        $this->documentManager->lock($document, $xWopiLock);
236
237
        return $this
238
            ->responseFactory
239
            ->createResponse()
240
            ->withHeader(
241
                WopiInterface::HEADER_ITEM_VERSION,
242
                sprintf('v%s', $version)
243
            );
244
    }
245
246
    public function putFile(
247
        string $fileId,
248
        string $accessToken,
249
        string $xWopiLock,
250
        string $xWopiEditors,
251
        RequestInterface $request
252
    ): ResponseInterface {
253
        return ($this->putFileExecutor)($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
254
    }
255
256
    public function putRelativeFile(
257
        string $fileId,
258
        string $accessToken,
259
        ?string $suggestedTarget,
260
        ?string $relativeTarget,
261
        bool $overwriteRelativeTarget,
262
        int $size,
263
        RequestInterface $request
264
    ): ResponseInterface {
265
        if ((null === $suggestedTarget) && (null === $relativeTarget)) {
266
            return $this
267
                ->responseFactory
268
                ->createResponse(400)
269
                ->withBody($this->streamFactory->createStream((string) json_encode([
270
                    'message' => 'target is null',
271
                ])));
272
        }
273
274
        if (null !== $suggestedTarget) {
275
            // If it starts with a dot...
276
            if (0 === strpos($suggestedTarget, '.', 0)) {
277
                $document = $this->documentManager->findByDocumentId($fileId);
278
279
                if (null === $document) {
280
                    return $this->makeDocumentNotFoundResponse();
0 ignored issues
show
Bug introduced by
The call to ChampsLibres\WopiBundle\...umentNotFoundResponse() has too few arguments starting with fileId. ( Ignorable by Annotation )

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

280
                    return $this->/** @scrutinizer ignore-call */ makeDocumentNotFoundResponse();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
281
                }
282
                $filename = pathinfo($this->documentManager->getBasename($document), PATHINFO_EXTENSION | PATHINFO_FILENAME);
283
284
                $suggestedTarget = sprintf('%s%s', $filename, $suggestedTarget);
0 ignored issues
show
Bug introduced by
It seems like $filename can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, 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

284
                $suggestedTarget = sprintf('%s%s', /** @scrutinizer ignore-type */ $filename, $suggestedTarget);
Loading history...
285
            }
286
287
            $target = $suggestedTarget;
288
        }
289
290
        if (null !== $relativeTarget) {
291
            $document = $this->documentManager->findByDocumentFilename($relativeTarget);
292
293
            /**
294
             * If a file with the specified name already exists,
295
             * the host must respond with a 409 Conflict,
296
             * unless the X-WOPI-OverwriteRelativeTarget request header is set to true.
297
             *
298
             * When responding with a 409 Conflict for this reason,
299
             * the host may include an X-WOPI-ValidRelativeTarget specifying a file name that is valid.
300
             *
301
             * If the X-WOPI-OverwriteRelativeTarget request header is set to true
302
             * and a file with the specified name already exists and is locked,
303
             * the host must respond with a 409 Conflict and include an
304
             * X-WOPI-Lock response header containing the value of the current lock on the file.
305
             */
306
            if (null !== $document) {
307
                if (false === $overwriteRelativeTarget) {
308
                    $extension = pathinfo($this->documentManager->getBasename($document), PATHINFO_EXTENSION);
309
310
                    return $this
311
                        ->responseFactory
312
                        ->createResponse(409)
313
                        ->withHeader('Content-Type', 'application/json')
314
                        ->withHeader(
315
                            WopiInterface::HEADER_VALID_RELATIVE_TARGET,
316
                            sprintf('%s.%s', uniqid(), $extension)
317
                        );
318
                }
319
320
                if ($this->documentManager->hasLock($document)) {
321
                    return $this
322
                        ->responseFactory
323
                        ->createResponse(409)
324
                        ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
325
                }
326
            }
327
328
            $target = $relativeTarget;
329
        }
330
331
        $pathInfo = pathinfo($target, PATHINFO_EXTENSION | PATHINFO_FILENAME);
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...
332
333
        $new = $this->documentManager->create([
334
            'basename' => $target,
335
            'name' => $pathInfo['filename'],
336
            'extension' => $pathInfo['extension'],
337
            'content' => (string) $request->getBody(),
338
            'size' => $request->getHeaderLine(WopiInterface::HEADER_SIZE),
339
        ]);
340
341
        $this->documentManager->write($new);
342
343
        $uri = $this
344
            ->uriFactory
345
            ->createUri(
346
                $this
347
                    ->router
348
                    ->generate(
349
                        'checkFileInfo',
350
                        [
351
                            'fileId' => $this->documentManager->getDocumentId($new),
352
                        ],
353
                        RouterInterface::ABSOLUTE_URL
354
                    )
355
            )
356
            ->withQuery(http_build_query([
357
                'access_token' => $accessToken,
358
            ]));
359
360
        $properties = [
361
            'Name' => $this->documentManager->getBasename($new),
362
            'Url' => (string) $uri,
363
            'HostEditUrl' => $this->documentManager->getDocumentId($new),
364
            'HostViewUrl' => $this->documentManager->getDocumentId($new),
365
        ];
366
367
        return $this
368
            ->responseFactory
369
            ->createResponse()
370
            ->withHeader('Content-Type', 'application/json')
371
            ->withBody($this->streamFactory->createStream((string) json_encode($properties)));
372
    }
373
374
    public function putUserInfo(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
375
    {
376
        $userCacheKey = sprintf('wopi_putUserInfo_%s', $this->tokenStorage->getToken()->getUser()->getUserIdentifier());
377
378
        $cacheItem = $this->cache->getItem($userCacheKey);
379
        $cacheItem->set((string) $request->getBody());
380
        $this->cache->save($cacheItem);
381
382
        return $this
383
            ->responseFactory
384
            ->createResponse();
385
    }
386
387
    public function refreshLock(
388
        string $fileId,
389
        string $accessToken,
390
        string $xWopiLock,
391
        RequestInterface $request
392
    ): ResponseInterface {
393
        $this->unlock($fileId, $accessToken, $xWopiLock, $request);
394
395
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
396
    }
397
398
    public function renameFile(
399
        string $fileId,
400
        string $accessToken,
401
        string $xWopiLock,
402
        string $xWopiRequestedName,
403
        RequestInterface $request
404
    ): ResponseInterface {
405
        $document = $this->documentManager->findByDocumentId($fileId);
406
407
        if (null === $document) {
408
            return $this->makeDocumentNotFoundResponse($fileId);
409
        }
410
411
        if ($this->documentManager->hasLock($document)) {
412
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
413
                return $this
414
                    ->responseFactory
415
                    ->createResponse(409)
416
                    ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
417
            }
418
        }
419
420
        $this->documentManager->write($document, ['filename' => $xWopiRequestedName]);
421
422
        $data = [
423
            'Name' => $xWopiRequestedName,
424
        ];
425
426
        return $this
427
            ->responseFactory
428
            ->createResponse(200)
429
            ->withHeader('Content-Type', 'application/json')
430
            ->withBody(
431
                $this->streamFactory->createStream((string) json_encode($data))
432
            );
433
    }
434
435
    public function unlock(
436
        string $fileId,
437
        string $accessToken,
438
        string $xWopiLock,
439
        RequestInterface $request
440
    ): ResponseInterface {
441
        $document = $this->documentManager->findByDocumentId($fileId);
442
443
        if (null === $document) {
444
            return $this->makeDocumentNotFoundResponse($fileId);
445
        }
446
447
        $version = $this->documentManager->getVersion($document);
448
449
        if (!$this->documentManager->hasLock($document)) {
450
            return $this
451
                ->responseFactory
452
                ->createResponse(409)
453
                ->withHeader(WopiInterface::HEADER_LOCK, '');
454
        }
455
456
        $currentLock = $this->documentManager->getLock($document);
457
458
        if ($currentLock !== $xWopiLock) {
459
            return $this
460
                ->responseFactory
461
                ->createResponse(409)
462
                ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
463
        }
464
465
        $this->documentManager->deleteLock($document);
466
467
        return $this
468
            ->responseFactory
469
            ->createResponse()
470
            ->withHeader(WopiInterface::HEADER_LOCK, '')
471
            ->withHeader(
472
                WopiInterface::HEADER_ITEM_VERSION,
473
                sprintf('v%s', $version)
474
            );
475
    }
476
477
    public function unlockAndRelock(
478
        string $fileId,
479
        string $accessToken,
480
        string $xWopiLock,
481
        string $xWopiOldLock,
482
        RequestInterface $request
483
    ): ResponseInterface {
484
        $this->unlock($fileId, $accessToken, $xWopiOldLock, $request);
485
486
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
487
    }
488
489
    private function makeDocumentNotFoundResponse(string $fileId): ResponseInterface
490
    {
491
        return $this->responseFactory->createResponse(404)
492
            ->withBody($this->streamFactory->createStream((string) json_encode([
493
                'message' => "Document with id {$fileId} not found",
494
            ])));
495
    }
496
}
497