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 | } |