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