203 lines
5.1 KiB
PHP
203 lines
5.1 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Core PHP Framework
|
||
|
|
*
|
||
|
|
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||
|
|
* See LICENSE file for details.
|
||
|
|
*/
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Core\Media\Image;
|
||
|
|
|
||
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
|
|
use Illuminate\Database\Eloquent\Model;
|
||
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
|
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||
|
|
use Illuminate\Support\Carbon;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Image optimisation record.
|
||
|
|
*
|
||
|
|
* @property int $id
|
||
|
|
* @property string $path
|
||
|
|
* @property string|null $original_path
|
||
|
|
* @property int $original_size
|
||
|
|
* @property int $optimized_size
|
||
|
|
* @property int $percentage_saved
|
||
|
|
* @property string|null $driver
|
||
|
|
* @property int|null $quality
|
||
|
|
* @property int|null $workspace_id
|
||
|
|
* @property string|null $optimizable_type
|
||
|
|
* @property int|null $optimizable_id
|
||
|
|
* @property Carbon|null $created_at
|
||
|
|
* @property Carbon|null $updated_at
|
||
|
|
*/
|
||
|
|
class ImageOptimization extends Model
|
||
|
|
{
|
||
|
|
use HasFactory;
|
||
|
|
|
||
|
|
protected $fillable = [
|
||
|
|
'path',
|
||
|
|
'original_path',
|
||
|
|
'original_size',
|
||
|
|
'optimized_size',
|
||
|
|
'percentage_saved',
|
||
|
|
'driver',
|
||
|
|
'quality',
|
||
|
|
'workspace_id',
|
||
|
|
'optimizable_type',
|
||
|
|
'optimizable_id',
|
||
|
|
];
|
||
|
|
|
||
|
|
protected $casts = [
|
||
|
|
'original_size' => 'integer',
|
||
|
|
'optimized_size' => 'integer',
|
||
|
|
'percentage_saved' => 'integer',
|
||
|
|
'quality' => 'integer',
|
||
|
|
];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The workspace this optimization belongs to.
|
||
|
|
*
|
||
|
|
* Returns a relationship to the Workspace model if it exists,
|
||
|
|
* otherwise returns null.
|
||
|
|
*/
|
||
|
|
public function workspace(): ?BelongsTo
|
||
|
|
{
|
||
|
|
if (! class_exists('Core\\Mod\\Tenant\\Models\\Workspace')) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->belongsTo('Core\\Mod\\Tenant\\Models\\Workspace');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the parent optimizable model (Page, User, etc.).
|
||
|
|
*/
|
||
|
|
public function optimizable(): MorphTo
|
||
|
|
{
|
||
|
|
return $this->morphTo();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Scope to a specific workspace.
|
||
|
|
*
|
||
|
|
* @param Model|null $workspace The workspace model to filter by
|
||
|
|
*/
|
||
|
|
public function scopeForWorkspace($query, ?Model $workspace)
|
||
|
|
{
|
||
|
|
if ($workspace === null) {
|
||
|
|
return $query;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $query->where('workspace_id', $workspace->id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get human-readable savings summary.
|
||
|
|
*
|
||
|
|
* Example: "45% saved (120KB → 66KB)"
|
||
|
|
*/
|
||
|
|
public function getSavingsHumanAttribute(): string
|
||
|
|
{
|
||
|
|
$originalKb = round($this->original_size / 1024, 1);
|
||
|
|
$optimizedKb = round($this->optimized_size / 1024, 1);
|
||
|
|
|
||
|
|
// Format with appropriate unit
|
||
|
|
$original = $this->formatBytes($this->original_size);
|
||
|
|
$optimized = $this->formatBytes($this->optimized_size);
|
||
|
|
|
||
|
|
return sprintf(
|
||
|
|
'%d%% saved (%s → %s)',
|
||
|
|
$this->percentage_saved,
|
||
|
|
$original,
|
||
|
|
$optimized
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get human-readable size saved.
|
||
|
|
*/
|
||
|
|
public function getSizeSavedHumanAttribute(): string
|
||
|
|
{
|
||
|
|
$saved = $this->original_size - $this->optimized_size;
|
||
|
|
|
||
|
|
return $this->formatBytes($saved);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format bytes to human-readable size.
|
||
|
|
*/
|
||
|
|
protected function formatBytes(int $bytes): string
|
||
|
|
{
|
||
|
|
if ($bytes < 1024) {
|
||
|
|
return $bytes.'B';
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($bytes < 1024 * 1024) {
|
||
|
|
return round($bytes / 1024, 1).'KB';
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($bytes < 1024 * 1024 * 1024) {
|
||
|
|
return round($bytes / (1024 * 1024), 1).'MB';
|
||
|
|
}
|
||
|
|
|
||
|
|
return round($bytes / (1024 * 1024 * 1024), 2).'GB';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get total savings statistics for a workspace.
|
||
|
|
*
|
||
|
|
* @param Model|null $workspace Optional workspace model to filter by
|
||
|
|
*/
|
||
|
|
public static function getWorkspaceStats(?Model $workspace = null): array
|
||
|
|
{
|
||
|
|
$query = static::query();
|
||
|
|
|
||
|
|
if ($workspace) {
|
||
|
|
$query->where('workspace_id', $workspace->id);
|
||
|
|
}
|
||
|
|
|
||
|
|
$optimizations = $query->get();
|
||
|
|
|
||
|
|
$totalOriginal = $optimizations->sum('original_size');
|
||
|
|
$totalOptimized = $optimizations->sum('optimized_size');
|
||
|
|
$totalSaved = $totalOriginal - $totalOptimized;
|
||
|
|
|
||
|
|
$averagePercentage = $optimizations->count() > 0
|
||
|
|
? $optimizations->avg('percentage_saved')
|
||
|
|
: 0;
|
||
|
|
|
||
|
|
return [
|
||
|
|
'count' => $optimizations->count(),
|
||
|
|
'total_original' => $totalOriginal,
|
||
|
|
'total_optimized' => $totalOptimized,
|
||
|
|
'total_saved' => $totalSaved,
|
||
|
|
'average_percentage' => round($averagePercentage, 1),
|
||
|
|
'total_saved_human' => static::formatBytesStatic($totalSaved),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Static version of formatBytes for use in getWorkspaceStats.
|
||
|
|
*/
|
||
|
|
public static function formatBytesStatic(int $bytes): string
|
||
|
|
{
|
||
|
|
if ($bytes < 1024) {
|
||
|
|
return $bytes.'B';
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($bytes < 1024 * 1024) {
|
||
|
|
return round($bytes / 1024, 1).'KB';
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($bytes < 1024 * 1024 * 1024) {
|
||
|
|
return round($bytes / (1024 * 1024), 1).'MB';
|
||
|
|
}
|
||
|
|
|
||
|
|
return round($bytes / (1024 * 1024 * 1024), 2).'GB';
|
||
|
|
}
|
||
|
|
}
|