Completed
Push — feature/EVO-10275-file-hash-le... ( 3c5ea6...ea488a )
by
unknown
22:29
created

FileManager::buildFileDocument()   C

Complexity

Conditions 16
Paths 72

Size

Total Lines 49
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 0
cts 39
cp 0
rs 5.1186
c 0
b 0
f 0
cc 16
eloc 31
nc 72
nop 3
crap 272

How to fix   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
 * Handles file specific actions
4
 */
5
6
namespace Graviton\FileBundle\Manager;
7
8
use Doctrine\ODM\MongoDB\Id\UuidGenerator;
9
use GravitonDyn\FileBundle\Document\File as FileDocument;
10
use GravitonDyn\FileBundle\Document\FileMetadataBase;
11
use GravitonDyn\FileBundle\Document\FileMetadataEmbedded;
12
use League\Flysystem\Filesystem;
13
use Symfony\Component\Form\Exception\InvalidArgumentException;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
17
use Symfony\Component\HttpFoundation\FileBag;
18
use Symfony\Component\HttpFoundation\File\UploadedFile;
19
use GravitonDyn\FileBundle\Document\File as DocumentFile;
20
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
21
use Doctrine\ODM\MongoDB\DocumentManager;
22
use Symfony\Component\Filesystem\Filesystem as SfFileSystem;
23
use GravitonDyn\FileBundle\Model\File as DocumentModel;
24
use Graviton\ExceptionBundle\Exception\NotFoundException;
25
26
/**
27
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
28
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
29
 * @link     http://swisscom.ch
30
 */
31
class FileManager
32
{
33
    /**
34
     * @var Filesystem
35
     */
36
    private $fileSystem;
37
38
    /**
39
     * @var DocumentManager
40
     */
41
    private $documentManager;
42
43
    /** @var array allowedMimeTypes Control files to be saved and returned */
44
    private $allowedMimeTypes = [];
45
46
    /**
47
     * FileManager constructor.
48
     *
49
     * @param Filesystem      $fileSystem      file system abstraction layer for s3 and more
50
     * @param ManagerRegistry $managerRegistry MongoDB registry manager
51
     */
52
    public function __construct(
53
        Filesystem $fileSystem,
54
        ManagerRegistry $managerRegistry
55
    ) {
56
        $this->fileSystem = $fileSystem;
57
        $this->documentManager = $managerRegistry->getManager();
58
    }
59
60
    /**
61
     * Configure allowed content types, empty is equal to all
62
     *
63
     * @param array $mimeTypes of Allowed types, application/pdf, image/jpeg...
64
     *
65
     * @return void
66
     */
67
    public function setAllowedMimeTypes(array $mimeTypes)
68
    {
69
        $this->allowedMimeTypes = $mimeTypes;
70
    }
71
72
    /**
73
     * Will update the response object with provided file data
74
     *
75
     * @param Response     $response response
76
     * @param DocumentFile $file     File document object from DB
77
     *
78
     * @return Response
79
     * @throws InvalidArgumentException if invalid info fetched from fileSystem
80
     */
81
    public function buildGetContentResponse(Response $response, FileDocument $file)
82
    {
83
        /** @var FileMetadataBase $metadata */
84
        $metadata = $file->getMetadata();
85
        if (!$metadata) {
86
            throw new InvalidArgumentException('Loaded file have no valid metadata');
87
        }
88
89
        // We use file's mimeType, just in case none we use DB's.
90
        $mimeType = $this->fileSystem->getMimetype($file->getId());
91
        if (!$mimeType) {
92
            $mimeType = $metadata->getMime();
93
        }
94
        if ($this->allowedMimeTypes && !in_array($mimeType, $this->allowedMimeTypes)) {
95
            throw new InvalidArgumentException('File mime type: '.$mimeType.' is not allowed as response.');
96
        }
97
98
        // Create Response
99
        $disposition = $response->headers->makeDisposition(
100
            ResponseHeaderBag::DISPOSITION_INLINE,
101
            $metadata->getFilename()
102
        );
103
104
        $response
105
            ->setStatusCode(Response::HTTP_OK)
106
            ->setContent($this->fileSystem->read($file->getId()));
107
        $response
108
            ->headers->set('Content-Type', $mimeType);
109
        $response
110
            ->headers->set('Content-Disposition', $disposition);
111
        return $response;
112
    }
113
114
115
    /**
116
     * Save or update a file
117
     *
118
     * @param string $id       ID of file
119
     * @param String $filepath path to the file to save
120
     *
121
     * @return void
122
     */
123
    public function saveFile($id, $filepath)
124
    {
125
        // will save using a stream
126
        $fp = fopen($filepath, 'r+');
127
128
        $this->fileSystem->putStream($id, $fp);
129
130
        // close file
131
        fclose($fp);
132
    }
133
134
    /**
135
     * @param DocumentFile  $document File Document
136
     * @param Request       $request  Request bag
137
     * @param DocumentModel $model    File Document Model
138
     * @return DocumentFile
139
     */
140
    public function handleSaveRequest(
141
        FileDocument $document,
142
        Request $request,
143
        DocumentModel $model
144
    ) {
145
        $file = $this->getUploadedFileFromRequest($request);
146
        $requestId = $request->get('id', '');
147
        if ($requestId && !$document->getId()) {
148
            $document->setId($requestId);
149
        }
150
151
        try {
152
            $original = $model->find($requestId);
153
        } catch (NotFoundException $e) {
154
            $original = false;
155
        }
156
157
        $isNew = $requestId ? !$original : true;
158
159
        // If posted  file document not equal the one to be created or updated, then error
160
        if (!$this->validIdRequest($document, $requestId)) {
161
            throw new InvalidArgumentException('File id and Request id must match.');
162
        }
163
164
        $document = $this->buildFileDocument($document, $file, $original);
165
        if (!$document->getId()) {
166
            $n = new UuidGenerator();
167
            $uuid = (string) $n->generateV4();
168
            $document->setId($uuid);
169
        }
170
171
        // Filename limitation
172
        if ($filename = $document->getMetadata()->getFilename()) {
173
            // None English chars
174
            if (preg_match('/[^a-z_\-0-9.]/i', $filename)) {
175
                throw new InvalidArgumentException('None special chars allowed for filename, given: '.$filename);
176
            }
177
        }
178
179
        // All ok, let's save the file
180
        if ($isNew) {
181
            if (!$file || $file->getSize() == 0) {
182
                throw new InvalidArgumentException('You can not create a new empty file resource. No file received.');
183
            }
184
        }
185
186
        if ($file) {
187
            $this->saveFile($document->getId(), $file->getRealPath());
188
            $sfFileSys = new SfFileSystem();
189
            $sfFileSys->remove($file->getRealPath());
190
        }
191
192
        if ($isNew) {
193
            $model->insertRecord($document);
194
        } else {
195
            $model->updateRecord($document->getId(), $document);
196
        }
197
198
        // store id of new record so we don't need to re-parse body later when needed
199
        $request->attributes->set('id', $document->getId());
200
201
        return $document;
202
    }
203
204
    /**
205
     * Create the basic needs for a file
206
     *
207
     * @param DocumentFile $document Post or Put file document
208
     * @param UploadedFile $file     To be used in set metadata
209
     * @param DocumentFile $original If there is a original document
210
     *
211
     * @return DocumentFile
212
     * @throws InvalidArgumentException
213
     */
214
    private function buildFileDocument(FileDocument $document, $file, $original)
215
    {
216
        $now = new \DateTime();
217
218
        // If only a file is posted, check if there is a original object and clone it
219
        if ($file && $original && !$document->getMetadata()) {
220
            $document = clone $original;
221
        }
222
223
        // Basic Metadata update
224
        $metadata = $document->getMetadata() ?: new FileMetadataEmbedded();
225
226
        // File related, if no file uploaded we keep original file info.
227
        if ($file) {
228
            $hash = $metadata->getHash();
229
            if (!$hash || strlen($hash)>64) {
230
                $hash = hash('sha256', file_get_contents($file->getRealPath()));
231
            } else {
232
                $hash = preg_replace('/[^a-z0-9_-]/i', '-', $hash);
233
            }
234
            $metadata->setHash($hash);
235
            $metadata->setMime($file->getMimeType());
236
            $metadata->setSize($file->getSize());
237
            if (!$metadata->getFilename()) {
238
                $fileName = $file->getClientOriginalName() ? $file->getClientOriginalName() : $file->getFilename();
239
                $fileName = preg_replace("/[^a-zA-Z0-9.]/", "-", $fileName);
240
                $metadata->setFilename($fileName);
241
            }
242
        } elseif ($original && ($originalMetadata = $original->getMetadata())) {
243
            if (!$metadata->getFilename()) {
244
                $metadata->setFilename($originalMetadata->getFilename());
245
            }
246
            $metadata->setHash($originalMetadata->getHash());
247
            $metadata->setMime($originalMetadata->getMime());
248
            $metadata->setSize($originalMetadata->getSize());
249
        }
250
251
        // Creation date. keep original if available
252
        if ($original && $original->getMetadata() && $original->getMetadata()->getCreatedate()) {
253
            $metadata->setCreatedate($original->getMetadata()->getCreatedate());
254
        } else {
255
            $metadata->setCreatedate($now);
256
        }
257
258
        $metadata->setModificationdate($now);
259
        $document->setMetadata($metadata);
260
261
        return $document;
262
    }
263
264
    /**
265
     * Simple validation for post/put request
266
     *
267
     * @param DocumentFile $document  File document
268
     * @param string       $requestId Request ID
269
     * @return bool
270
     */
271
    private function validIdRequest(FileDocument $document, $requestId)
0 ignored issues
show
Coding Style introduced by
function validIdRequest() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
272
    {
273
        if (!$requestId && !$document->getId()) {
274
            return true;
275
        }
276
        if ($requestId === $document->getId()) {
277
            return true;
278
        }
279
        return false;
280
    }
281
282
    /**
283
     * Simple delete item from file system
284
     *
285
     * @param string $id ID of file to be deleted
286
     *
287
     * @return void
288
     */
289
    public function remove($id)
290
    {
291
        if ($this->fileSystem->has($id)) {
292
            $this->fileSystem->delete($id);
293
        }
294
    }
295
296
    /**
297
     * Set global uploaded file.
298
     * Only ONE file allowed per upload.
299
     *
300
     * @param Request $request service request
301
     * @return UploadedFile if file was uploaded
302
     * @throws InvalidArgumentException
303
     */
304
    private function getUploadedFileFromRequest(Request $request)
305
    {
306
        $file = false;
307
308
        if ($request->files instanceof FileBag && $request->files->count() > 0) {
309
            if ($request->files->count() > 1) {
310
                throw new InvalidArgumentException('Only 1 file upload per requests allowed.');
311
            }
312
            $files = $request->files->all();
313
            $file = reset($files);
314
            if ($this->allowedMimeTypes && !in_array($file->getMimeType(), $this->allowedMimeTypes)) {
315
                throw new InvalidArgumentException('File mime type: '.$file->getMimeType().' is not allowed.');
316
            }
317
        }
318
319
        return $file;
320
    }
321
}
322