* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ class Media { /** * Tries to find a file by model and filename * and to copy it to the media folder. * * @param \Kirby\Cms\Model|null $model * @param string $hash * @param string $filename * @return \Kirby\Cms\Response|false */ public static function link(Model $model = null, string $hash, string $filename) { if ($model === null) { return false; } // fix issues with spaces in filenames $filename = urldecode($filename); // try to find a file by model and filename // this should work for all original files if ($file = $model->file($filename)) { // check if the request contained an outdated media hash if ($file->mediaHash() !== $hash) { // if at least the token was correct, redirect if (Str::startsWith($hash, $file->mediaToken() . '-') === true) { return Response::redirect($file->mediaUrl(), 307); } else { // don't leak the correct token, render the error page return false; } } // send the file to the browser return Response::file($file->publish()->mediaRoot()); } // try to generate a thumb for the file return static::thumb($model, $hash, $filename); } /** * Copy the file to the final media folder location * * @param \Kirby\Cms\File $file * @param string $dest * @return bool */ public static function publish(File $file, string $dest): bool { // never publish risky files (e.g. HTML, PHP or Apache config files) FileRules::validFile($file, false); $src = $file->root(); $version = dirname($dest); $directory = dirname($version); // unpublish all files except stuff in the version folder Media::unpublish($directory, $file, $version); // copy/overwrite the file to the dest folder return F::copy($src, $dest, true); } /** * Tries to find a job file for the * given filename and then calls the thumb * component to create a thumbnail accordingly * * @param \Kirby\Cms\Model|string $model * @param string $hash * @param string $filename * @return \Kirby\Cms\Response|false */ public static function thumb($model, string $hash, string $filename) { $kirby = App::instance(); // assets if (is_string($model) === true) { $root = $kirby->root('media') . '/assets/' . $model . '/' . $hash; // parent files for file model that already included hash } elseif (is_a($model, '\Kirby\Cms\File')) { $root = dirname($model->mediaRoot()); // model files } else { $root = $model->mediaRoot() . '/' . $hash; } try { $thumb = $root . '/' . $filename; $job = $root . '/.jobs/' . $filename . '.json'; $options = Data::read($job); if (empty($options) === true) { return false; } if (is_string($model) === true) { $source = $kirby->root('index') . '/' . $model . '/' . $options['filename']; } else { $source = $model->file($options['filename'])->root(); } try { $kirby->thumb($source, $thumb, $options); F::remove($job); return Response::file($thumb); } catch (Throwable $e) { F::remove($thumb); return Response::file($source); } } catch (Throwable $e) { return false; } } /** * Deletes all versions of the given file * within the parent directory * * @param string $directory * @param \Kirby\Cms\File $file * @param string|null $ignore * @return bool */ public static function unpublish(string $directory, File $file, string $ignore = null): bool { if (is_dir($directory) === false) { return true; } // get both old and new versions (pre and post Kirby 3.4.0) $versions = array_merge( glob($directory . '/' . crc32($file->filename()) . '-*', GLOB_ONLYDIR), glob($directory . '/' . $file->mediaToken() . '-*', GLOB_ONLYDIR) ); // delete all versions of the file foreach ($versions as $version) { if ($version === $ignore) { continue; } Dir::remove($version); } return true; } }