FileManager::setAllowedMimeTypes()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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  https://opensource.org/licenses/MIT MIT 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
    /**
44
     * @var array allowedMimeTypes Control files to be saved and returned
45
     */
46
    private $allowedMimeTypes = [];
47
48
    /**
49
     * @var bool whether or not we should read the files mimetype or trust the database (depending on storage)
50
     */
51
    private $readFileSystemMimeType = false;
52
53
    /**
54
     * FileManager constructor.
55
     *
56
     * @param Filesystem      $fileSystem      file system abstraction layer for s3 and more
57
     * @param ManagerRegistry $managerRegistry MongoDB registry manager
58
     */
59
    public function __construct(
60
        Filesystem $fileSystem,
61
        ManagerRegistry $managerRegistry
62
    ) {
63
        $this->fileSystem = $fileSystem;
64
        $this->documentManager = $managerRegistry->getManager();
65
    }
66
67
    /**
68
     * Configure allowed content types, empty is equal to all
69
     *
70
     * @param array $mimeTypes of Allowed types, application/pdf, image/jpeg...
71
     *
72
     * @return void
73
     */
74
    public function setAllowedMimeTypes(array $mimeTypes)
75
    {
76
        $this->allowedMimeTypes = $mimeTypes;
77
    }
78
79
    /**
80
     * set ReadFileSystemMimeType
81
     *
82
     * @param bool $readFileSystemMimeType readFileSystemMimeType
83
     *
84
     * @return void
85
     */
86
    public function setReadFileSystemMimeType($readFileSystemMimeType)
87
    {
88
        $this->readFileSystemMimeType = $readFileSystemMimeType;
89
    }
90
91
    /**
92
     * Will update the response object with provided file data
93
     *
94
     * @param Response     $response response
95
     * @param DocumentFile $file     File document object from DB
96
     *
97
     * @return Response
98
     * @throws InvalidArgumentException if invalid info fetched from fileSystem
99
     */
100
    public function buildGetContentResponse(Response $response, FileDocument $file)
101
    {
102
        /** @var FileMetadataBase $metadata */
103
        $metadata = $file->getMetadata();
104
        if (!$metadata) {
105
            throw new InvalidArgumentException('Loaded file have no valid metadata');
106
        }
107
108
        // We use file's mimeType, just in case none we use DB's.
109
        $mimeType = null;
110
111
        if ($this->readFileSystemMimeType) {
112
            $mimeType = $this->fileSystem->getMimetype($file->getId());
113
        }
114
        if (!$mimeType) {
115
            $mimeType = $metadata->getMime();
116
        }
117
        if ($this->allowedMimeTypes && !in_array($mimeType, $this->allowedMimeTypes)) {
118
            throw new InvalidArgumentException('File mime type: '.$mimeType.' is not allowed as response.');
119
        }
120
121
        // Create Response
122
        $disposition = $response->headers->makeDisposition(
123
            ResponseHeaderBag::DISPOSITION_INLINE,
124
            $metadata->getFilename()
125
        );
126
127
        $response
128
            ->setStatusCode(Response::HTTP_OK)
129
            ->setContent($this->fileSystem->read($file->getId()));
130
        $response
131
            ->headers->set('Content-Type', $mimeType);
132
        $response
133
            ->headers->set('Content-Disposition', $disposition);
134
        return $response;
135
    }
136
137
138
    /**
139
     * Save or update a file
140
     *
141
     * @param string $id       ID of file
142
     * @param String $filepath path to the file to save
143
     *
144
     * @return void
145
     */
146
    public function saveFile($id, $filepath)
147
    {
148
        // will save using a stream
149
        $fp = fopen($filepath, 'r+');
150
151
        $this->fileSystem->putStream($id, $fp);
152
153
        // close file
154
        fclose($fp);
155
    }
156
157
    /**
158
     * @param DocumentFile  $document File Document
159
     * @param Request       $request  Request bag
160
     * @param DocumentModel $model    File Document Model
161
     * @return DocumentFile
162
     */
163
    public function handleSaveRequest(
164
        FileDocument $document,
165
        Request $request,
166
        DocumentModel $model
167
    ) {
168
        $file = $this->getUploadedFileFromRequest($request);
169
        $requestId = $request->get('id', '');
170
        if ($requestId && !$document->getId()) {
171
            $document->setId($requestId);
172
        }
173
174
        try {
175
            $original = $model->find($requestId, true);
176
        } catch (NotFoundException $e) {
177
            $original = false;
178
        }
179
180
        $isNew = $requestId ? !$original : true;
181
182
        // If posted  file document not equal the one to be created or updated, then error
183
        if (!$this->validIdRequest($document, $requestId)) {
184
            throw new InvalidArgumentException('File id and Request id must match.');
185
        }
186
187
        $document = $this->buildFileDocument($document, $file, $original);
188
        if (!$document->getId()) {
189
            $n = new UuidGenerator();
190
            $uuid = (string) $n->generateV4();
191
            $document->setId($uuid);
192
        }
193
194
        // Filename limitation
195
        if ($filename = $document->getMetadata()->getFilename()) {
196
            // None English chars
197
            if (preg_match('/[^a-z_\-0-9.]/i', $filename)) {
198
                throw new InvalidArgumentException('None special chars allowed for filename, given: '.$filename);
199
            }
200
        }
201
202
        // All ok, let's save the file
203
        if ($isNew) {
204
            if (!$file || $file->getSize() == 0) {
205
                throw new InvalidArgumentException('You can not create a new empty file resource. No file received.');
206
            }
207
        }
208
209
        if ($file) {
210
            $this->saveFile($document->getId(), $file->getRealPath());
211
            $sfFileSys = new SfFileSystem();
212
            $sfFileSys->remove($file->getRealPath());
213
        }
214
215
        if ($isNew) {
216
            $model->insertRecord($document);
217
        } else {
218
            $model->updateRecord($document->getId(), $document);
219
        }
220
221
        // store id of new record so we don't need to re-parse body later when needed
222
        $request->attributes->set('id', $document->getId());
223
224
        return $document;
225
    }
226
227
    /**
228
     * Create the basic needs for a file
229
     *
230
     * @param DocumentFile $document Post or Put file document
231
     * @param UploadedFile $file     To be used in set metadata
232
     * @param DocumentFile $original If there is a original document
233
     *
234
     * @return DocumentFile
235
     * @throws InvalidArgumentException
236
     */
237
    private function buildFileDocument(FileDocument $document, $file, $original)
238
    {
239
        $now = new \DateTime();
240
241
        // If only a file is posted, check if there is a original object and clone it
242
        if ($file && $original && !$document->getMetadata()) {
243
            $document = clone $original;
244
        }
245
246
        // Basic Metadata update
247
        $metadata = $document->getMetadata() ?: new FileMetadataEmbedded();
248
249
        // File related, if no file uploaded we keep original file info.
250
        if ($file) {
251
            $hash = $metadata->getHash();
252
            if (!$hash || strlen($hash)>64) {
253
                $hash = hash('sha256', file_get_contents($file->getRealPath()));
254
            } else {
255
                $hash = preg_replace('/[^a-z0-9_-]/i', '-', $hash);
256
            }
257
            $metadata->setHash($hash);
258
            $metadata->setMime($file->getMimeType());
259
            $metadata->setSize($file->getSize());
260
            if (!$metadata->getFilename()) {
261
                $fileName = $file->getClientOriginalName() ? $file->getClientOriginalName() : $file->getFilename();
262
                $fileName = preg_replace("/[^a-zA-Z0-9.]/", "-", $fileName);
263
                $metadata->setFilename($fileName);
264
            }
265
        } elseif ($original && ($originalMetadata = $original->getMetadata())) {
266
            if (!$metadata->getFilename()) {
267
                $metadata->setFilename($originalMetadata->getFilename());
268
            }
269
            $metadata->setHash($originalMetadata->getHash());
270
            $metadata->setMime($originalMetadata->getMime());
271
            $metadata->setSize($originalMetadata->getSize());
272
        }
273
274
        // Creation date. keep original if available
275
        if ($original && $original->getMetadata() && $original->getMetadata()->getCreatedate()) {
276
            $metadata->setCreatedate($original->getMetadata()->getCreatedate());
277
        } else {
278
            $metadata->setCreatedate($now);
279
        }
280
281
        $metadata->setModificationdate($now);
282
        $document->setMetadata($metadata);
283
284
        return $document;
285
    }
286
287
    /**
288
     * Simple validation for post/put request
289
     *
290
     * @param DocumentFile $document  File document
291
     * @param string       $requestId Request ID
292
     * @return bool
293
     */
294
    private function validIdRequest(FileDocument $document, $requestId)
295
    {
296
        if (!$requestId && !$document->getId()) {
297
            return true;
298
        }
299
        if ($requestId === $document->getId()) {
300
            return true;
301
        }
302
        return false;
303
    }
304
305
    /**
306
     * Simple delete item from file system
307
     *
308
     * @param string $id ID of file to be deleted
309
     *
310
     * @return void
311
     */
312
    public function remove($id)
313
    {
314
        if ($this->fileSystem->has($id)) {
315
            $this->fileSystem->delete($id);
316
        }
317
    }
318
319
    /**
320
     * Set global uploaded file.
321
     * Only ONE file allowed per upload.
322
     *
323
     * @param Request $request service request
324
     * @return UploadedFile if file was uploaded
325
     * @throws InvalidArgumentException
326
     */
327
    private function getUploadedFileFromRequest(Request $request)
328
    {
329
        $file = false;
330
331
        if ($request->files instanceof FileBag && $request->files->count() > 0) {
332
            if ($request->files->count() > 1) {
333
                throw new InvalidArgumentException('Only 1 file upload per requests allowed.');
334
            }
335
            $files = $request->files->all();
336
            $file = reset($files);
337
            if ($this->allowedMimeTypes && !in_array($file->getMimeType(), $this->allowedMimeTypes)) {
338
                throw new InvalidArgumentException('File mime type: '.$file->getMimeType().' is not allowed.');
339
            }
340
        }
341
342
        return $file;
343
    }
344
}
345