Completed
Push — master ( 471441...a5fa89 )
by
unknown
16:03
created

FileManager::handleSaveRequest()   D

Complexity

Conditions 14
Paths 312

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
dl 0
loc 63
ccs 0
cts 48
cp 0
rs 4.5048
c 0
b 0
f 0
cc 14
eloc 36
nc 312
nop 3
crap 210

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