Completed
Push — master ( 19c8d0...561fdf )
by Lucas
10:48
created

FileManager::saveFiles()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 28
ccs 16
cts 16
cp 1
rs 8.8571
cc 2
eloc 15
nc 2
nop 3
crap 2
1
<?php
2
/**
3
 * Handles file specific actions
4
 */
5
6
namespace Graviton\FileBundle;
7
8
use Gaufrette\File;
9
use Gaufrette\FileSystem;
10
use Graviton\ExceptionBundle\Exception\MalformedInputException;
11
use Graviton\RestBundle\Model\DocumentModel;
12
use GravitonDyn\FileBundle\Document\File as FileDocument;
13
use GravitonDyn\FileBundle\Document\FileMetadata;
14
use Symfony\Component\HttpFoundation\File\Exception\UploadException;
15
use Symfony\Component\HttpFoundation\File\UploadedFile;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
18
19
/**
20
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
21
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
22
 * @link     http://swisscom.ch
23
 */
24
class FileManager
25
{
26
    /**
27
     * @var FileSystem
28
     */
29
    private $fileSystem;
30
31
    /**
32
     * @var FileDocumentFactory
33
     */
34
    private $fileDocumentFactory;
35
36
    /**
37
     * FileManager constructor.
38
     *
39
     * @param FileSystem          $fileSystem          file system abstraction layer for s3 and more
40
     * @param FileDocumentFactory $fileDocumentFactory Instance to be used to create action entries.
41
     */
42 4
    public function __construct(FileSystem $fileSystem, FileDocumentFactory $fileDocumentFactory)
43
    {
44 4
        $this->fileSystem = $fileSystem;
45 4
        $this->fileDocumentFactory = $fileDocumentFactory;
46 4
    }
47
48
    /**
49
     * Indicates whether the file matching the specified key exists
50
     *
51
     * @param string $key Identifier to be found
52
     *
53
     * @return boolean TRUE if the file exists, FALSE otherwise
54
     */
55 2
    public function has($key)
56
    {
57 2
        return $this->fileSystem->has($key);
58
    }
59
60
    /**
61
     * Deletes the file matching the specified key
62
     *
63
     * @param string $key Identifier to be deleted
64
     *
65
     * @throws \RuntimeException when cannot read file
66
     *
67
     * @return boolean
68
     */
69 2
    public function delete($key)
0 ignored issues
show
Coding Style introduced by
function delete() 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...
70
    {
71 2
        return $this->fileSystem->delete($key);
72
    }
73
74
    /**
75
     * Reads the content from the file
76
     *
77
     * @param  string $key Key of the file
78
     *
79
     * @throws \Gaufrette\Exception\FileNotFound when file does not exist
80
     * @throws \RuntimeException                 when cannot read file
81
     *
82
     * @return string
83
     */
84 1
    public function read($key)
85
    {
86 1
        return $this->fileSystem->read($key);
87
    }
88
89
    /**
90
     * Stores uploaded files to CDN
91
     *
92
     * @param Request           $request  Current Http request
93
     * @param DocumentModel     $model    Model to be used to manage entity
94
     * @param FileDocument|null $fileData meta information about the file to be stored.
95
     *
96
     * @return array
97
     */
98 1
    public function saveFiles(Request $request, DocumentModel $model, FileDocument $fileData = null)
99
    {
100 1
        $inStore = [];
101 1
        $files = $this->extractUploadedFiles($request);
102
103 1
        foreach ($files as $key => $fileInfo) {
104
            /** @var FileDocument $record */
105 1
            $record = $this->getRecord($model, $fileData, $request->get('id'));
106 1
            $inStore[] = $record->getId();
107
108
            /** @var \Gaufrette\File $file */
109 1
            $file = $this->saveFile($record->getId(), $fileInfo['content']);
110
111 1
            $this->initOrUpdateMetadata(
112 1
                $record,
113 1
                $file->getSize(),
114 1
                $fileInfo,
115
                $fileData
116 1
            );
117
118 1
            $model->updateRecord($record->getId(), $record);
119
120
            // TODO NOTICE: ONLY UPLOAD OF ONE FILE IS CURRENTLY SUPPORTED
121 1
            break;
122 1
        }
123
124 1
        return $inStore;
125
    }
126
127
    /**
128
     * Save or update a file
129
     *
130
     * @param string $id   ID of file
131
     * @param String $data content to save
132
     *
133
     * @return File
134
     *
135
     * @throws BadRequestHttpException
136
     */
137 1
    public function saveFile($id, $data)
138
    {
139 1
        if (is_resource($data)) {
140
            throw new BadRequestHttpException('/file does not support storing resources');
141
        }
142 1
        $file = new File($id, $this->fileSystem);
143 1
        $file->setContent($data);
144
145 1
        return $file;
146
    }
147
148
    /**
149
     * Moves uploaded files to tmp directory
150
     *
151
     * @param Request $request Current http request
152
     *
153
     * @return array
154
     */
155 1
    private function extractUploadedFiles(Request $request)
156
    {
157 1
        $uploadedFiles = [];
158
159
        /** @var  $uploadedFile \Symfony\Component\HttpFoundation\File\UploadedFile */
160 1
        foreach ($request->files->all() as $field => $uploadedFile) {
161 1
            if (0 === $uploadedFile->getError()) {
162 1
                $uploadedFiles[$field] = [
163
                    'data' => [
164 1
                        'mimetype' => $uploadedFile->getMimeType(),
165 1
                        'filename' => $uploadedFile->getClientOriginalName()
166 1
                    ],
167 1
                    'content' => file_get_contents($uploadedFile->getPathName())
168 1
                ];
169 1
            } else {
170
                throw new UploadException($uploadedFile->getErrorMessage());
171
            }
172 1
        }
173
174 1
        if (empty($uploadedFiles)) {
175
            $uploadedFiles['upload'] = [
176
                'data' => [
177
                    'mimetype' => $request->headers->get('Content-Type'),
178
                    'filename' => ''
179
                ],
180
                'content' => $request->getContent()
181
            ];
182
        }
183
184 1
        return $uploadedFiles;
185
    }
186
187
    /**
188
     * Provides a set up instance of the file document
189
     *
190
     * @param DocumentModel     $model    Document model
191
     * @param FileDocument|null $fileData File information
192
     * @param string            $id       Alternative Id to be checked
193
     *
194
     * @return FileDocument
195
     */
196 1
    private function getRecord(DocumentModel $model, FileDocument $fileData = null, $id = '')
197
    {
198
        // does it really exist??
199 1
        if (!empty($fileData)) {
200 1
            $record = $model->find($fileData->getId());
201 1
        }
202 1
        if (empty($record) && !empty($id)) {
203
            $record = $model->find($id);
204
        }
205
206 1
        if (!empty($record)) {
207
            // handle missing 'id' field in input to a PUT operation
208
            // if it is settable on the document, let's set it and move on.. if not, inform the user..
209 View Code Duplication
            if ($record->getId() != $id) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
                // try to set it..
211
                if (is_callable(array($fileData, 'setId'))) {
212
                    $record->setId($id);
213
                } else {
214
                    throw new MalformedInputException('No ID was supplied in the request payload.');
215
                }
216
            }
217
218
            return $model->updateRecord($id, $record);
219
        }
220
221 1
        if (!empty($fileData)) {
222 1
            $fId = $fileData->getId();
223 1
            if (empty($fId) && !empty($id)) {
224
                $fileData->setId($id);
225
            }
226 1
            $record = $fileData;
227 1
        } else {
228
            $entityClass = $model->getEntityClass();
229
            $record = new $entityClass();
230
        }
231
232 1
        return $model->insertRecord($record);
233
    }
234
235
    /**
236
     * Updates or initialzes the metadata information of the current entity.
237
     *
238
     * @param FileDocument $file     Document to be used
239
     * @param integer      $fileSize Size of the uploaded file
240
     * @param array        $fileInfo Additional info about the file
241
     * @param FileDocument $fileData File data to be updated
242
     *
243
     * @return void
244
     */
245 1
    private function initOrUpdateMetadata(FileDocument $file, $fileSize, array $fileInfo, FileDocument $fileData = null)
246
    {
247 1
        $meta = $file->getMetadata();
248 1
        $actions = [];
249 1
        $additionalInfo = '';
250
251 1
        if (!empty($fileData)) {
252 1
            $actions = !empty($actions = $fileData->getMetadata()->getAction()->toArray()) ? $actions : [];
253 1
            $additionalInfo = $fileData->getMetadata()->getAdditionalinformation();
254 1
            $file->setLinks(!empty($links = $fileData->getLinks()->toArray()) ? $links : []);
255 1
        }
256
257 1
        if (!empty($meta)) {
258 1
            $actions = (empty($actions)) ? $meta->getAction()->toArray() : $actions;
259 1
            $additionalInfo = empty($additionalInfo) ? $meta->getAdditionalinformation() : $additionalInfo;
260
            $meta
261 1
                ->setAction($actions)
262 1
                ->setAdditionalInformation($additionalInfo)
263 1
                ->setSize((int) $fileSize)
264 1
                ->setModificationdate(new \DateTime());
265
266 1
            if (!empty($fileInfo['data']['mimetype'])) {
267 1
                $meta->setMime($fileInfo['data']['mimetype']);
268 1
            }
269 1
            if (!empty($fileInfo['data']['filename'])) {
270 1
                $meta->setFilename($fileInfo['data']['filename']);
271 1
            }
272
273 1
        } else {
274
            // update record with file metadata
275
            $meta = $this->fileDocumentFactory->initiateFileMataData(
276
                $file->getId(),
277
                (int) $fileSize,
278
                $fileInfo['data']['filename'],
279
                $fileInfo['data']['mimetype'],
280
                $actions,
281
                $additionalInfo
282
            );
283
        }
284
285 1
        $file->setMetadata($meta);
286 1
    }
287
288
    /**
289
     * Extracts different information sent in the request content.
290
     *
291
     * @param Request $request Current http request
292
     *
293
     * @return array
294
     */
295
    public function extractDataFromRequestContent(Request $request)
296
    {
297
        // split content
298
        $contentType = $request->headers->get('Content-Type');
299
        list(, $boundary) = explode('; boundary=', $contentType);
300
301
        // fix boundary dash count
302
        $boundary = '--' . $boundary;
303
304
        $content = $request->getContent();
305
        $contentBlocks = explode($boundary, $content, -1);
306
        $metadataInfo = '';
307
        $fileInfo = '';
308
309
        // determine content blocks usage
310
        foreach ($contentBlocks as $contentBlock) {
311
            if (empty($contentBlock)) {
312
                continue;
313
            }
314
            if (40 === strpos($contentBlock, 'upload')) {
315
                $fileInfo = $contentBlock;
316
                continue;
317
            }
318
            if (40 === strpos($contentBlock, 'metadata')) {
319
                $metadataInfo = $contentBlock;
320
                continue;
321
            }
322
        }
323
324
        $attributes = array_merge(
325
            $request->attributes->all(),
326
            $this->extractMetaDataFromContent($metadataInfo)
327
        );
328
        $files = $this->extractFileFromContent($fileInfo);
329
330
        return ['files' => $files, 'attributes' => $attributes];
331
    }
332
333
    /**
334
     * Extracts meta information from request content.
335
     *
336
     * @param string $metadataInfoString Information about metadata information
337
     *
338
     * @return array
339
     */
340
    private function extractMetaDataFromContent($metadataInfoString)
341
    {
342
        if (empty($metadataInfoString)) {
343
            return ['metadata' => '{}'];
344
        }
345
346
        $metadataInfo = explode("\r\n", ltrim($metadataInfoString));
347
        return ['metadata' => $metadataInfo[2]];
348
    }
349
350
    /**
351
     * Extracts file data from request content
352
     *
353
     * @param string $fileInfoString Information about uploaded files.
354
     *
355
     * @return array
356
     */
357
    private function extractFileFromContent($fileInfoString)
358
    {
359
        if (empty($fileInfoString)) {
360
            return null;
361
        }
362
363
        $fileInfo = explode("\r\n\r\n", ltrim($fileInfoString), 2);
364
365
        // write content to file ("upload_tmp_dir" || sys_get_temp_dir() )
366
        preg_match('@name=\"([^"]*)\";\sfilename=\"([^"]*)\"\s*Content-Type:\s([^"]*)@', $fileInfo[0], $matches);
367
        $originalName = $matches[2];
368
        $dir = ini_get('upload_tmp_dir');
369
        $dir = (empty($dir)) ? sys_get_temp_dir() : $dir;
370
        $file = $dir . '/' . $originalName;
371
        $fileContent = substr($fileInfo[1], 0, -2);
372
373
        // create file
374
        touch($file);
375
        $size = file_put_contents($file, $fileContent, LOCK_EX);
376
377
        $files = [
378
            $matches[1] => new UploadedFile(
379
                $file,
380
                $originalName,
381
                $matches[3],
382
                $size
383
            )
384
        ];
385
386
        return $files;
387
    }
388
}
389