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

conedevelopment / root / 19758157679

28 Nov 2025 08:23AM UTC coverage: 76.162% (+0.05%) from 76.113%
19758157679

push

github

iamgergo
fix test

3393 of 4455 relevant lines covered (76.16%)

34.06 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Cone\Root\Models;
6

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

34
class Medium extends Model implements Contract
35
{
36
    use Filterable;
37
    use HasFactory;
38
    use HasUuids;
39
    use InteractsWithProxy;
40

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

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

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

76
    /**
77
     * The number of models to return for pagination.
78
     *
79
     * @var int
80
     */
81
    protected $perPage = 25;
82

83
    /**
84
     * The table associated with the model.
85
     *
86
     * @var string
87
     */
88
    protected $table = 'root_media';
89

90
    /**
91
     * The "booted" method of the model.
92
     */
93
    protected static function booted(): void
38✔
94
    {
95
        static::deleting(static function (self $medium): void {
38✔
96
            Storage::disk($medium->disk)->deleteDirectory($medium->uuid);
4✔
97
        });
38✔
98
    }
99

100
    /**
101
     * Get the proxied interface.
102
     */
103
    public static function getProxiedInterface(): string
1✔
104
    {
105
        return Contract::class;
1✔
106
    }
107

108
    /**
109
     * Create a new factory instance for the model.
110
     */
111
    protected static function newFactory(): MediumFactory
26✔
112
    {
113
        return MediumFactory::new();
26✔
114
    }
115

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

126
        if (is_callable($callback)) {
×
127
            call_user_func_array($callback, [$medium, $file]);
×
128
        }
129

130
        $medium->save();
×
131

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

134
        MoveFile::withChain($medium->convertible() ? [new PerformConversions($medium)] : [])
×
135
            ->dispatch($medium, $path, false);
×
136

137
        return $medium;
×
138
    }
139

140
    /**
141
     * Make a new medium instance from the given path.
142
     */
143
    public static function fromPath(string $path, array $attributes = []): static
2✔
144
    {
145
        $type = mime_content_type($path);
2✔
146

147
        if (! Str::is('image/svg*', $type) && Str::is('image/*', $type)) {
2✔
148
            [$width, $height] = getimagesize($path);
2✔
149
        }
150

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

162
    /**
163
     * Get the attributes that should be cast.
164
     *
165
     * @return array{'values':'\Illuminate\Database\Eloquent\Casts\AsArrayObject'}
166
     */
167
    protected function casts(): array
38✔
168
    {
169
        return [
38✔
170
            'properties' => AsArrayObject::class,
38✔
171
        ];
38✔
172
    }
173

174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function getMorphClass(): string
×
178
    {
179
        return static::getProxiedClass();
×
180
    }
181

182
    /**
183
     * Get the columns that should receive a unique identifier.
184
     */
185
    public function uniqueIds(): array
30✔
186
    {
187
        return ['uuid'];
30✔
188
    }
189

190
    /**
191
     * Get the user for the medium.
192
     */
193
    public function user(): BelongsTo
2✔
194
    {
195
        return $this->belongsTo(App::make(User::class)::class);
2✔
196
    }
197

198
    /**
199
     * Determine if the file is image.
200
     *
201
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<bool, never>
202
     */
203
    protected function isImage(): Attribute
5✔
204
    {
205
        return new Attribute(
5✔
206
            get: static fn (mixed $value, array $attributes): bool => Str::is('image/*', $attributes['mime_type'])
5✔
207
        );
5✔
208
    }
209

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

226
    /**
227
     * Get the formatted size attribute.
228
     *
229
     * @return \Illuminate\Database\Eloquent\Casts\Attribute<string, never>
230
     */
231
    protected function formattedSize(): Attribute
1✔
232
    {
233
        return new Attribute(
1✔
234
            get: fn (): string => Number::fileSize($this->size ?: 0)
1✔
235
        );
1✔
236
    }
237

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

252
    /**
253
     * Determine if the medium should is convertible.
254
     */
255
    public function convertible(): bool
2✔
256
    {
257
        return $this->isImage && ! Str::is(['image/svg*', 'image/gif'], $this->mime_type);
2✔
258
    }
259

260
    /**
261
     * Perform the conversions on the medium.
262
     */
263
    public function convert(): static
1✔
264
    {
265
        Conversion::perform($this);
1✔
266

267
        return $this;
1✔
268
    }
269

270
    /**
271
     * Get the path to the conversion.
272
     */
273
    public function getPath(?string $conversion = null, bool $absolute = false): string
13✔
274
    {
275
        $path = sprintf('%s/%s', $this->uuid, $this->file_name);
13✔
276

277
        if (! is_null($conversion) && $conversion !== 'original') {
13✔
278
            $path = substr_replace(
2✔
279
                $path, "-{$conversion}", -(mb_strlen(Str::afterLast($path, '.')) + 1), -mb_strlen("-{$conversion}")
2✔
280
            );
2✔
281
        }
282

283
        return $absolute ? Storage::disk($this->disk)->path($path) : $path;
13✔
284
    }
285

286
    /**
287
     * Get the full path to the conversion.
288
     */
289
    public function getAbsolutePath(?string $conversion = null): string
8✔
290
    {
291
        return $this->getPath($conversion, true);
8✔
292
    }
293

294
    /**
295
     * Get the url to the conversion.
296
     */
297
    public function getUrl(?string $conversion = null): string
5✔
298
    {
299
        return URL::to(Storage::disk($this->disk)->url($this->getPath($conversion)));
5✔
300
    }
301

302
    /**
303
     * Check if the medium has the given conversion.
304
     */
305
    public function hasConversion(string $conversion): bool
2✔
306
    {
307
        return in_array($conversion, $this->properties['conversions'] ?? []);
2✔
308
    }
309

310
    /**
311
     * Download the medium.
312
     */
313
    public function download(?string $conversion = null): BinaryFileResponse
1✔
314
    {
315
        return Response::download($this->getAbsolutePath($conversion));
1✔
316
    }
317

318
    /**
319
     * Scope the query only to the given search term.
320
     */
321
    #[Scope]
1✔
322
    protected function search(Builder $query, string $value): Builder
323
    {
324
        if (is_null($value)) {
1✔
325
            return $query;
×
326
        }
327

328
        return $query->where($query->qualifyColumn('name'), 'like', "%{$value}%");
1✔
329
    }
330

331
    /**
332
     * Scope the query only to the given type.
333
     */
334
    #[Scope]
1✔
335
    protected function type(Builder $query, string $value): Builder
336
    {
337
        return match ($value) {
1✔
338
            'image' => $query->where($query->qualifyColumn('mime_type'), 'like', 'image%'),
1✔
339
            'file' => $query->where($query->qualifyColumn('mime_type'), 'not like', 'image%'),
1✔
340
            default => $query,
1✔
341
        };
1✔
342
    }
343
}
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