MediaUploadTrait.php 24 KB

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