• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

conedevelopment / root / 15084089635

17 May 2025 10:00AM UTC coverage: 77.93% (+0.04%) from 77.891%
15084089635

push

github

web-flow
Modernize back-end.yml (#240)

3291 of 4223 relevant lines covered (77.93%)

36.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.42
/src/Models/Medium.php
1
<?php
2

3
namespace Cone\Root\Models;
4

5
use Closure;
6
use Cone\Root\Database\Factories\MediumFactory;
7
use Cone\Root\Interfaces\Models\Medium as Contract;
8
use Cone\Root\Interfaces\Models\User;
9
use Cone\Root\Jobs\MoveFile;
10
use Cone\Root\Jobs\PerformConversions;
11
use Cone\Root\Support\Facades\Conversion;
12
use Cone\Root\Traits\Filterable;
13
use Cone\Root\Traits\InteractsWithProxy;
14
use Illuminate\Database\Eloquent\Builder;
15
use Illuminate\Database\Eloquent\Casts\Attribute;
16
use Illuminate\Database\Eloquent\Concerns\HasUuids;
17
use Illuminate\Database\Eloquent\Factories\HasFactory;
18
use Illuminate\Database\Eloquent\Model;
19
use Illuminate\Database\Eloquent\Relations\BelongsTo;
20
use Illuminate\Http\UploadedFile;
21
use Illuminate\Support\Facades\App;
22
use Illuminate\Support\Facades\Config;
23
use Illuminate\Support\Facades\Response;
24
use Illuminate\Support\Facades\Storage;
25
use Illuminate\Support\Facades\URL;
26
use Illuminate\Support\Number;
27
use Illuminate\Support\Str;
28
use Symfony\Component\HttpFoundation\BinaryFileResponse;
29

30
class Medium extends Model implements Contract
31
{
32
    use Filterable;
33
    use HasFactory;
34
    use HasUuids;
35
    use InteractsWithProxy;
36

37
    /**
38
     * The accessors to append to the model's array form.
39
     *
40
     * @var list<string>
41
     */
42
    protected $appends = [
43
        'is_image',
44
        'urls',
45
    ];
46

47
    /**
48
     * The attributes that should have default values.
49
     *
50
     * @var array<string, string>
51
     */
52
    protected $attributes = [
53
        'properties' => '{"conversions":[]}',
54
    ];
55

56
    /**
57
     * The attributes that should be cast to native types.
58
     *
59
     * @var array<string, string>
60
     */
61
    protected $casts = [
62
        'properties' => 'json',
63
    ];
64

65
    /**
66
     * The attributes that are mass assignable.
67
     *
68
     * @var list<string>
69
     */
70
    protected $fillable = [
71
        'disk',
72
        'file_name',
73
        'height',
74
        'mime_type',
75
        'name',
76
        'properties',
77
        'size',
78
        'width',
79
    ];
80

81
    /**
82
     * The number of models to return for pagination.
83
     *
84
     * @var int
85
     */
86
    protected $perPage = 25;
87

88
    /**
89
     * The table associated with the model.
90
     *
91
     * @var string
92
     */
93
    protected $table = 'root_media';
94

95
    /**
96
     * The "booted" method of the model.
97
     */
98
    protected static function booted(): void
38✔
99
    {
100
        static::deleting(static function (self $medium): void {
38✔
101
            Storage::disk($medium->disk)->deleteDirectory($medium->uuid);
4✔
102
        });
38✔
103
    }
104

105
    /**
106
     * Get the proxied interface.
107
     */
108
    public static function getProxiedInterface(): string
1✔
109
    {
110
        return Contract::class;
1✔
111
    }
112

113
    /**
114
     * Create a new factory instance for the model.
115
     */
116
    protected static function newFactory(): MediumFactory
26✔
117
    {
118
        return MediumFactory::new();
26✔
119
    }
120

121
    /**
122
     * Upload the given file.
123
     */
124
    public static function upload(UploadedFile $file, ?Closure $callback = null): static
×
125
    {
126
        $medium = static::fromPath($file->getPathname(), [
×
127
            'file_name' => $file->getClientOriginalName(),
×
128
            'name' => pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME),
×
129
        ]);
×
130

131
        if (is_callable($callback)) {
×
132
            call_user_func_array($callback, [$medium, $file]);
×
133
        }
134

135
        $medium->save();
×
136

137
        $path = Storage::disk('local')->path(Storage::disk('local')->putFile('root-tmp', $file));
×
138

139
        MoveFile::withChain($medium->convertible() ? [new PerformConversions($medium)] : [])
×
140
            ->dispatch($medium, $path, false);
×
141

142
        return $medium;
×
143
    }
144

145
    /**
146
     * Make a new medium instance from the given path.
147
     */
148
    public static function fromPath(string $path, array $attributes = []): static
2✔
149
    {
150
        $type = mime_content_type($path);
2✔
151

152
        if (! Str::is('image/svg*', $type) && Str::is('image/*', $type)) {
2✔
153
            [$width, $height] = getimagesize($path);
2✔
154
        }
155

156
        return new static(array_merge([
2✔
157
            'file_name' => $name = basename($path),
2✔
158
            'mime_type' => $type,
2✔
159
            'width' => $width ?? null,
2✔
160
            'height' => $height ?? null,
2✔
161
            'disk' => Config::get('root.media.disk', 'public'),
2✔
162
            'size' => max(round(filesize($path) / 1024), 1),
2✔
163
            'name' => pathinfo($name, PATHINFO_FILENAME),
2✔
164
        ], $attributes));
2✔
165
    }
166

167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function getMorphClass(): string
×
171
    {
172
        return static::getProxiedClass();
×
173
    }
174

175
    /**
176
     * Get the columns that should receive a unique identifier.
177
     */
178
    public function uniqueIds(): array
30✔
179
    {
180
        return ['uuid'];
30✔
181
    }
182

183
    /**
184
     * Get the user for the medium.
185
     */
186
    public function user(): BelongsTo
2✔
187
    {
188
        return $this->belongsTo(App::make(User::class)::class);
2✔
189
    }
190

191
    /**
192
     * Determine if the file is image.
193
     *
194
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<bool, never>
195
     */
196
    protected function isImage(): Attribute
5✔
197
    {
198
        return new Attribute(
5✔
199
            get: static fn (mixed $value, array $attributes): bool => Str::is('image/*', $attributes['mime_type'])
5✔
200
        );
5✔
201
    }
202

203
    /**
204
     * Get the conversion URLs.
205
     *
206
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<array<string, string>, never>
207
     */
208
    protected function urls(): Attribute
3✔
209
    {
210
        return new Attribute(
3✔
211
            get: fn (): array => array_reduce(
3✔
212
                $this->properties['conversions'] ?? [],
3✔
213
                fn (array $urls, string $conversion): array => array_merge($urls, [$conversion => $this->getUrl($conversion)]),
3✔
214
                ['original' => $this->getUrl()]
3✔
215
            )
3✔
216
        );
3✔
217
    }
218

219
    /**
220
     * Get the formatted size attribute.
221
     *
222
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<string, never>
223
     */
224
    protected function formattedSize(): Attribute
1✔
225
    {
226
        return new Attribute(
1✔
227
            get: fn (): string => Number::fileSize($this->size ?: 0)
1✔
228
        );
1✔
229
    }
230

231
    /**
232
     * Get the dimensions attribute.
233
     *
234
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<string|null, never>
235
     */
236
    protected function dimensions(): Attribute
1✔
237
    {
238
        return new Attribute(
1✔
239
            get: static fn (mixed $value, array $attributes): ?string => isset($attributes['width'], $attributes['height'])
1✔
240
                ? sprintf('%dx%d px', $attributes['width'], $attributes['height'])
×
241
                : null
×
242
        );
1✔
243
    }
244

245
    /**
246
     * Determine if the medium should is convertible.
247
     */
248
    public function convertible(): bool
2✔
249
    {
250
        return $this->isImage && ! Str::is(['image/svg*', 'image/gif'], $this->mime_type);
2✔
251
    }
252

253
    /**
254
     * Perform the conversions on the medium.
255
     */
256
    public function convert(): static
1✔
257
    {
258
        Conversion::perform($this);
1✔
259

260
        return $this;
1✔
261
    }
262

263
    /**
264
     * Get the path to the conversion.
265
     */
266
    public function getPath(?string $conversion = null, bool $absolute = false): string
13✔
267
    {
268
        $path = sprintf('%s/%s', $this->uuid, $this->file_name);
13✔
269

270
        if (! is_null($conversion) && $conversion !== 'original') {
13✔
271
            $path = substr_replace(
2✔
272
                $path, "-{$conversion}", -(mb_strlen(Str::afterLast($path, '.')) + 1), -mb_strlen("-{$conversion}")
2✔
273
            );
2✔
274
        }
275

276
        return $absolute ? Storage::disk($this->disk)->path($path) : $path;
13✔
277
    }
278

279
    /**
280
     * Get the full path to the conversion.
281
     */
282
    public function getAbsolutePath(?string $conversion = null): string
8✔
283
    {
284
        return $this->getPath($conversion, true);
8✔
285
    }
286

287
    /**
288
     * Get the url to the conversion.
289
     */
290
    public function getUrl(?string $conversion = null): string
5✔
291
    {
292
        return URL::to(Storage::disk($this->disk)->url($this->getPath($conversion)));
5✔
293
    }
294

295
    /**
296
     * Check if the medium has the given conversion.
297
     */
298
    public function hasConversion(string $conversion): bool
2✔
299
    {
300
        return in_array($conversion, $this->properties['conversions'] ?? []);
2✔
301
    }
302

303
    /**
304
     * Download the medium.
305
     */
306
    public function download(?string $conversion = null): BinaryFileResponse
1✔
307
    {
308
        return Response::download($this->getAbsolutePath($conversion));
1✔
309
    }
310

311
    /**
312
     * Scope the query only to the given search term.
313
     */
314
    public function scopeSearch(Builder $query, ?string $value = null): Builder
1✔
315
    {
316
        if (is_null($value)) {
1✔
317
            return $query;
1✔
318
        }
319

320
        return $query->where($query->qualifyColumn('name'), 'like', "%{$value}%");
1✔
321
    }
322

323
    /**
324
     * Scope the query only to the given type.
325
     */
326
    public function scopeType(Builder $query, string $value): Builder
1✔
327
    {
328
        return match ($value) {
1✔
329
            'image' => $query->where($query->qualifyColumn('mime_type'), 'like', 'image%'),
1✔
330
            'file' => $query->where($query->qualifyColumn('mime_type'), 'not like', 'image%'),
1✔
331
            default => $query,
1✔
332
        };
1✔
333
    }
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc