Completed
Pull Request — develop (#352)
by Samuel
06:45 queued 10s
created

FileManager::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$record is of type null|object, but the function expects a object<Graviton\I18nBundle\Document\Translatable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
209
    }
210
211
    /**
212
     * Updates or initialzes the metadata information of the current entity.
213
     *
214
     * @param FileDocument $file     Document to be used
215
     * @param integer      $fileSize Size of the uploaded file
216
     * @param array        $fileInfo Additional info about the file
217
     * @param FileDocument $fileData File data to be updated
218
     *
219
     * @return void
220
     */
221 1
    private function initOrUpdateMetaData(FileDocument $file, $fileSize, array $fileInfo, FileDocument $fileData = null)
222
    {
223 1
        if (empty($meta = $file->getMetadata()) && (empty($fileData) || empty($meta = $fileData->getMetadata()))) {
224
            $meta = $this->fileDocumentFactory->createFileMataData();
225
            $meta->setId($file->getId());
226
            $meta->setCreatedate(new \DateTime());
227
        }
228
229 1
        $meta->setModificationdate(new \DateTime());
230 1
        if (empty($meta->getFilename()) && !empty($fileInfo['data']['filename'])) {
231 1
            $meta->setFilename($fileInfo['data']['filename']);
232 1
        }
233 1
        if (!empty($fileInfo['data']['mimetype'])) {
234 1
            $meta->setMime($fileInfo['data']['mimetype']);
235 1
        }
236 1
        $meta->setSize($fileSize);
237 1
        $file->setMetadata($meta);
238 1
    }
239
240
    /**
241
     * Extracts different information sent in the request content.
242
     *
243
     * @param Request $request Current http request
244
     *
245
     * @return array
246
     */
247
    public function extractDataFromRequestContent(Request $request)
248
    {
249
        // split content
250
        $contentType = $request->headers->get('Content-Type');
251
        list(, $boundary) = explode('; boundary=', $contentType);
252
253
        // fix boundary dash count
254
        $boundary = '--' . $boundary;
255
256
        $content = $request->getContent();
257
        $contentBlocks = explode($boundary, $content, -1);
258
        $metadataInfo = '';
259
        $fileInfo = '';
260
261
        // determine content blocks usage
262
        foreach ($contentBlocks as $contentBlock) {
263
            if (empty($contentBlock)) {
264
                continue;
265
            }
266
            if (40 === strpos($contentBlock, 'upload')) {
267
                $fileInfo = $contentBlock;
268
                continue;
269
            }
270
            if (40 === strpos($contentBlock, 'metadata')) {
271
                $metadataInfo = $contentBlock;
272
                continue;
273
            }
274
        }
275
276
        $attributes = array_merge(
277
            $request->attributes->all(),
278
            $this->extractMetaDataFromContent($metadataInfo)
279
        );
280
        $files = $this->extractFileFromContent($fileInfo);
281
282
        return ['files' => $files, 'attributes' => $attributes];
283
    }
284
285
    /**
286
     * Extracts meta information from request content.
287
     *
288
     * @param string $metadataInfoString Information about metadata information
289
     *
290
     * @return array
291
     */
292
    private function extractMetaDataFromContent($metadataInfoString)
293
    {
294
        if (empty($metadataInfoString)) {
295
            return ['metadata' => '{}'];
296
        }
297
298
        $metadataInfo = explode("\r\n", ltrim($metadataInfoString));
299
        return ['metadata' => $metadataInfo[2]];
300
    }
301
302
    /**
303
     * Extracts file data from request content
304
     *
305
     * @param string $fileInfoString Information about uploaded files.
306
     *
307
     * @return array
308
     */
309
    private function extractFileFromContent($fileInfoString)
310
    {
311
        if (empty($fileInfoString)) {
312
            return null;
313
        }
314
315
        $fileInfo = explode("\r\n\r\n", ltrim($fileInfoString), 2);
316
317
        // write content to file ("upload_tmp_dir" || sys_get_temp_dir() )
318
        preg_match('@name=\"([^"]*)\";\sfilename=\"([^"]*)\"\s*Content-Type:\s([^"]*)@', $fileInfo[0], $matches);
319
        $originalName = $matches[2];
320
        $dir = ini_get('upload_tmp_dir');
321
        $dir = (empty($dir)) ? sys_get_temp_dir() : $dir;
322
        $file = $dir . '/' . $originalName;
323
        $fileContent = substr($fileInfo[1], 0, -2);
324
325
        // create file
326
        touch($file);
327
        $size = file_put_contents($file, $fileContent, LOCK_EX);
328
329
        $files = [
330
            $matches[1] => new UploadedFile(
331
                $file,
332
                $originalName,
333
                $matches[3],
334
                $size
335
            )
336
        ];
337
338
        return $files;
339
    }
340
}
341