1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6declare(strict_types=1);
7
8namespace App\Libraries;
9
10use App\Exceptions\InvariantException;
11use App\Models\Model;
12use Illuminate\Http\File;
13use League\Flysystem\Local\LocalFilesystemAdapter;
14
15class Uploader
16{
17 private const DEFAULT_MAX_FILESIZE = 10_000_000;
18
19 private ?string $filename;
20
21 public function __construct(
22 private string $baseDir,
23 public Model $model,
24 private string $attr,
25 private array $processors = [],
26 ) {
27 $this->filename = $this->model->{$this->attr};
28 }
29
30 private static function process(string $name, ?array $options, string $srcPath): ?string
31 {
32 switch ($name) {
33 case 'image':
34 $processor = new ImageProcessor(
35 $srcPath,
36 $options['maxDimensions'],
37 $options['maxFilesize'] ?? static::DEFAULT_MAX_FILESIZE,
38 );
39 $processor->process();
40
41 return $processor->ext();
42 }
43
44 throw new InvariantException('unknown process name');
45 }
46
47 public function delete(): void
48 {
49 $this->setFilename(null);
50 \Storage::deleteDirectory($this->dir());
51 }
52
53 public function get(): ?string
54 {
55 $path = $this->path();
56
57 return $path === null ? null : \Storage::get($path);
58 }
59
60 public function store(string $srcPath, string $ext = ''): void
61 {
62 foreach ($this->processors as $processName => $processOptions) {
63 $newExt = static::process($processName, $processOptions, $srcPath);
64 if ($newExt !== null) {
65 $ext = $newExt;
66 }
67 }
68
69 $this->delete();
70 $filename = hash_file('sha256', $srcPath);
71 if (present($ext)) {
72 $filename .= ".{$ext}";
73 }
74 $this->setFilename($filename);
75 $storage = \Storage::disk();
76
77 if ($storage->getAdapter() instanceof LocalFilesystemAdapter) {
78 $options = [
79 'visibility' => 'public',
80 'directory_visibility' => 'public',
81 ];
82 }
83
84 $storage->putFileAs(
85 $this->dir(),
86 new File($srcPath),
87 $this->filename,
88 $options ?? [],
89 );
90 }
91
92 public function url(): ?string
93 {
94 $path = $this->path();
95
96 return $path === null ? null : StorageUrl::make(null, $path);
97 }
98
99 private function dir(): string
100 {
101 return "{$this->baseDir}/{$this->model->getKey()}";
102 }
103
104 private function path(): ?string
105 {
106 return $this->filename === null ? null : "{$this->dir()}/{$this->filename}";
107 }
108
109 private function setFilename(?string $filename): void
110 {
111 $this->filename = $filename;
112 $this->model->{$this->attr} = $filename;
113 }
114}