Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
37.46% |
257 / 686 |
|
6.25% |
2 / 32 |
CRAP | |
0.00% |
0 / 1 |
ApiController | |
37.46% |
257 / 686 |
|
6.25% |
2 / 32 |
11203.86 | |
0.00% |
0 / 1 |
apiMediaUpload | |
65.57% |
40 / 61 |
|
0.00% |
0 / 1 |
17.88 | |||
replaceUploadFiles | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
90 | |||
uploadFiles | |
93.94% |
31 / 33 |
|
0.00% |
0 / 1 |
9.02 | |||
uploadFilesToDestination | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
createMediaPath | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
createDbEntry | |
91.84% |
45 / 49 |
|
0.00% |
0 / 1 |
8.03 | |||
loadFileContent | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
342 | |||
normalizeDbPath | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
apiMediaUpdate | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
2.21 | |||
validateMediaUpdate | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
updateMediaFromRequest | |
91.30% |
21 / 23 |
|
0.00% |
0 / 1 |
5.02 | |||
apiReferenceCreate | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
20 | |||
createReferenceFromRequest | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
validateReferenceCreate | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
30 | |||
apiCollectionAdd | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
apiCollectionCreate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
validateCollectionCreate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
createCollectionFromRequest | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
30 | |||
createMediaCollectionFromMedia | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
createRecursiveMediaCollection | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
42 | |||
apiMediaCreate | |
90.70% |
39 / 43 |
|
0.00% |
0 / 1 |
7.04 | |||
apiMediaExport | |
38.18% |
21 / 55 |
|
0.00% |
0 / 1 |
85.27 | |||
prepareEncryptedMedia | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
56 | |||
createView | |
62.32% |
43 / 69 |
|
0.00% |
0 / 1 |
100.54 | |||
setMediaResponseHeader | |
0.00% |
0 / 69 |
|
0.00% |
0 / 1 |
1122 | |||
validateMediaTypeCreate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
apiMediaTypeCreate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
createDocTypeFromRequest | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
validateMediaTypeL11nCreate | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
apiMediaTypeL11nCreate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
createMediaTypeL11nFromRequest | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
resizeImage | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package Modules\Media |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace Modules\Media\Controller; |
16 | |
17 | use Modules\Admin\Models\AccountPermission; |
18 | use Modules\Admin\Models\NullAccount; |
19 | use Modules\Media\Models\Collection; |
20 | use Modules\Media\Models\CollectionMapper; |
21 | use Modules\Media\Models\Media; |
22 | use Modules\Media\Models\MediaContent; |
23 | use Modules\Media\Models\MediaContentMapper; |
24 | use Modules\Media\Models\MediaMapper; |
25 | use Modules\Media\Models\MediaType; |
26 | use Modules\Media\Models\MediaTypeL11nMapper; |
27 | use Modules\Media\Models\MediaTypeMapper; |
28 | use Modules\Media\Models\NullCollection; |
29 | use Modules\Media\Models\NullMedia; |
30 | use Modules\Media\Models\NullMediaType; |
31 | use Modules\Media\Models\PathSettings; |
32 | use Modules\Media\Models\PermissionCategory; |
33 | use Modules\Media\Models\Reference; |
34 | use Modules\Media\Models\ReferenceMapper; |
35 | use Modules\Media\Models\UploadFile; |
36 | use Modules\Media\Models\UploadStatus; |
37 | use Modules\Media\Theme\Backend\Components\Media\ElementView; |
38 | use Modules\Tag\Models\NullTag; |
39 | use phpOMS\Account\PermissionType; |
40 | use phpOMS\Application\ApplicationAbstract; |
41 | use phpOMS\Asset\AssetType; |
42 | use phpOMS\Autoloader; |
43 | use phpOMS\Localization\BaseStringL11n; |
44 | use phpOMS\Message\Http\HttpRequest; |
45 | use phpOMS\Message\Http\HttpResponse; |
46 | use phpOMS\Message\Http\RequestStatusCode; |
47 | use phpOMS\Message\RequestAbstract; |
48 | use phpOMS\Message\ResponseAbstract; |
49 | use phpOMS\Model\Html\Head; |
50 | use phpOMS\Security\Guard; |
51 | use phpOMS\System\File\FileUtils; |
52 | use phpOMS\System\File\Local\Directory; |
53 | use phpOMS\System\MimeType; |
54 | use phpOMS\Utils\ImageUtils; |
55 | use phpOMS\Utils\Parser\Markdown\Markdown; |
56 | use phpOMS\Utils\StringUtils; |
57 | use phpOMS\Views\View; |
58 | |
59 | /** |
60 | * Media class. |
61 | * |
62 | * @package Modules\Media |
63 | * @license OMS License 2.0 |
64 | * @link https://jingga.app |
65 | * @since 1.0.0 |
66 | */ |
67 | final class ApiController extends Controller |
68 | { |
69 | /** |
70 | * Api method to upload media file. |
71 | * |
72 | * @param RequestAbstract $request Request |
73 | * @param ResponseAbstract $response Response |
74 | * @param array $data Generic data |
75 | * |
76 | * @return void |
77 | * |
78 | * @api |
79 | * |
80 | * @since 1.0.0 |
81 | */ |
82 | public function apiMediaUpload(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
83 | { |
84 | $uploads = $this->uploadFiles( |
85 | names: $request->getDataList('names'), |
86 | fileNames: $request->getDataList('filenames'), |
87 | files: $request->files, |
88 | account: $request->header->account, |
89 | basePath: __DIR__ . '/../../../Modules/Media/Files' . \urldecode($request->getDataString('path') ?? ''), |
90 | virtualPath: \urldecode($request->getDataString('virtualpath') ?? ''), |
91 | password: $request->getDataString('password') ?? '', |
92 | encryptionKey: $request->getDataString('encryption') ?? ($request->getDataBool('isencrypted') === true && !empty($_SERVER['OMS_PRIVATE_KEY_I'] ?? '') ? $_SERVER['OMS_PRIVATE_KEY_I'] : ''), |
93 | pathSettings: $request->getDataInt('pathsettings') ?? PathSettings::RANDOM_PATH, // IMPORTANT!!! |
94 | hasAccountRelation: $request->getDataBool('link_account') ?? false, |
95 | readContent: $request->getDataBool('parse_content') ?? false, |
96 | unit: $request->getDataInt('unit') |
97 | ); |
98 | |
99 | $ids = []; |
100 | foreach ($uploads as $file) { |
101 | $ids[] = $file->id; |
102 | |
103 | // add media types |
104 | if (!empty($types = $request->getDataJson('types'))) { |
105 | foreach ($types as $type) { |
106 | if (!isset($type['id'])) { |
107 | $request->setData('name', $type['name'], true); |
108 | $request->setData('title', $type['title'], true); |
109 | $request->setData('lang', $type['lang'] ?? null, true); |
110 | |
111 | $internalResponse = new HttpResponse(); |
112 | $this->apiMediaTypeCreate($request, $internalResponse); |
113 | |
114 | if (!\is_array($data = $internalResponse->get($request->uri->__toString()))) { |
115 | continue; |
116 | } |
117 | |
118 | $file->addMediaType($tId = $data['response']); |
119 | } else { |
120 | $file->addMediaType(new NullMediaType($tId = (int) $type['id'])); |
121 | } |
122 | |
123 | $this->createModelRelation( |
124 | $request->header->account, |
125 | $file->id, |
126 | $tId, |
127 | MediaMapper::class, |
128 | 'types', |
129 | '', |
130 | $request->getOrigin() |
131 | ); |
132 | } |
133 | } |
134 | |
135 | // add tags |
136 | if (!empty($tags = $request->getDataJson('tags'))) { |
137 | foreach ($tags as $tag) { |
138 | if (!isset($tag['id'])) { |
139 | $request->setData('title', $tag['title'], true); |
140 | $request->setData('color', $tag['color'], true); |
141 | $request->setData('icon', $tag['icon'] ?? null, true); |
142 | $request->setData('language', $tag['language'], true); |
143 | |
144 | $internalResponse = new HttpResponse(); |
145 | $this->app->moduleManager->get('Tag')->apiTagCreate($request, $internalResponse); |
146 | |
147 | if (!\is_array($data = $internalResponse->get($request->uri->__toString()))) { |
148 | continue; |
149 | } |
150 | |
151 | $file->addTag($tId = $data['response']); |
152 | } else { |
153 | $file->addTag(new NullTag($tId = (int) $tag['id'])); |
154 | } |
155 | |
156 | $this->createModelRelation( |
157 | $request->header->account, |
158 | $file->id, |
159 | $tId, |
160 | MediaMapper::class, |
161 | 'tags', |
162 | '', |
163 | $request->getOrigin() |
164 | ); |
165 | } |
166 | } |
167 | } |
168 | |
169 | $this->createStandardAddResponse($request, $response, $ids); |
170 | } |
171 | |
172 | /** |
173 | * Upload a media file and replace the existing media file |
174 | * |
175 | * @param array $files Files |
176 | * @param array $media Media files to update |
177 | * @param bool $sameNameIfPossible use exact same file name as original file name if the extension is the same |
178 | * |
179 | * @return Media[] |
180 | * |
181 | * @since 1.0.0 |
182 | */ |
183 | public function replaceUploadFiles( |
184 | array $files, |
185 | array $media, |
186 | bool $sameNameIfPossible = false |
187 | ) : array |
188 | { |
189 | if (empty($files) || \count($files) !== \count($media)) { |
190 | return []; |
191 | } |
192 | |
193 | $nCounter = -1; |
194 | foreach ($files as $file) { |
195 | ++$nCounter; |
196 | |
197 | // set output dir same as existing media |
198 | $outputDir = \dirname($media[$nCounter]->getAbsolutePath()); |
199 | |
200 | // set upload name (either same as old file name or new file name) |
201 | $mediaFilename = \basename($media[$nCounter]->getAbsolutePath()); |
202 | $uploadFilename = \basename($file['tmp_name']); |
203 | |
204 | $splitMediaFilename = \explode('.', $mediaFilename); |
205 | $splitUploadFilename = \explode('.', $uploadFilename); |
206 | |
207 | $mediaExtension = ($c = \count($splitMediaFilename)) > 1 ? $splitMediaFilename[$c - 1] : ''; |
208 | $uploadExtension = ($c = \count($splitUploadFilename)) > 1 ? $splitUploadFilename[$c - 1] : ''; |
209 | |
210 | if ($sameNameIfPossible && $mediaExtension === $uploadExtension) { |
211 | $uploadFilename = $mediaFilename; |
212 | } |
213 | |
214 | // remove old file |
215 | \unlink($media[$nCounter]->getAbsolutePath()); |
216 | |
217 | // upload file |
218 | $upload = new UploadFile(); |
219 | $upload->outputDir = $outputDir; |
220 | $upload->preserveFileName = $sameNameIfPossible; |
221 | |
222 | $status = $upload->upload([$file], [$uploadFilename], true); |
223 | $stat = \reset($status); |
224 | |
225 | // update media data |
226 | $media[$nCounter]->setPath(self::normalizeDbPath($stat['path']) . '/' . $stat['filename']); |
227 | $media[$nCounter]->size = $stat['size']; |
228 | $media[$nCounter]->extension = $stat['extension']; |
229 | |
230 | MediaMapper::update()->execute($media[$nCounter]); |
231 | |
232 | if (!empty($media[$nCounter]?->content->content)) { |
233 | $media[$nCounter]->content->content = self::loadFileContent( |
234 | $media[$nCounter]->getAbsolutePath(), |
235 | $media[$nCounter]->extension |
236 | ); |
237 | |
238 | MediaContentMapper::update()->execute($media[$nCounter]->content); |
239 | } |
240 | } |
241 | |
242 | return $media; |
243 | } |
244 | |
245 | /** |
246 | * Upload a media file |
247 | * |
248 | * @param array $names Database names |
249 | * @param array $fileNames FileNames |
250 | * @param array $files Files |
251 | * @param int $account Uploader |
252 | * @param string $basePath Base path. The path which is used for the upload. |
253 | * @param string $virtualPath virtual path The path which is used to visually structure the files, like directories |
254 | * The file storage on the system can be different |
255 | * @param string $password File password. The password to protect the file (only database) |
256 | * @param string $encryptionKey Encryption key. Used to encrypt the file on the local file storage. |
257 | * @param int $pathSettings Settings which describe where the file should be uploaded to (physically) |
258 | * - RANDOM_PATH = random location in the base path |
259 | * - FILE_PATH = combination of base path and virtual path |
260 | * @param bool $hasAccountRelation The uploaded files should be related to an account |
261 | * |
262 | * @return Media[] |
263 | * |
264 | * @since 1.0.0 |
265 | */ |
266 | public function uploadFiles( |
267 | array $names = [], |
268 | array $fileNames = [], |
269 | array $files = [], |
270 | int $account = 0, |
271 | string $basePath = '', |
272 | string $virtualPath = '', |
273 | string $password = '', |
274 | string $encryptionKey = '', |
275 | int $pathSettings = PathSettings::RANDOM_PATH, |
276 | bool $hasAccountRelation = true, |
277 | bool $readContent = false, |
278 | int $unit = null |
279 | ) : array |
280 | { |
281 | if (empty($files)) { |
282 | return []; |
283 | } |
284 | |
285 | $outputDir = ''; |
286 | $absolute = false; |
287 | |
288 | if ($pathSettings === PathSettings::RANDOM_PATH) { |
289 | $outputDir = self::createMediaPath($basePath); |
290 | } elseif ($pathSettings === PathSettings::FILE_PATH) { |
291 | $outputDir = \rtrim($basePath, '/\\'); |
292 | $absolute = true; |
293 | } else { |
294 | return []; |
295 | } |
296 | |
297 | if (!Guard::isSafePath($outputDir, __DIR__ . '/../../../')) { |
298 | return []; |
299 | } |
300 | |
301 | $upload = new UploadFile(); |
302 | $upload->outputDir = $outputDir; |
303 | $upload->preserveFileName = empty($fileNames) || \count($fileNames) === \count($files); |
304 | |
305 | $status = $upload->upload($files, $fileNames, $absolute, $encryptionKey); |
306 | |
307 | $sameLength = \count($names) === \count($status); |
308 | $nCounter = -1; |
309 | |
310 | $created = []; |
311 | foreach ($status as &$stat) { |
312 | ++$nCounter; |
313 | |
314 | // Possible: name != filename (name = database media name, filename = name on the file system) |
315 | $stat['name'] = $sameLength ? $names[$nCounter] : $stat['filename']; |
316 | |
317 | $created[] = self::createDbEntry( |
318 | $stat, |
319 | $account, |
320 | $virtualPath, |
321 | app: $hasAccountRelation ? $this->app : null, |
322 | readContent: $readContent, |
323 | unit: $unit, |
324 | password: $password, |
325 | isEncrypted: !empty($encryptionKey) |
326 | ); |
327 | } |
328 | |
329 | return $created; |
330 | } |
331 | |
332 | /** |
333 | * Uploads a file to a destination |
334 | * |
335 | * @param array $files Files to upload |
336 | * @param array $fileNames Names on the directory |
337 | * @param string $path Upload path |
338 | * @param bool $preserveFileName Preserve file name |
339 | * |
340 | * @return array |
341 | * |
342 | * @since 1.0.0 |
343 | */ |
344 | public static function uploadFilesToDestination( |
345 | array $files, |
346 | array $fileNames = [], |
347 | string $path = '', |
348 | bool $preserveFileName = true |
349 | ) : array |
350 | { |
351 | $upload = new UploadFile(); |
352 | $upload->outputDir = $path; //empty($path) ? $upload->outputDir : $path; |
353 | $upload->preserveFileName = $preserveFileName; |
354 | |
355 | return $upload->upload($files, $fileNames, true, ''); |
356 | } |
357 | |
358 | /** |
359 | * Create a random file path to store media files |
360 | * |
361 | * @param string $basePath Base path for file storage |
362 | * |
363 | * @return string Random path to store media files |
364 | * |
365 | * @since 1.0.0 |
366 | */ |
367 | public static function createMediaPath(string $basePath = '/Modules/Media/Files') : string |
368 | { |
369 | $rndPath = \bin2hex(\random_bytes(4)); |
370 | return $basePath . '/_' . $rndPath[0] . $rndPath[1] . $rndPath[2] . $rndPath[3] . '/_' . $rndPath[4] . $rndPath[5] . $rndPath[6] . $rndPath[7]; |
371 | } |
372 | |
373 | /** |
374 | * Create db entry for uploaded file |
375 | * |
376 | * @param array $status Files |
377 | * @param int $account Uploader |
378 | * @param string $virtualPath Virtual path (not on the hard-drive) |
379 | * @param string $ip Ip of the origin |
380 | * @param null|ApplicationAbstract $app Should create relation to uploader |
381 | * @param bool $readContent Should the content of the file be stored in the db |
382 | * |
383 | * @return Media |
384 | * |
385 | * @since 1.0.0 |
386 | */ |
387 | public static function createDbEntry( |
388 | array $status, |
389 | int $account, |
390 | string $virtualPath = '', |
391 | string $ip = '127.0.0.1', |
392 | ApplicationAbstract $app = null, |
393 | bool $readContent = false, |
394 | int $unit = null, |
395 | string $password = '', |
396 | bool $isEncrypted = false |
397 | ) : Media |
398 | { |
399 | if (!isset($status['status']) || $status['status'] !== UploadStatus::OK) { |
400 | return new NullMedia(); |
401 | } |
402 | |
403 | $media = new Media(); |
404 | |
405 | $media->setPath(self::normalizeDbPath($status['path']) . '/' . $status['filename']); |
406 | $media->name = empty($status['name']) ? $status['filename'] : $status['name']; |
407 | $media->size = $status['size']; |
408 | $media->createdBy = new NullAccount($account); |
409 | $media->extension = $status['extension']; |
410 | $media->unit = $unit; |
411 | $media->setVirtualPath($virtualPath); |
412 | $media->setPassword($password); |
413 | $media->isEncrypted = $isEncrypted; |
414 | |
415 | // Store text from document in DB for later use e.g. full text search (uses OCR, text extraction etc. if necessary) |
416 | if ($readContent && \is_file($media->getAbsolutePath())) { |
417 | $content = self::loadFileContent($media->getAbsolutePath(), $media->extension); |
418 | |
419 | if (!empty($content)) { |
420 | $media->content = new MediaContent(); |
421 | $media->content->content = $content; |
422 | } |
423 | } |
424 | |
425 | if ($app === null) { |
426 | MediaMapper::create()->execute($media); |
427 | |
428 | return $media; |
429 | } |
430 | |
431 | $app->eventManager->triggerSimilar('PRE:Module:Media-media-create', '', $media); |
432 | MediaMapper::create()->execute($media); |
433 | $app->eventManager->triggerSimilar('POST:Module:Media-media-create', '', |
434 | [ |
435 | $account, |
436 | null, $media, |
437 | StringUtils::intHash(MediaMapper::class), 'Media-media-create', |
438 | self::NAME, |
439 | (string) $media->id, |
440 | '', |
441 | $ip, |
442 | ] |
443 | ); |
444 | |
445 | $app->moduleManager->get('Admin', 'Api')->createAccountModelPermission( |
446 | new AccountPermission( |
447 | $account, |
448 | $app->unitId, |
449 | $app->appId, |
450 | self::NAME, |
451 | self::NAME, |
452 | PermissionCategory::MEDIA, |
453 | $media->id, |
454 | null, |
455 | PermissionType::READ | PermissionType::MODIFY | PermissionType::DELETE | PermissionType::PERMISSION |
456 | ), |
457 | $account, |
458 | $ip |
459 | ); |
460 | |
461 | return $media; |
462 | } |
463 | |
464 | /** |
465 | * Load the text content of a file |
466 | * |
467 | * @param string $path Path of the file |
468 | * @param string $extension File extension |
469 | * |
470 | * @return string |
471 | * |
472 | * @since 1.0.0 |
473 | */ |
474 | public static function loadFileContent(string $path, string $extension, string $output = 'html') : string |
475 | { |
476 | switch ($extension) { |
477 | case 'pdf': |
478 | return \phpOMS\Utils\Parser\Pdf\PdfParser::pdf2text($path/*, __DIR__ . '/../../../Tools/OCRImageOptimizer/bin/OCRImageOptimizerApp'*/); |
479 | case 'doc': |
480 | case 'docx': |
481 | $include = \realpath(__DIR__ . '/../../../Resources/'); |
482 | if ($include === false) { |
483 | return ''; |
484 | } |
485 | |
486 | if (!Autoloader::inPaths($include)) { |
487 | Autoloader::addPath($include); |
488 | } |
489 | |
490 | return \phpOMS\Utils\Parser\Document\DocumentParser::parseDocument($path, $output); |
491 | case 'ppt': |
492 | case 'pptx': |
493 | $include = \realpath(__DIR__ . '/../../../Resources/'); |
494 | if ($include === false) { |
495 | return ''; |
496 | } |
497 | |
498 | if (!Autoloader::inPaths($include)) { |
499 | Autoloader::addPath($include); |
500 | } |
501 | |
502 | return \phpOMS\Utils\Parser\Presentation\PresentationParser::parsePresentation($path, $output); |
503 | case 'xls': |
504 | case 'xlsx': |
505 | $include = \realpath(__DIR__ . '/../../../Resources/'); |
506 | if ($include === false) { |
507 | return ''; |
508 | } |
509 | |
510 | if (!Autoloader::inPaths($include)) { |
511 | Autoloader::addPath($include); |
512 | } |
513 | |
514 | return \phpOMS\Utils\Parser\Spreadsheet\SpreadsheetParser::parseSpreadsheet($path, $output); |
515 | case 'txt': |
516 | case 'md': |
517 | $contents = \file_get_contents($path); |
518 | |
519 | return $contents === false ? '' : $contents; |
520 | default: |
521 | return ''; |
522 | } |
523 | } |
524 | |
525 | /** |
526 | * Normalize the file path |
527 | * |
528 | * @param string $path Path to the file |
529 | * |
530 | * @return string |
531 | * |
532 | * @throws \Exception |
533 | * |
534 | * @since 1.0.0 |
535 | */ |
536 | public static function normalizeDbPath(string $path) : string |
537 | { |
538 | $realpath = \realpath(__DIR__ . '/../../../'); |
539 | if ($realpath === false) { |
540 | throw new \Exception(); // @codeCoverageIgnore |
541 | } |
542 | |
543 | return FileUtils::absolute(\str_replace('\\', '/', |
544 | \str_replace($realpath, '', |
545 | \rtrim($path, '\\/') |
546 | ) |
547 | )); |
548 | } |
549 | |
550 | /** |
551 | * Api method to update media. |
552 | * |
553 | * @param RequestAbstract $request Request |
554 | * @param ResponseAbstract $response Response |
555 | * @param array $data Generic data |
556 | * |
557 | * @return void |
558 | * |
559 | * @api |
560 | * |
561 | * @since 1.0.0 |
562 | */ |
563 | public function apiMediaUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
564 | { |
565 | if (!empty($val = $this->validateMediaUpdate($request))) { |
566 | $response->header->status = RequestStatusCode::R_400; |
567 | $this->createInvalidUpdateResponse($request, $response, $val); |
568 | |
569 | return; |
570 | } |
571 | |
572 | /** @var Media $old */ |
573 | $old = MediaMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
574 | $new = $this->updateMediaFromRequest($request, clone $old); |
575 | |
576 | $this->updateModel($request->header->account, $old, $new, MediaMapper::class, 'media', $request->getOrigin()); |
577 | $this->createStandardUpdateResponse($request, $response, $new); |
578 | } |
579 | |
580 | /** |
581 | * Validate media update request |
582 | * |
583 | * @param RequestAbstract $request Request |
584 | * |
585 | * @return array<string, bool> Returns the validation array of the request |
586 | * |
587 | * @since 1.0.0 |
588 | */ |
589 | private function validateMediaUpdate(RequestAbstract $request) : array |
590 | { |
591 | $val = []; |
592 | if (($val['id'] = !$request->hasData('id'))) { |
593 | return $val; |
594 | } |
595 | |
596 | return []; |
597 | } |
598 | |
599 | /** |
600 | * Method to update media from request. |
601 | * |
602 | * @param RequestAbstract $request Request |
603 | * |
604 | * @return Media |
605 | * |
606 | * @since 1.0.0 |
607 | */ |
608 | private function updateMediaFromRequest(RequestAbstract $request, Media $new) : Media |
609 | { |
610 | $new->name = $request->getDataString('name') ?? $new->name; |
611 | $new->description = $request->getDataString('description') ?? $new->description; |
612 | $new->setPath($request->getDataString('path') ?? $new->getPath()); |
613 | $new->setVirtualPath(\urldecode($request->getDataString('virtualpath') ?? $new->getVirtualPath())); |
614 | |
615 | if ($new->id === 0 |
616 | || !$this->app->accountManager->get($request->header->account)->hasPermission( |
617 | PermissionType::MODIFY, |
618 | $this->app->unitId, |
619 | $this->app->appId, |
620 | self::NAME, |
621 | PermissionCategory::MEDIA, |
622 | $request->header->account |
623 | ) |
624 | ) { |
625 | return $new; |
626 | } |
627 | |
628 | // @todo: create test for this content change and the parsed content change |
629 | if ($request->hasData('content')) { |
630 | \file_put_contents( |
631 | $new->isAbsolute |
632 | ? $new->getPath() |
633 | : __DIR__ . '/../../../' . \ltrim($new->getPath(), '\\/'), |
634 | $request->getDataString('content') ?? '' |
635 | ); |
636 | |
637 | $new->size = \strlen($request->getDataString('content') ?? ''); |
638 | } |
639 | |
640 | return $new; |
641 | } |
642 | |
643 | /** |
644 | * Api method to create a reference. |
645 | * |
646 | * @param RequestAbstract $request Request |
647 | * @param ResponseAbstract $response Response |
648 | * @param array $data Generic data |
649 | * |
650 | * @return void |
651 | * |
652 | * @api |
653 | * |
654 | * @since 1.0.0 |
655 | */ |
656 | public function apiReferenceCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
657 | { |
658 | if (!empty($val = $this->validateReferenceCreate($request))) { |
659 | $response->header->status = RequestStatusCode::R_400; |
660 | $this->createInvalidCreateResponse($request, $response, $val); |
661 | |
662 | return; |
663 | } |
664 | |
665 | $ref = $this->createReferenceFromRequest($request); |
666 | $this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin()); |
667 | |
668 | // get parent collection |
669 | // create relation |
670 | $parentCollectionId = (int) $request->getData('parent'); |
671 | if ($parentCollectionId === 0) { |
672 | /** @var Collection $parentCollection */ |
673 | $parentCollection = CollectionMapper::get() |
674 | ->where('virtualPath', \dirname($request->getDataString('virtualpath') ?? '')) |
675 | ->where('name', \basename($request->getDataString('virtualpath') ?? '')) |
676 | ->execute(); |
677 | |
678 | $parentCollectionId = $parentCollection->id; |
679 | } |
680 | |
681 | if (!$request->hasData('source')) { |
682 | /** @var \Modules\Media\Models\Media $child */ |
683 | $child = MediaMapper::get() |
684 | ->where('virtualPath', \dirname($request->getDataString('child') ?? '')) |
685 | ->where('name', \basename($request->getDataString('child') ?? '')) |
686 | ->execute(); |
687 | |
688 | $request->setData('source', $child->id); |
689 | } |
690 | |
691 | $this->createModelRelation( |
692 | $request->header->account, |
693 | $parentCollectionId, |
694 | $ref->id, |
695 | CollectionMapper::class, |
696 | 'sources', |
697 | '', |
698 | $request->getOrigin() |
699 | ); |
700 | |
701 | $this->createStandardCreateResponse($request, $response, $ref); |
702 | } |
703 | |
704 | /** |
705 | * Method to create a reference from request. |
706 | * |
707 | * @param RequestAbstract $request Request |
708 | * |
709 | * @return Reference Returns the collection from the request |
710 | * |
711 | * @since 1.0.0 |
712 | */ |
713 | private function createReferenceFromRequest(RequestAbstract $request) : Reference |
714 | { |
715 | $mediaReference = new Reference(); |
716 | $mediaReference->name = \basename($request->getDataString('virtualpath') ?? '/'); |
717 | $mediaReference->source = new NullMedia((int) $request->getData('source')); |
718 | $mediaReference->createdBy = new NullAccount($request->header->account); |
719 | $mediaReference->setVirtualPath(\dirname($request->getDataString('virtualpath') ?? '/')); |
720 | |
721 | return $mediaReference; |
722 | } |
723 | |
724 | /** |
725 | * Validate reference create request |
726 | * |
727 | * @param RequestAbstract $request Request |
728 | * |
729 | * @return array<string, bool> Returns the validation array of the request |
730 | * |
731 | * @since 1.0.0 |
732 | */ |
733 | private function validateReferenceCreate(RequestAbstract $request) : array |
734 | { |
735 | $val = []; |
736 | if (($val['parent'] = (!$request->hasData('parent') && !$request->hasData('virtualpath'))) |
737 | || ($val['source'] = (!$request->hasData('source') && !$request->hasData('child'))) |
738 | ) { |
739 | return $val; |
740 | } |
741 | |
742 | return []; |
743 | } |
744 | |
745 | /** |
746 | * Api method to add an element to a collection. |
747 | * |
748 | * Very similar to create Reference |
749 | * Reference = it's own media element which points to another element (disadvantage = additional step) |
750 | * Collection add = directly pointing to other media element (disadvantage = we don't know if we are allowed to modify/delete) |
751 | * |
752 | * @param RequestAbstract $request Request |
753 | * @param ResponseAbstract $response Response |
754 | * @param array $data Generic data |
755 | * |
756 | * @return void |
757 | * |
758 | * @api |
759 | * |
760 | * @since 1.0.0 |
761 | */ |
762 | public function apiCollectionAdd(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
763 | { |
764 | $collection = (int) $request->getData('collection'); |
765 | $media = $request->getDataJson('media-list'); |
766 | |
767 | foreach ($media as $file) { |
768 | $this->createModelRelation( |
769 | $request->header->account, |
770 | $collection, |
771 | $file, |
772 | CollectionMapper::class, |
773 | 'sources', |
774 | '', |
775 | $request->getOrigin() |
776 | ); |
777 | } |
778 | } |
779 | |
780 | /** |
781 | * Api method to create a collection. |
782 | * |
783 | * @param RequestAbstract $request Request |
784 | * @param ResponseAbstract $response Response |
785 | * @param array $data Generic data |
786 | * |
787 | * @return void |
788 | * |
789 | * @api |
790 | * |
791 | * @since 1.0.0 |
792 | */ |
793 | public function apiCollectionCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
794 | { |
795 | if (!empty($val = $this->validateCollectionCreate($request))) { |
796 | $response->header->status = RequestStatusCode::R_400; |
797 | $this->createInvalidCreateResponse($request, $response, $val); |
798 | |
799 | return; |
800 | } |
801 | |
802 | $collection = $this->createCollectionFromRequest($request); |
803 | $this->createModel($request->header->account, $collection, CollectionMapper::class, 'collection', $request->getOrigin()); |
804 | $this->createStandardCreateResponse($request, $response, $collection); |
805 | } |
806 | |
807 | /** |
808 | * Validate collection create request |
809 | * |
810 | * @param RequestAbstract $request Request |
811 | * |
812 | * @return array<string, bool> Returns the validation array of the request |
813 | * |
814 | * @since 1.0.0 |
815 | */ |
816 | private function validateCollectionCreate(RequestAbstract $request) : array |
817 | { |
818 | $val = []; |
819 | if (($val['name'] = !$request->hasData('name'))) { |
820 | return $val; |
821 | } |
822 | |
823 | return []; |
824 | } |
825 | |
826 | /** |
827 | * Method to create collection from request. |
828 | * |
829 | * @param RequestAbstract $request Request |
830 | * |
831 | * @return Collection Returns the collection from the request |
832 | * |
833 | * @since 1.0.0 |
834 | */ |
835 | private function createCollectionFromRequest(RequestAbstract $request) : Collection |
836 | { |
837 | $mediaCollection = new Collection(); |
838 | $mediaCollection->name = $request->getDataString('name') ?? ''; |
839 | $mediaCollection->description = ($description = Markdown::parse($request->getDataString('description') ?? '')); |
840 | $mediaCollection->descriptionRaw = $description; |
841 | $mediaCollection->createdBy = new NullAccount($request->header->account); |
842 | |
843 | $media = $request->getDataJson('media-list'); |
844 | foreach ($media as $file) { |
845 | $mediaCollection->addSource(new NullMedia((int) $file)); |
846 | } |
847 | |
848 | $virtualPath = \urldecode($request->getDataString('virtualpath') ?? '/'); |
849 | $basePath = __DIR__ . '/../../../Modules/Media/Files'; |
850 | $outputDir = $request->hasData('path') |
851 | ? $basePath . '/' . \ltrim($request->getDataString('path') ?? '', '\\/') |
852 | : self::createMediaPath($basePath); |
853 | |
854 | $dirPath = $outputDir . '/' . ($request->getDataString('name') ?? ''); |
855 | $outputDir = \substr($outputDir, \strlen(__DIR__ . '/../../..')); |
856 | |
857 | $mediaCollection->setVirtualPath($virtualPath); |
858 | $mediaCollection->setPath($outputDir); |
859 | |
860 | if (($request->getDataBool('create_directory') ?? false) |
861 | && !\is_dir($dirPath) |
862 | ) { |
863 | \mkdir($dirPath, 0755, true); |
864 | } |
865 | |
866 | return $mediaCollection; |
867 | } |
868 | |
869 | /** |
870 | * Method to create media collection from request. |
871 | * |
872 | * This doesn't create a database entry only the collection model. |
873 | * |
874 | * @param string $name Collection name |
875 | * @param string $description Description |
876 | * @param Media[] $media Media files to create the collection from |
877 | * @param int $account Account Id |
878 | * |
879 | * @return Collection |
880 | * |
881 | * @since 1.0.0 |
882 | */ |
883 | public function createMediaCollectionFromMedia(string $name, string $description, array $media, int $account) : Collection |
884 | { |
885 | if (empty($media) |
886 | || !$this->app->accountManager->get($account)->hasPermission( |
887 | PermissionType::CREATE, $this->app->unitId, null, self::NAME, PermissionCategory::COLLECTION, null) |
888 | ) { |
889 | return new NullCollection(); |
890 | } |
891 | |
892 | /* Create collection */ |
893 | $mediaCollection = new Collection(); |
894 | $mediaCollection->name = $name; |
895 | $mediaCollection->description = Markdown::parse($description); |
896 | $mediaCollection->descriptionRaw = $description; |
897 | $mediaCollection->createdBy = new NullAccount($account); |
898 | $mediaCollection->setSources($media); |
899 | $mediaCollection->setVirtualPath('/'); |
900 | $mediaCollection->setPath('/Modules/Media/Files'); |
901 | |
902 | return $mediaCollection; |
903 | } |
904 | |
905 | /** |
906 | * Create a collection recursively |
907 | * |
908 | * The function also creates all parent collections if they don't exist |
909 | * |
910 | * @param string $path Virtual path of the collection |
911 | * @param int $account Account who creates this collection |
912 | * @param string $physicalPath The physical path where the corresponding directory should be created |
913 | * |
914 | * @return Collection |
915 | * |
916 | * @since 1.0.0 |
917 | */ |
918 | public function createRecursiveMediaCollection(string $path, int $account, string $physicalPath = '') : Collection |
919 | { |
920 | $status = false; |
921 | if (!empty($physicalPath)) { |
922 | $status = \is_dir($physicalPath) ? true : \mkdir($physicalPath, 0755, true); |
923 | } |
924 | |
925 | $path = \trim($path, '/'); |
926 | $paths = \explode('/', $path); |
927 | $tempPaths = $paths; |
928 | $length = \count($paths); |
929 | |
930 | /** @var Collection $parentCollection */ |
931 | $parentCollection = null; |
932 | |
933 | $temp = ''; |
934 | for ($i = $length; $i > 0; --$i) { |
935 | $temp = '/' . \implode('/', $tempPaths); |
936 | |
937 | /** @var Collection $parentCollection */ |
938 | $parentCollection = CollectionMapper::getParentCollection($temp)->execute(); |
939 | if ($parentCollection->id > 0) { |
940 | break; |
941 | } |
942 | |
943 | \array_pop($tempPaths); |
944 | } |
945 | |
946 | for (; $i < $length; ++$i) { |
947 | /* Create collection */ |
948 | $childCollection = new Collection(); |
949 | $childCollection->name = $paths[$i]; |
950 | $childCollection->createdBy = new NullAccount($account); |
951 | $childCollection->setVirtualPath('/'. \ltrim($temp, '/')); |
952 | $childCollection->setPath('/Modules/Media/Files' . $temp); |
953 | |
954 | $this->createModel($account, $childCollection, CollectionMapper::class, 'collection', '127.0.0.1'); |
955 | $this->createModelRelation( |
956 | $account, |
957 | $parentCollection->id, |
958 | $childCollection->id, |
959 | CollectionMapper::class, |
960 | 'sources', |
961 | '', |
962 | '127.0.0.1' |
963 | ); |
964 | |
965 | $parentCollection = $childCollection; |
966 | $temp .= '/' . $paths[$i]; |
967 | } |
968 | |
969 | return $parentCollection; |
970 | } |
971 | |
972 | /** |
973 | * Api method to create media file. |
974 | * |
975 | * @param RequestAbstract $request Request |
976 | * @param ResponseAbstract $response Response |
977 | * @param array $data Generic data |
978 | * |
979 | * @return void |
980 | * |
981 | * @api |
982 | * |
983 | * @throws \Exception |
984 | * |
985 | * @since 1.0.0 |
986 | */ |
987 | public function apiMediaCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
988 | { |
989 | $path = \urldecode($request->getDataString('path') ?? ''); |
990 | $virtualPath = \urldecode($request->getDataString('virtualpath') ?? '/'); |
991 | $fileName = $request->getDataString('filename') ?? ($request->getDataString('name') ?? ''); |
992 | $fileName .= \strripos($fileName, '.') === false ? '.txt' : ''; |
993 | |
994 | $outputDir = ''; |
995 | $basePath = __DIR__ . '/../../../Modules/Media/Files'; |
996 | if (!$request->hasData('path')) { |
997 | $outputDir = self::createMediaPath($basePath); |
998 | } elseif (\stripos( |
999 | FileUtils::absolute($basePath . '/' . \ltrim($path, '\\/')), |
1000 | FileUtils::absolute(__DIR__ . '/../../../') |
1001 | ) !== 0) { |
1002 | $outputDir = self::createMediaPath($basePath); |
1003 | } else { |
1004 | $outputDir = $basePath . '/' . \ltrim($path, '\\/'); |
1005 | } |
1006 | |
1007 | if (!\is_dir($outputDir)) { |
1008 | $created = Directory::create($outputDir, 0775, true); |
1009 | |
1010 | if (!$created) { |
1011 | throw new \Exception('Couldn\'t create outputdir: "' . $outputDir . '"'); // @codeCoverageIgnore |
1012 | } |
1013 | } |
1014 | |
1015 | \file_put_contents($outputDir . '/' . $fileName, $request->getDataString('content') ?? ''); |
1016 | $outputDir = \substr($outputDir, \strlen(__DIR__ . '/../../..')); |
1017 | |
1018 | $status = [ |
1019 | [ |
1020 | 'status' => UploadStatus::OK, |
1021 | 'path' => $outputDir, |
1022 | 'filename' => $fileName, |
1023 | 'name' => $request->getDataString('name') ?? '', |
1024 | 'size' => \strlen($request->getDataString('content') ?? ''), |
1025 | 'extension' => \substr($fileName, \strripos($fileName, '.') + 1), |
1026 | ], |
1027 | ]; |
1028 | |
1029 | $ids = []; |
1030 | foreach ($status as $stat) { |
1031 | $created = self::createDbEntry( |
1032 | $stat, |
1033 | $request->header->account, |
1034 | $virtualPath, |
1035 | $request->getOrigin(), |
1036 | $this->app, |
1037 | unit: $request->getDataInt('unit'), |
1038 | password: $request->getDataString('password') ?? '', |
1039 | isEncrypted: $request->getDataBool('isencrypted') ?? $request->hasData('encryption') |
1040 | ); |
1041 | |
1042 | $ids[] = $created->id; |
1043 | } |
1044 | |
1045 | $this->createStandardAddResponse($request, $response, $ids); |
1046 | } |
1047 | |
1048 | /** |
1049 | * Routing end-point for application behaviour. |
1050 | * |
1051 | * @param HttpRequest $request Request |
1052 | * @param HttpResponse $response Response |
1053 | * @param array $data Generic data |
1054 | * |
1055 | * @return void |
1056 | * |
1057 | * @api |
1058 | * |
1059 | * @since 1.0.0 |
1060 | */ |
1061 | public function apiMediaExport(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1062 | { |
1063 | $filePath = ''; |
1064 | $media = null; |
1065 | |
1066 | if ($request->hasData('id')) { |
1067 | /** @var Media $media */ |
1068 | $media = MediaMapper::get()->where('id', (int) $request->getData('id'))->execute(); |
1069 | $filePath = $media->getAbsolutePath(); |
1070 | } else { |
1071 | $path = \urldecode($request->getDataString('path') ?? ''); |
1072 | $media = new NullMedia(); |
1073 | |
1074 | if (\is_file($filePath = __DIR__ . '/../../../' . \ltrim($path, '\\/'))) { |
1075 | $name = \explode('.', \basename($path)); |
1076 | |
1077 | $media->name = $name[0]; |
1078 | $media->extension = $name[\count($name) - 1] ?? ''; |
1079 | $media->isAbsolute = false; |
1080 | $media->setVirtualPath(\dirname($path)); |
1081 | $media->setPath('/' . \ltrim($path, '\\/')); |
1082 | } |
1083 | } |
1084 | |
1085 | if ($media->id > 0) { |
1086 | if (!($data['ignorePermission'] ?? false) |
1087 | && $request->header->account !== $media->createdBy->id |
1088 | && !$this->app->accountManager->get($request->header->account)->hasPermission( |
1089 | PermissionType::READ, |
1090 | $this->app->unitId, |
1091 | $this->app->appId, |
1092 | self::NAME, |
1093 | PermissionCategory::MEDIA, |
1094 | $media->id |
1095 | ) |
1096 | ) { |
1097 | $response->header->status = RequestStatusCode::R_403; |
1098 | $this->createInvalidReturnResponse($request, $response, $media); |
1099 | |
1100 | return; |
1101 | } |
1102 | |
1103 | if (!isset($data['guard'])) { |
1104 | $data['guard'] = __DIR__ . '/../Files'; |
1105 | } |
1106 | } elseif (empty($data) || !isset($data['guard'])) { |
1107 | $response->header->status = RequestStatusCode::R_403; |
1108 | $this->createInvalidReturnResponse($request, $response, $media); |
1109 | } |
1110 | |
1111 | if (!Guard::isSafePath($filePath, $data['guard'] ?? '')) { |
1112 | $response->header->status = RequestStatusCode::R_403; |
1113 | |
1114 | return; |
1115 | } |
1116 | |
1117 | if ($media->hasPassword() |
1118 | && !$media->comparePassword($request->getDataString('password') ?? '') |
1119 | ) { |
1120 | $view = new View($this->app->l11nManager, $request, $response); |
1121 | $view->setTemplate('/Modules/Media/Theme/Api/invalidPassword'); |
1122 | |
1123 | return; |
1124 | } |
1125 | |
1126 | if ($media->isEncrypted) { |
1127 | $media = $this->prepareEncryptedMedia($media, $request); |
1128 | |
1129 | if ($media->id === 0) { |
1130 | $response->header->status = RequestStatusCode::R_500; |
1131 | $this->createInvalidReturnResponse($request, $response, $media); |
1132 | |
1133 | return; |
1134 | } |
1135 | } |
1136 | |
1137 | if ($media->extension !== 'collection' && !\is_file($media->getAbsolutePath())) { |
1138 | $response->header->status = RequestStatusCode::R_500; |
1139 | $this->createInvalidReturnResponse($request, $response, $media); |
1140 | |
1141 | return; |
1142 | } |
1143 | |
1144 | $this->setMediaResponseHeader($media, $request, $response); |
1145 | $view = $this->createView($media, $request, $response); |
1146 | $view->data['path'] = __DIR__ . '/../../../'; |
1147 | |
1148 | $response->set('export', $view); |
1149 | } |
1150 | |
1151 | /** |
1152 | * Decrypt an encrypted media element |
1153 | * |
1154 | * @param Media $media Media model |
1155 | * @param RequestAbstract $request Request model |
1156 | * |
1157 | * @return Media |
1158 | * |
1159 | * @since 1.0.0 |
1160 | */ |
1161 | private function prepareEncryptedMedia(Media $media, RequestAbstract $request) : Media |
1162 | { |
1163 | $path = ''; |
1164 | $absolutePath = ''; |
1165 | |
1166 | $counter = 0; |
1167 | do { |
1168 | $randomName = \sha1(\random_bytes(32)); |
1169 | |
1170 | $path = '../../../Temp/' . $randomName . '.' . $media->getExtension(); |
1171 | $absolutePath = __DIR__ . '/' . $path; |
1172 | |
1173 | ++$counter; |
1174 | } while (!\is_file($absolutePath) && $counter < 1000); |
1175 | |
1176 | if ($counter >= 1000) { |
1177 | return new NullMedia(); |
1178 | } |
1179 | |
1180 | $encryptionKey = $request->getDataBool('isencrypted') === true && !empty($_SESSION['OMS_PRIVATE_KEY_I'] ?? '') |
1181 | ? $_SESSION['OMS_PRIVATE_KEY_I'] |
1182 | : $request->getDataString('encrpkey') ?? ''; |
1183 | |
1184 | $decrypted = $media->decrypt($encryptionKey, $absolutePath); |
1185 | |
1186 | if (!$decrypted) { |
1187 | return new NullMedia(); |
1188 | } |
1189 | |
1190 | $media->path = $media->isAbsolute ? $absolutePath : $path; |
1191 | |
1192 | return $media; |
1193 | } |
1194 | |
1195 | /** |
1196 | * Routing end-point for application behaviour. |
1197 | * |
1198 | * @param Media $media Media |
1199 | * @param HttpRequest $request Request |
1200 | * @param HttpResponse $response Response |
1201 | * |
1202 | * @return View |
1203 | * |
1204 | * @since 1.0.0 |
1205 | */ |
1206 | public function createView(Media $media, RequestAbstract $request, ResponseAbstract $response) : View |
1207 | { |
1208 | $view = new ElementView($this->app->l11nManager, $request, $response); |
1209 | $view->media = $media; |
1210 | |
1211 | if (!\headers_sent()) { |
1212 | $response->endAllOutputBuffering(); // for large files |
1213 | } |
1214 | |
1215 | if (($type = $request->getDataString('type')) === null) { |
1216 | $view->setTemplate('/Modules/Media/Theme/Api/render'); |
1217 | } elseif ($type === 'html') { |
1218 | $head = new Head(); |
1219 | |
1220 | $css = ''; |
1221 | if (\is_file(__DIR__ . '/../../../Web/Backend/css/backend-small.css')) { |
1222 | $css = \file_get_contents(__DIR__ . '/../../../Web/Backend/css/backend-small.css'); |
1223 | |
1224 | if ($css === false) { |
1225 | $css = ''; // @codeCoverageIgnore |
1226 | } |
1227 | } |
1228 | |
1229 | $css = \preg_replace('!\s+!', ' ', $css); |
1230 | $head->setStyle('core', $css ?? ''); |
1231 | |
1232 | $head->addAsset(AssetType::CSS, 'cssOMS/styles.css?v=1.0.0'); |
1233 | $view->data['head'] = $head; |
1234 | |
1235 | switch (\strtolower($media->extension)) { |
1236 | case 'jpg': |
1237 | case 'jpeg': |
1238 | case 'gif': |
1239 | case 'png': |
1240 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/image_raw'); |
1241 | break; |
1242 | case 'pdf': |
1243 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/pdf_raw'); |
1244 | break; |
1245 | case 'c': |
1246 | case 'cpp': |
1247 | case 'h': |
1248 | case 'php': |
1249 | case 'js': |
1250 | case 'css': |
1251 | case 'csv': |
1252 | case 'rs': |
1253 | case 'py': |
1254 | case 'r': |
1255 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/text_raw'); |
1256 | break; |
1257 | case 'json': |
1258 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/json_raw'); |
1259 | break; |
1260 | case 'txt': |
1261 | case 'cfg': |
1262 | case 'log': |
1263 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/text_raw'); |
1264 | break; |
1265 | case 'md': |
1266 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/markdown_raw'); |
1267 | break; |
1268 | case 'xls': |
1269 | case 'xlsx': |
1270 | $view->setTemplate('/Modules/Media/Theme/Api/spreadsheetAsHtml'); |
1271 | break; |
1272 | case 'doc': |
1273 | case 'docx': |
1274 | $view->setTemplate('/Modules/Media/Theme/Api/wordAsHtml'); |
1275 | break; |
1276 | case 'mp3': |
1277 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/audio_raw'); |
1278 | break; |
1279 | case 'mp4': |
1280 | case 'mpeg': |
1281 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/video_raw'); |
1282 | break; |
1283 | case 'collection': |
1284 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/collection_raw'); |
1285 | break; |
1286 | default: |
1287 | $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/default'); |
1288 | } |
1289 | } |
1290 | |
1291 | return $view; |
1292 | } |
1293 | |
1294 | /** |
1295 | * Set header for report/template |
1296 | * |
1297 | * @param Media $media Media file |
1298 | * @param RequestAbstract $request Request |
1299 | * @param ResponseAbstract $response Response |
1300 | * |
1301 | * @return void |
1302 | * |
1303 | * @since 1.0.0 |
1304 | */ |
1305 | private function setMediaResponseHeader(Media $media, RequestAbstract $request, ResponseAbstract $response) : void |
1306 | { |
1307 | switch ($request->getDataString('type') ?? \strtolower($media->extension)) { |
1308 | case 'htm': |
1309 | case 'html': |
1310 | $response->header->set('Content-Type', MimeType::M_HTML, true); |
1311 | break; |
1312 | case 'pdf': |
1313 | $response->header->set('Content-Type', MimeType::M_PDF, true); |
1314 | break; |
1315 | case 'c': |
1316 | case 'cpp': |
1317 | case 'h': |
1318 | case 'php': |
1319 | case 'js': |
1320 | case 'css': |
1321 | case 'rs': |
1322 | case 'py': |
1323 | case 'r': |
1324 | $response->header->set('Content-Type', MimeType::M_TXT, true); |
1325 | break; |
1326 | case 'txt': |
1327 | case 'cfg': |
1328 | case 'log': |
1329 | case 'md': |
1330 | $response->header->set('Content-Type', MimeType::M_TXT, true); |
1331 | break; |
1332 | case 'csv': |
1333 | case 'json': |
1334 | $response->header->set('Content-Type', MimeType::M_CSV, true); |
1335 | break; |
1336 | case 'xls': |
1337 | $response->header->set('Content-Type', MimeType::M_XLS, true); |
1338 | break; |
1339 | case 'xlsx': |
1340 | $response->header->set('Content-Type', MimeType::M_XLSX, true); |
1341 | break; |
1342 | case 'doc': |
1343 | $response->header->set('Content-Type', MimeType::M_DOC, true); |
1344 | break; |
1345 | case 'docx': |
1346 | $response->header->set('Content-Type', MimeType::M_DOCX, true); |
1347 | break; |
1348 | case 'ppt': |
1349 | $response->header->set('Content-Type', MimeType::M_PPT, true); |
1350 | break; |
1351 | case 'pptx': |
1352 | $response->header->set('Content-Type', MimeType::M_PPTX, true); |
1353 | break; |
1354 | case 'jpg': |
1355 | case 'jpeg': |
1356 | $response->header->set('Content-Type', MimeType::M_JPG, true); |
1357 | break; |
1358 | case 'gif': |
1359 | $response->header->set('Content-Type', MimeType::M_GIF, true); |
1360 | break; |
1361 | case 'png': |
1362 | $response->header->set('Content-Type', MimeType::M_PNG, true); |
1363 | break; |
1364 | case 'mp3': |
1365 | $response->header->set('Content-Type', MimeType::M_MP3, true); |
1366 | break; |
1367 | case 'mp4': |
1368 | $response->header->set('Content-Type', MimeType::M_MP4, true); |
1369 | break; |
1370 | case 'mpeg': |
1371 | $response->header->set('Content-Type', MimeType::M_MPEG, true); |
1372 | break; |
1373 | default: |
1374 | $response->header->set('Content-Type', MimeType::M_BIN, true); |
1375 | $response->header->set('Content-Disposition', 'attachment; filename="' . \addslashes($media->name) . '"', true); |
1376 | $response->header->set('Content-Transfer-Encoding', 'binary', true); |
1377 | } |
1378 | } |
1379 | |
1380 | /** |
1381 | * Validate document create request |
1382 | * |
1383 | * @param RequestAbstract $request Request |
1384 | * |
1385 | * @return array<string, bool> |
1386 | * |
1387 | * @since 1.0.0 |
1388 | */ |
1389 | private function validateMediaTypeCreate(RequestAbstract $request) : array |
1390 | { |
1391 | $val = []; |
1392 | if (($val['name'] = !$request->hasData('name')) |
1393 | ) { |
1394 | return $val; |
1395 | } |
1396 | |
1397 | return []; |
1398 | } |
1399 | |
1400 | /** |
1401 | * Api method to create document |
1402 | * |
1403 | * @param RequestAbstract $request Request |
1404 | * @param ResponseAbstract $response Response |
1405 | * @param array $data Generic data |
1406 | * |
1407 | * @return void |
1408 | * |
1409 | * @api |
1410 | * |
1411 | * @since 1.0.0 |
1412 | */ |
1413 | public function apiMediaTypeCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1414 | { |
1415 | if (!empty($val = $this->validateMediaTypeCreate($request))) { |
1416 | $response->header->status = RequestStatusCode::R_400; |
1417 | $this->createInvalidCreateResponse($request, $response, $val); |
1418 | |
1419 | return; |
1420 | } |
1421 | |
1422 | $type = $this->createDocTypeFromRequest($request); |
1423 | $this->createModel($request->header->account, $type, MediaTypeMapper::class, 'doc_type', $request->getOrigin()); |
1424 | $this->createStandardCreateResponse($request, $response, $type); |
1425 | } |
1426 | |
1427 | /** |
1428 | * Method to create task from request. |
1429 | * |
1430 | * @param RequestAbstract $request Request |
1431 | * |
1432 | * @return MediaType |
1433 | * |
1434 | * @since 1.0.0 |
1435 | */ |
1436 | private function createDocTypeFromRequest(RequestAbstract $request) : MediaType |
1437 | { |
1438 | $type = new MediaType(); |
1439 | $type->name = $request->getDataString('name') ?? ''; |
1440 | |
1441 | if ($request->hasData('title')) { |
1442 | $type->setL11n( |
1443 | $request->getDataString('title') ?? '', |
1444 | $request->getDataString('lang') ?? $request->header->l11n->language |
1445 | ); |
1446 | } |
1447 | |
1448 | return $type; |
1449 | } |
1450 | |
1451 | /** |
1452 | * Validate l11n create request |
1453 | * |
1454 | * @param RequestAbstract $request Request |
1455 | * |
1456 | * @return array<string, bool> |
1457 | * |
1458 | * @since 1.0.0 |
1459 | */ |
1460 | private function validateMediaTypeL11nCreate(RequestAbstract $request) : array |
1461 | { |
1462 | $val = []; |
1463 | if (($val['title'] = !$request->hasData('title')) |
1464 | || ($val['type'] = !$request->hasData('type')) |
1465 | ) { |
1466 | return $val; |
1467 | } |
1468 | |
1469 | return []; |
1470 | } |
1471 | |
1472 | /** |
1473 | * Api method to create tag localization |
1474 | * |
1475 | * @param RequestAbstract $request Request |
1476 | * @param ResponseAbstract $response Response |
1477 | * @param array $data Generic data |
1478 | * |
1479 | * @return void |
1480 | * |
1481 | * @api |
1482 | * |
1483 | * @since 1.0.0 |
1484 | */ |
1485 | public function apiMediaTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void |
1486 | { |
1487 | if (!empty($val = $this->validateMediaTypeL11nCreate($request))) { |
1488 | $response->header->status = RequestStatusCode::R_400; |
1489 | $this->createInvalidCreateResponse($request, $response, $val); |
1490 | |
1491 | return; |
1492 | } |
1493 | |
1494 | $l11nMediaType = $this->createMediaTypeL11nFromRequest($request); |
1495 | $this->createModel($request->header->account, $l11nMediaType, MediaTypeL11nMapper::class, 'media_type_l11n', $request->getOrigin()); |
1496 | $this->createStandardCreateResponse($request, $response, $l11nMediaType); |
1497 | } |
1498 | |
1499 | /** |
1500 | * Method to create tag localization from request. |
1501 | * |
1502 | * @param RequestAbstract $request Request |
1503 | * |
1504 | * @return BaseStringL11n |
1505 | * |
1506 | * @since 1.0.0 |
1507 | */ |
1508 | private function createMediaTypeL11nFromRequest(RequestAbstract $request) : BaseStringL11n |
1509 | { |
1510 | $l11nMediaType = new BaseStringL11n(); |
1511 | $l11nMediaType->ref = $request->getDataInt('type') ?? 0; |
1512 | $l11nMediaType->content = $request->getDataString('title') ?? ''; |
1513 | $l11nMediaType->setLanguage( |
1514 | $request->getDataString('language') ?? $request->header->l11n->language |
1515 | ); |
1516 | |
1517 | return $l11nMediaType; |
1518 | } |
1519 | |
1520 | /** |
1521 | * Resize image file |
1522 | * |
1523 | * @param Media $media Media object |
1524 | * @param int $width New width |
1525 | * @param int $height New height |
1526 | * @param bool $crop Crop image instead of resizing |
1527 | * |
1528 | * @return Media |
1529 | * @since 1.0.0 |
1530 | */ |
1531 | public function resizeImage( |
1532 | Media $media, |
1533 | int $width, |
1534 | int $height, |
1535 | bool $crop = false) : Media { |
1536 | ImageUtils::resize( |
1537 | $media->getAbsolutePath(), |
1538 | $media->getAbsolutePath(), |
1539 | $width, |
1540 | $height, |
1541 | $crop |
1542 | ); |
1543 | |
1544 | $temp = \filesize($media->getAbsolutePath()); |
1545 | $media->size = $temp === false ? 0 : $temp; |
1546 | |
1547 | return $media; |
1548 | } |
1549 | } |