*/ protected const EXTENSIONS = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', 'image/avif' => 'avif', ]; public function store(MediaRequest $request, RemoteImage $remote): JsonResponse { $this->authorize('upload', Media::class); $field = $request->hasFile('file') ? 'file' : 'url'; $contents = $field === 'file' ? (string) $request->file('file')->get() : $remote->fetch((string) $request->string('url')); $mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($contents) ?: null; $size = @getimagesizefromstring($contents); // Both, because each catches what the other misses: the type is read // from the magic bytes, and a file that has them but does not decode // is not an image anybody can render. if ($size === false || ! in_array($mime, (array) config('media.upload.mimes'), true)) { throw ValidationException::withMessages([$field => [__('rules.media.not_an_image')]]); } $path = config('media.upload.directory').'/'.Str::random(40).'.'.self::EXTENSIONS[$mime]; Storage::disk((string) config('media.disk'))->put($path, $contents); $media = Media::for($path); // What is already known, so the answer carries dimensions straight // away rather than after the queue has run. The processor writes the // same values, and the rest, when it does. if (! $media->isProcessed()) { $media->forceFill([ 'mime' => $mime, 'size' => strlen($contents), 'width' => $size[0], 'height' => $size[1], ])->save(); } return MediaResource::make($media->fresh())->response()->setStatusCode(201); } }