MediaUploadTrait.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. <?php
  2. /**
  3. * @package Grav\Common\Media
  4. *
  5. * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Media\Traits;
  9. use Exception;
  10. use Grav\Common\Config\Config;
  11. use Grav\Common\Filesystem\Folder;
  12. use Grav\Common\Grav;
  13. use Grav\Common\Language\Language;
  14. use Grav\Common\Page\Medium\Medium;
  15. use Grav\Common\Page\Medium\MediumFactory;
  16. use Grav\Common\Security;
  17. use Grav\Common\Utils;
  18. use Grav\Framework\Filesystem\Filesystem;
  19. use Grav\Framework\Form\FormFlashFile;
  20. use Grav\Framework\Mime\MimeTypes;
  21. use Psr\Http\Message\UploadedFileInterface;
  22. use RocketTheme\Toolbox\File\YamlFile;
  23. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  24. use RuntimeException;
  25. use function dirname;
  26. use function in_array;
  27. /**
  28. * Implements media upload and delete functionality.
  29. */
  30. trait MediaUploadTrait
  31. {
  32. /** @var array */
  33. private $_upload_defaults = [
  34. 'self' => true, // Whether path is in the media collection path itself.
  35. 'avoid_overwriting' => false, // Do not override existing files (adds datetime postfix if conflict).
  36. 'random_name' => false, // True if name needs to be randomized.
  37. 'accept' => ['image/*'], // Accepted mime types or file extensions.
  38. 'limit' => 10, // Maximum number of files.
  39. 'filesize' => null, // Maximum filesize in MB.
  40. 'destination' => null // Destination path, if empty, exception is thrown.
  41. ];
  42. /**
  43. * Create Medium from an uploaded file.
  44. *
  45. * @param UploadedFileInterface $uploadedFile
  46. * @param array $params
  47. * @return Medium|null
  48. */
  49. public function createFromUploadedFile(UploadedFileInterface $uploadedFile, array $params = [])
  50. {
  51. return MediumFactory::fromUploadedFile($uploadedFile, $params);
  52. }
  53. /**
  54. * Checks that uploaded file meets the requirements. Returns new filename.
  55. *
  56. * @example
  57. * $filename = null; // Override filename if needed (ignored if randomizing filenames).
  58. * $settings = ['destination' => 'user://pages/media']; // Settings from the form field.
  59. * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings);
  60. * $media->copyUploadedFile($uploadedFile, $filename);
  61. *
  62. * @param UploadedFileInterface $uploadedFile
  63. * @param string|null $filename
  64. * @param array|null $settings
  65. * @return string
  66. * @throws RuntimeException
  67. */
  68. public function checkUploadedFile(UploadedFileInterface $uploadedFile, string $filename = null, array $settings = null): string
  69. {
  70. // Check if there is an upload error.
  71. switch ($uploadedFile->getError()) {
  72. case UPLOAD_ERR_OK:
  73. break;
  74. case UPLOAD_ERR_INI_SIZE:
  75. case UPLOAD_ERR_FORM_SIZE:
  76. throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 400);
  77. case UPLOAD_ERR_PARTIAL:
  78. case UPLOAD_ERR_NO_FILE:
  79. if (!$uploadedFile instanceof FormFlashFile) {
  80. throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 400);
  81. }
  82. break;
  83. case UPLOAD_ERR_NO_TMP_DIR:
  84. throw new RuntimeException($this->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 400);
  85. case UPLOAD_ERR_CANT_WRITE:
  86. case UPLOAD_ERR_EXTENSION:
  87. default:
  88. throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 400);
  89. }
  90. $metadata = [
  91. 'filename' => $uploadedFile->getClientFilename(),
  92. 'mime' => $uploadedFile->getClientMediaType(),
  93. 'size' => $uploadedFile->getSize(),
  94. ];
  95. if ($uploadedFile instanceof FormFlashFile) {
  96. $uploadedFile->checkXss();
  97. }
  98. return $this->checkFileMetadata($metadata, $filename, $settings);
  99. }
  100. /**
  101. * Checks that file metadata meets the requirements. Returns new filename.
  102. *
  103. * @param array $metadata
  104. * @param array|null $settings
  105. * @return string
  106. * @throws RuntimeException
  107. */
  108. public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string
  109. {
  110. // Add the defaults to the settings.
  111. $settings = $this->getUploadSettings($settings);
  112. // Destination is always needed (but it can be set in defaults).
  113. $self = $settings['self'] ?? false;
  114. if (!isset($settings['destination']) && $self === false) {
  115. throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400);
  116. }
  117. if (null === $filename) {
  118. // If no filename is given, use the filename from the uploaded file (path is not allowed).
  119. $folder = '';
  120. $filename = $metadata['filename'] ?? '';
  121. } else {
  122. // If caller sets the filename, we will accept any custom path.
  123. $folder = dirname($filename);
  124. if ($folder === '.') {
  125. $folder = '';
  126. }
  127. $filename = Utils::basename($filename);
  128. }
  129. $extension = Utils::pathinfo($filename, PATHINFO_EXTENSION);
  130. // Decide which filename to use.
  131. if ($settings['random_name']) {
  132. // Generate random filename if asked for.
  133. $filename = mb_strtolower(Utils::generateRandomString(15) . '.' . $extension);
  134. }
  135. // Handle conflicting filename if needed.
  136. if ($settings['avoid_overwriting']) {
  137. $destination = $settings['destination'];
  138. if ($destination && $this->fileExists($filename, $destination)) {
  139. $filename = date('YmdHis') . '-' . $filename;
  140. }
  141. }
  142. $filepath = $folder . $filename;
  143. // Check if the filename is allowed.
  144. if (!Utils::checkFilename($filename)) {
  145. throw new RuntimeException(
  146. sprintf($this->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'), $filepath, $this->translate('PLUGIN_ADMIN.BAD_FILENAME'))
  147. );
  148. }
  149. // Check if the file extension is allowed.
  150. $extension = mb_strtolower($extension);
  151. if (!$extension || !$this->getConfig()->get("media.types.{$extension}")) {
  152. // Not a supported type.
  153. throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension, 400);
  154. }
  155. // Calculate maximum file size (from MB).
  156. $filesize = $settings['filesize'];
  157. if ($filesize) {
  158. $max_filesize = $filesize * 1048576;
  159. if ($metadata['size'] > $max_filesize) {
  160. // TODO: use own language string
  161. throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
  162. }
  163. } elseif (null === $filesize) {
  164. // Check size against the Grav upload limit.
  165. $grav_limit = Utils::getUploadLimit();
  166. if ($grav_limit > 0 && $metadata['size'] > $grav_limit) {
  167. throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
  168. }
  169. }
  170. $grav = Grav::instance();
  171. /** @var MimeTypes $mimeChecker */
  172. $mimeChecker = $grav['mime'];
  173. // Handle Accepted file types. Accept can only be mime types (image/png | image/*) or file extensions (.pdf | .jpg)
  174. // Do not trust mime type sent by the browser.
  175. $mime = $metadata['mime'] ?? $mimeChecker->getMimeType($extension);
  176. $validExtensions = $mimeChecker->getExtensions($mime);
  177. if (!in_array($extension, $validExtensions, true)) {
  178. throw new RuntimeException('The mime type does not match to file extension', 400);
  179. }
  180. $accepted = false;
  181. $errors = [];
  182. foreach ((array)$settings['accept'] as $type) {
  183. // Force acceptance of any file when star notation
  184. if ($type === '*') {
  185. $accepted = true;
  186. break;
  187. }
  188. $isMime = strstr($type, '/');
  189. $find = str_replace(['.', '*', '+'], ['\.', '.*', '\+'], $type);
  190. if ($isMime) {
  191. $match = preg_match('#' . $find . '$#', $mime);
  192. if (!$match) {
  193. // TODO: translate
  194. $errors[] = 'The MIME type "' . $mime . '" for the file "' . $filepath . '" is not an accepted.';
  195. } else {
  196. $accepted = true;
  197. break;
  198. }
  199. } else {
  200. $match = preg_match('#' . $find . '$#', $filename);
  201. if (!$match) {
  202. // TODO: translate
  203. $errors[] = 'The File Extension for the file "' . $filepath . '" is not an accepted.';
  204. } else {
  205. $accepted = true;
  206. break;
  207. }
  208. }
  209. }
  210. if (!$accepted) {
  211. throw new RuntimeException(implode('<br />', $errors), 400);
  212. }
  213. return $filepath;
  214. }
  215. /**
  216. * Copy uploaded file to the media collection.
  217. *
  218. * WARNING: Always check uploaded file before copying it!
  219. *
  220. * @example
  221. * $settings = ['destination' => 'user://pages/media']; // Settings from the form field.
  222. * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings);
  223. * $media->copyUploadedFile($uploadedFile, $filename, $settings);
  224. *
  225. * @param UploadedFileInterface $uploadedFile
  226. * @param string $filename
  227. * @param array|null $settings
  228. * @return void
  229. * @throws RuntimeException
  230. */
  231. public function copyUploadedFile(UploadedFileInterface $uploadedFile, string $filename, array $settings = null): void
  232. {
  233. // Add the defaults to the settings.
  234. $settings = $this->getUploadSettings($settings);
  235. $path = $settings['destination'] ?? $this->getPath();
  236. if (!$path || !$filename) {
  237. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400);
  238. }
  239. /** @var UniformResourceLocator $locator */
  240. $locator = $this->getGrav()['locator'];
  241. try {
  242. // Clear locator cache to make sure we have up to date information from the filesystem.
  243. $locator->clearCache();
  244. $this->clearCache();
  245. $filesystem = Filesystem::getInstance(false);
  246. // Calculate path without the retina scaling factor.
  247. $basename = $filesystem->basename($filename);
  248. $pathname = $filesystem->pathname($filename);
  249. // Get name for the uploaded file.
  250. [$base, $ext,,] = $this->getFileParts($basename);
  251. $name = "{$pathname}{$base}.{$ext}";
  252. // Upload file.
  253. if ($uploadedFile instanceof FormFlashFile) {
  254. // FormFlashFile needs some additional logic.
  255. if ($uploadedFile->getError() === \UPLOAD_ERR_OK) {
  256. // Move uploaded file.
  257. $this->doMoveUploadedFile($uploadedFile, $filename, $path);
  258. } elseif (strpos($filename, 'original/') === 0 && !$this->fileExists($filename, $path) && $this->fileExists($basename, $path)) {
  259. // Original image support: override original image if it's the same as the uploaded image.
  260. $this->doCopy($basename, $filename, $path);
  261. }
  262. // FormFlashFile may also contain metadata.
  263. $metadata = $uploadedFile->getMetaData();
  264. if ($metadata) {
  265. // TODO: This overrides metadata if used with multiple retina image sizes.
  266. $this->doSaveMetadata(['upload' => $metadata], $name, $path);
  267. }
  268. } else {
  269. // Not a FormFlashFile.
  270. $this->doMoveUploadedFile($uploadedFile, $filename, $path);
  271. }
  272. // Post-processing: Special content sanitization for SVG.
  273. $mime = Utils::getMimeByFilename($filename);
  274. if (Utils::contains($mime, 'svg', false)) {
  275. $this->doSanitizeSvg($filename, $path);
  276. }
  277. // Add the new file into the media.
  278. // TODO: This overrides existing media sizes if used with multiple retina image sizes.
  279. $this->doAddUploadedMedium($name, $filename, $path);
  280. } catch (Exception $e) {
  281. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE') . $e->getMessage(), 400);
  282. } finally {
  283. // Finally clear media cache.
  284. $locator->clearCache();
  285. $this->clearCache();
  286. }
  287. }
  288. /**
  289. * Delete real file from the media collection.
  290. *
  291. * @param string $filename
  292. * @param array|null $settings
  293. * @return void
  294. * @throws RuntimeException
  295. */
  296. public function deleteFile(string $filename, array $settings = null): void
  297. {
  298. // Add the defaults to the settings.
  299. $settings = $this->getUploadSettings($settings);
  300. $filesystem = Filesystem::getInstance(false);
  301. // First check for allowed filename.
  302. $basename = $filesystem->basename($filename);
  303. if (!Utils::checkFilename($basename)) {
  304. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ": {$this->translate('PLUGIN_ADMIN.BAD_FILENAME')}: " . $filename, 400);
  305. }
  306. $path = $settings['destination'] ?? $this->getPath();
  307. if (!$path) {
  308. return;
  309. }
  310. /** @var UniformResourceLocator $locator */
  311. $locator = $this->getGrav()['locator'];
  312. $locator->clearCache();
  313. $pathname = $filesystem->pathname($filename);
  314. // Get base name of the file.
  315. [$base, $ext,,] = $this->getFileParts($basename);
  316. $name = "{$pathname}{$base}.{$ext}";
  317. // Remove file and all all the associated metadata.
  318. $this->doRemove($name, $path);
  319. // Finally clear media cache.
  320. $locator->clearCache();
  321. $this->clearCache();
  322. }
  323. /**
  324. * Rename file inside the media collection.
  325. *
  326. * @param string $from
  327. * @param string $to
  328. * @param array|null $settings
  329. */
  330. public function renameFile(string $from, string $to, array $settings = null): void
  331. {
  332. // Add the defaults to the settings.
  333. $settings = $this->getUploadSettings($settings);
  334. $filesystem = Filesystem::getInstance(false);
  335. $path = $settings['destination'] ?? $this->getPath();
  336. if (!$path) {
  337. // TODO: translate error message
  338. throw new RuntimeException('Failed to rename file: Bad destination', 400);
  339. }
  340. /** @var UniformResourceLocator $locator */
  341. $locator = $this->getGrav()['locator'];
  342. $locator->clearCache();
  343. // Get base name of the file.
  344. $pathname = $filesystem->pathname($from);
  345. // Remove @2x, @3x and .meta.yaml
  346. [$base, $ext,,] = $this->getFileParts($filesystem->basename($from));
  347. $from = "{$pathname}{$base}.{$ext}";
  348. [$base, $ext,,] = $this->getFileParts($filesystem->basename($to));
  349. $to = "{$pathname}{$base}.{$ext}";
  350. $this->doRename($from, $to, $path);
  351. // Finally clear media cache.
  352. $locator->clearCache();
  353. $this->clearCache();
  354. }
  355. /**
  356. * Internal logic to move uploaded file.
  357. *
  358. * @param UploadedFileInterface $uploadedFile
  359. * @param string $filename
  360. * @param string $path
  361. */
  362. protected function doMoveUploadedFile(UploadedFileInterface $uploadedFile, string $filename, string $path): void
  363. {
  364. $filepath = sprintf('%s/%s', $path, $filename);
  365. /** @var UniformResourceLocator $locator */
  366. $locator = $this->getGrav()['locator'];
  367. // Do not use streams internally.
  368. if ($locator->isStream($filepath)) {
  369. $filepath = (string)$locator->findResource($filepath, true, true);
  370. }
  371. Folder::create(dirname($filepath));
  372. $uploadedFile->moveTo($filepath);
  373. }
  374. /**
  375. * Get upload settings.
  376. *
  377. * @param array|null $settings Form field specific settings (override).
  378. * @return array
  379. */
  380. public function getUploadSettings(?array $settings = null): array
  381. {
  382. return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults;
  383. }
  384. /**
  385. * Internal logic to copy file.
  386. *
  387. * @param string $src
  388. * @param string $dst
  389. * @param string $path
  390. */
  391. protected function doCopy(string $src, string $dst, string $path): void
  392. {
  393. $src = sprintf('%s/%s', $path, $src);
  394. $dst = sprintf('%s/%s', $path, $dst);
  395. /** @var UniformResourceLocator $locator */
  396. $locator = $this->getGrav()['locator'];
  397. // Do not use streams internally.
  398. if ($locator->isStream($dst)) {
  399. $dst = (string)$locator->findResource($dst, true, true);
  400. }
  401. Folder::create(dirname($dst));
  402. copy($src, $dst);
  403. }
  404. /**
  405. * Internal logic to rename file.
  406. *
  407. * @param string $from
  408. * @param string $to
  409. * @param string $path
  410. */
  411. protected function doRename(string $from, string $to, string $path): void
  412. {
  413. /** @var UniformResourceLocator $locator */
  414. $locator = $this->getGrav()['locator'];
  415. $fromPath = $path . '/' . $from;
  416. if ($locator->isStream($fromPath)) {
  417. $fromPath = $locator->findResource($fromPath, true, true);
  418. }
  419. if (!is_file($fromPath)) {
  420. return;
  421. }
  422. $mediaPath = dirname($fromPath);
  423. $toPath = $mediaPath . '/' . $to;
  424. if ($locator->isStream($toPath)) {
  425. $toPath = $locator->findResource($toPath, true, true);
  426. }
  427. if (is_file($toPath)) {
  428. // TODO: translate error message
  429. throw new RuntimeException(sprintf('File could not be renamed: %s already exists (%s)', $to, $mediaPath), 500);
  430. }
  431. $result = rename($fromPath, $toPath);
  432. if (!$result) {
  433. // TODO: translate error message
  434. throw new RuntimeException(sprintf('File could not be renamed: %s -> %s (%s)', $from, $to, $mediaPath), 500);
  435. }
  436. // TODO: Add missing logic to handle retina files.
  437. if (is_file($fromPath . '.meta.yaml')) {
  438. $result = rename($fromPath . '.meta.yaml', $toPath . '.meta.yaml');
  439. if (!$result) {
  440. // TODO: translate error message
  441. throw new RuntimeException(sprintf('Meta could not be renamed: %s -> %s (%s)', $from, $to, $mediaPath), 500);
  442. }
  443. }
  444. }
  445. /**
  446. * Internal logic to remove file.
  447. *
  448. * @param string $filename
  449. * @param string $path
  450. */
  451. protected function doRemove(string $filename, string $path): void
  452. {
  453. $filesystem = Filesystem::getInstance(false);
  454. /** @var UniformResourceLocator $locator */
  455. $locator = $this->getGrav()['locator'];
  456. // If path doesn't exist, there's nothing to do.
  457. $pathname = $filesystem->pathname($filename);
  458. if (!$this->fileExists($pathname, $path)) {
  459. return;
  460. }
  461. $folder = $locator->isStream($path) ? (string)$locator->findResource($path, true, true) : $path;
  462. // Remove requested media file.
  463. if ($this->fileExists($filename, $path)) {
  464. $result = unlink("{$folder}/{$filename}");
  465. if (!$result) {
  466. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500);
  467. }
  468. }
  469. // Remove associated metadata.
  470. $this->doRemoveMetadata($filename, $path);
  471. // Remove associated 2x, 3x and their .meta.yaml files.
  472. $targetPath = rtrim(sprintf('%s/%s', $folder, $pathname), '/');
  473. $dir = scandir($targetPath, SCANDIR_SORT_NONE);
  474. if (false === $dir) {
  475. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500);
  476. }
  477. /** @var UniformResourceLocator $locator */
  478. $locator = $this->getGrav()['locator'];
  479. $basename = $filesystem->basename($filename);
  480. $fileParts = (array)$filesystem->pathinfo($filename);
  481. foreach ($dir as $file) {
  482. $preg_name = preg_quote($fileParts['filename'], '`');
  483. $preg_ext = preg_quote($fileParts['extension'] ?? '.', '`');
  484. $preg_filename = preg_quote($basename, '`');
  485. if (preg_match("`({$preg_name}@\d+x\.{$preg_ext}(?:\.meta\.yaml)?$|{$preg_filename}\.meta\.yaml)$`", $file)) {
  486. $testPath = $targetPath . '/' . $file;
  487. if ($locator->isStream($testPath)) {
  488. $testPath = (string)$locator->findResource($testPath, true, true);
  489. $locator->clearCache($testPath);
  490. }
  491. if (is_file($testPath)) {
  492. $result = unlink($testPath);
  493. if (!$result) {
  494. throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500);
  495. }
  496. }
  497. }
  498. }
  499. $this->hide($filename);
  500. }
  501. /**
  502. * @param array $metadata
  503. * @param string $filename
  504. * @param string $path
  505. */
  506. protected function doSaveMetadata(array $metadata, string $filename, string $path): void
  507. {
  508. $filepath = sprintf('%s/%s', $path, $filename);
  509. /** @var UniformResourceLocator $locator */
  510. $locator = $this->getGrav()['locator'];
  511. // Do not use streams internally.
  512. if ($locator->isStream($filepath)) {
  513. $filepath = (string)$locator->findResource($filepath, true, true);
  514. }
  515. $file = YamlFile::instance($filepath . '.meta.yaml');
  516. $file->save($metadata);
  517. }
  518. /**
  519. * @param string $filename
  520. * @param string $path
  521. */
  522. protected function doRemoveMetadata(string $filename, string $path): void
  523. {
  524. $filepath = sprintf('%s/%s', $path, $filename);
  525. /** @var UniformResourceLocator $locator */
  526. $locator = $this->getGrav()['locator'];
  527. // Do not use streams internally.
  528. if ($locator->isStream($filepath)) {
  529. $filepath = (string)$locator->findResource($filepath, true);
  530. if (!$filepath) {
  531. return;
  532. }
  533. }
  534. $file = YamlFile::instance($filepath . '.meta.yaml');
  535. if ($file->exists()) {
  536. $file->delete();
  537. }
  538. }
  539. /**
  540. * @param string $filename
  541. * @param string $path
  542. */
  543. protected function doSanitizeSvg(string $filename, string $path): void
  544. {
  545. $filepath = sprintf('%s/%s', $path, $filename);
  546. /** @var UniformResourceLocator $locator */
  547. $locator = $this->getGrav()['locator'];
  548. // Do not use streams internally.
  549. if ($locator->isStream($filepath)) {
  550. $filepath = (string)$locator->findResource($filepath, true, true);
  551. }
  552. Security::sanitizeSVG($filepath);
  553. }
  554. /**
  555. * @param string $name
  556. * @param string $filename
  557. * @param string $path
  558. */
  559. protected function doAddUploadedMedium(string $name, string $filename, string $path): void
  560. {
  561. $filepath = sprintf('%s/%s', $path, $filename);
  562. $medium = $this->createFromFile($filepath);
  563. $realpath = $path . '/' . $name;
  564. $this->add($realpath, $medium);
  565. }
  566. /**
  567. * @param string $string
  568. * @return string
  569. */
  570. protected function translate(string $string): string
  571. {
  572. return $this->getLanguage()->translate($string);
  573. }
  574. abstract protected function getPath(): ?string;
  575. abstract protected function getGrav(): Grav;
  576. abstract protected function getConfig(): Config;
  577. abstract protected function getLanguage(): Language;
  578. abstract protected function clearCache(): void;
  579. }