PutFile::__invoke()   C
last analyzed

Complexity

Conditions 14
Paths 19

Size

Total Lines 154
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 89
c 1
b 0
f 0
nc 19
nop 5
dl 0
loc 154
ccs 0
cts 110
cp 0
crap 210
rs 5.2533

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ChampsLibres\WopiBundle\Service\Wopi;
6
7
use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
8
use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
9
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
10
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
11
use DateTimeImmutable;
12
use DateTimeInterface;
13
use Psr\Http\Message\RequestInterface;
14
use Psr\Http\Message\ResponseFactoryInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\StreamFactoryInterface;
17
use Psr\Log\LoggerInterface;
18
use RuntimeException;
19
20
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
21
22
use function strlen;
23
24
class PutFile
25
{
26
    private const LOG_PREFIX = '[wopi][wopi/PutFile] ';
27
28
    private AuthorizationManagerInterface $authorizationManager;
29
30
    private DocumentManagerInterface $documentManager;
31
32
    private bool $enableLock;
33
34
    private LoggerInterface $logger;
35
36
    private ResponseFactoryInterface $responseFactory;
37
38
    private StreamFactoryInterface $streamFactory;
39
40
    private UserManagerInterface $userManager;
41
42
    /**
43
     * @var 'version'|'timestamp'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'version'|'timestamp' at position 0 could not be parsed: Unknown type name ''version'' at position 0 in 'version'|'timestamp'.
Loading history...
44
     */
45
    private string $versionManagement;
46
47
    public function __construct(
48
        AuthorizationManagerInterface $authorizationManager,
49
        DocumentManagerInterface $documentManager,
50
        LoggerInterface $logger,
51
        ParameterBagInterface $parameterBag,
52
        ResponseFactoryInterface $responseFactory,
53
        StreamFactoryInterface $streamFactory,
54
        UserManagerInterface $userManager
55
    ) {
56
        $this->documentManager = $documentManager;
57
        $this->logger = $logger;
58
        $this->responseFactory = $responseFactory;
59
        $this->streamFactory = $streamFactory;
60
        $this->userManager = $userManager;
61
        $this->authorizationManager = $authorizationManager;
62
        $this->versionManagement = $parameterBag->get('wopi')['version_management'];
63
        $this->enableLock = $parameterBag->get('wopi')['enable_lock'];
64
    }
65
66
    public function __invoke(
67
        string $fileId,
68
        string $accessToken,
69
        string $xWopiLock,
70
        string $xWopiEditors,
71
        RequestInterface $request
72
    ): ResponseInterface {
73
        $this->logger->debug(self::LOG_PREFIX . 'put file', ['fileId' => $fileId]);
74
        $document = $this->documentManager->findByDocumentId($fileId);
75
76
        if (null === $document) {
77
            return $this->responseFactory->createResponse(404)
78
                ->withBody($this->streamFactory->createStream((string) json_encode([
79
                    'message' => "Document with id {$fileId} not found",
80
                ])));
81
        }
82
83
        if (!$this->authorizationManager->userCanWrite($accessToken, $document, $request)) {
84
            $userIdentifier = $this->userManager->getUserId($accessToken, $fileId, $request);
85
            $this->logger->info(self::LOG_PREFIX . 'user is not allowed to write document', ['fileId' => $fileId, 'userIdentifier' => $userIdentifier]);
86
87
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
88
                'message' => 'user is not allowed to write this document',
89
            ])));
90
        }
91
92
        $version = $this->documentManager->getVersion($document);
93
94
        // File is unlocked
95
        if ($this->enableLock && false === $this->documentManager->hasLock($document)) {
96
            if (0 !== $this->documentManager->getSize($document)) {
97
                $this->logger->error(self::LOG_PREFIX . 'file unlocked', ['fileId' => $fileId]);
98
99
                return $this
100
                    ->responseFactory
101
                    ->createResponse(409)
102
                    ->withHeader(
103
                        WopiInterface::HEADER_ITEM_VERSION,
104
                        sprintf('v%s', $version)
105
                    );
106
            }
107
        }
108
109
        // File is locked
110
        if (
111
            $this->enableLock
112
            && $this->documentManager->hasLock($document)
113
            && $xWopiLock !== $currentLock = $this->documentManager->getLock($document)
114
        ) {
115
            $this->logger->error(self::LOG_PREFIX . 'file locked and lock does not match', ['fileId' => $fileId]);
116
117
            return $this
118
                ->responseFactory
119
                ->createResponse(409)
120
                ->withHeader(
121
                    WopiInterface::HEADER_LOCK,
122
                    $currentLock
123
                )
124
                ->withHeader(
125
                    WopiInterface::HEADER_ITEM_VERSION,
126
                    sprintf('v%s', $version)
127
                )
128
                ->withBody(
129
                    $this->streamFactory->createStream((string) json_encode([
130
                        'message' => 'File locked',
131
                    ]))
132
                );
133
        }
134
135
        // for collabora online editor, check timestamp if present
136
        if ($request->hasHeader('x-lool-wopi-timestamp')) {
137
            $date = DateTimeImmutable::createFromFormat(
138
                DateTimeImmutable::ATOM,
139
                $request->getHeader('x-lool-wopi-timestamp')[0]
140
            );
141
142
            if (false === $date) {
143
                $errors = DateTimeImmutable::getLastErrors();
144
145
                if (false === $errors) {
146
                    throw new RuntimeException('Could not find error on DateTimeImmutable parsing');
147
                }
148
149
                $e = array_merge($errors['warnings'], $errors['errors']);
150
151
                $this->logger->error(self::LOG_PREFIX . 'Error parsing date', ['fileId' => $fileId,
152
                    'date' => $request->getHeader('x-lool-wopi-timestamp')[0], 'errors' => $e]);
153
154
                throw new RuntimeException('Error parsing date: ' . implode(', ', $e));
155
            }
156
157
            if ($this->documentManager->getLastModifiedDate($document) > $date) {
158
                $this->logger->error(self::LOG_PREFIX . 'File has more recent modified date', ['fileId' => $fileId]);
159
160
                return $this
161
                    ->responseFactory
162
                    ->createResponse(409)
163
                    ->withHeader(
164
                        WopiInterface::HEADER_LOCK,
165
                        $currentLock ?? ''
166
                    )
167
                    ->withHeader(
168
                        WopiInterface::HEADER_ITEM_VERSION,
169
                        sprintf('v%s', $version)
170
                    )
171
                    ->withBody(
172
                        $this->streamFactory->createStream(
173
                            (string) json_encode(
174
                                [
175
                                    'LOOLStatusCode' => 1010,
176
                                    'COOLStatusCode' => 1010,
177
                                ]
178
                            )
179
                        )
180
                    );
181
            }
182
        }
183
184
        $body = (string) $request->getBody();
185
        $this->documentManager->write(
186
            $document,
187
            [
188
                'content' => $body,
189
                'size' => strlen($body),
190
            ]
191
        );
192
        $version = $this->documentManager->getVersion($document);
193
194
        $response = $this
195
            ->responseFactory
196
            ->createResponse()
197
            ->withHeader(
198
                WopiInterface::HEADER_LOCK,
199
                $xWopiLock
200
            )
201
            ->withHeader(
202
                WopiInterface::HEADER_ITEM_VERSION,
203
                sprintf('v%s', $version)
204
            );
205
206
        if ('timestamp' === $this->versionManagement) {
207
            return $response
208
                ->withBody(
209
                    $this->streamFactory->createStream(
210
                        (string) json_encode([
211
                            'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)->format(DateTimeInterface::ATOM),
212
                        ])
213
                    )
214
                );
215
        }
216
217
        $this->logger->info(self::LOG_PREFIX . 'file saved', ['fileId' => $fileId]);
218
219
        return $response;
220
    }
221
}
222