212 lines
5.9 KiB
PHP
212 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Str;
|
|
|
|
class Kamera extends Model
|
|
{
|
|
public const DEFAULT_STOCK = 2;
|
|
|
|
private const IMAGE_DIRECTORY = 'images/kameras';
|
|
private const IMAGE_OVERRIDES = [
|
|
'canon1100d' => 'Canon_100D.png',
|
|
];
|
|
|
|
private static ?array $cachedImagePaths = null;
|
|
|
|
protected $table = 'kameras';
|
|
|
|
protected $fillable = [
|
|
'kode_kamera',
|
|
'nama',
|
|
'merek',
|
|
'harga_sewa',
|
|
'stok',
|
|
'price',
|
|
'iso',
|
|
'af_point',
|
|
'sensor',
|
|
'sensor_score',
|
|
'low_light',
|
|
'portabilitas',
|
|
'af_speed',
|
|
];
|
|
|
|
public function getImagePathAttribute(): ?string
|
|
{
|
|
$images = self::availableImages();
|
|
|
|
if ($images === []) {
|
|
return null;
|
|
}
|
|
|
|
$brand = is_string($this->merek) ? $this->merek : '';
|
|
$name = is_string($this->nama) ? $this->nama : '';
|
|
|
|
if ($overridePath = self::overrideImagePath($name)) {
|
|
return $overridePath;
|
|
}
|
|
|
|
$sourceName = Str::startsWith(Str::lower($name), Str::lower($brand)) ? $name : trim($brand.' '.$name);
|
|
$fullCompactVariants = self::compactVariants($sourceName);
|
|
$modelCompactVariants = self::compactVariants($name);
|
|
$bestMatch = null;
|
|
$bestScore = 0;
|
|
|
|
foreach ($images as $image) {
|
|
$score = self::scoreImageMatch($image, $fullCompactVariants, $modelCompactVariants);
|
|
|
|
if ($score > $bestScore) {
|
|
$bestScore = $score;
|
|
$bestMatch = $image['path'];
|
|
}
|
|
}
|
|
|
|
return $bestScore >= 70 ? $bestMatch : null;
|
|
}
|
|
|
|
private static function availableImages(): array
|
|
{
|
|
if (self::$cachedImagePaths !== null) {
|
|
return self::$cachedImagePaths;
|
|
}
|
|
|
|
$directory = public_path(self::IMAGE_DIRECTORY);
|
|
|
|
if (! File::isDirectory($directory)) {
|
|
return self::$cachedImagePaths = [];
|
|
}
|
|
|
|
return self::$cachedImagePaths = collect(File::files($directory))
|
|
->filter(fn ($file) => in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'png', 'webp'], true))
|
|
->map(function ($file): array {
|
|
return [
|
|
'path' => self::IMAGE_DIRECTORY.'/'.$file->getFilename(),
|
|
'compact' => self::compactString(pathinfo($file->getFilename(), PATHINFO_FILENAME)),
|
|
'priority' => self::extensionPriority($file->getExtension()),
|
|
'length' => strlen($file->getFilename()),
|
|
];
|
|
})
|
|
->sortBy([
|
|
['priority', 'asc'],
|
|
['length', 'asc'],
|
|
['path', 'asc'],
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private static function scoreImageMatch(array $image, array $fullCompactVariants, array $modelCompactVariants): int
|
|
{
|
|
$score = 0;
|
|
$fileCompact = $image['compact'];
|
|
|
|
foreach ($fullCompactVariants as $variant) {
|
|
if ($variant !== '' && $fileCompact === $variant) {
|
|
return 160;
|
|
}
|
|
|
|
if ($variant !== '' && Str::contains($fileCompact, $variant)) {
|
|
$score = max($score, 125);
|
|
}
|
|
}
|
|
|
|
foreach ($modelCompactVariants as $variant) {
|
|
if ($variant !== '' && $fileCompact === $variant) {
|
|
return 150;
|
|
}
|
|
|
|
if ($variant !== '' && Str::contains($fileCompact, $variant)) {
|
|
$score = max($score, 115);
|
|
}
|
|
}
|
|
|
|
$meaningfulTokens = self::meaningfulTokens($modelCompactVariants[0] ?? '');
|
|
|
|
if ($meaningfulTokens !== [] && collect($meaningfulTokens)->every(fn ($token) => Str::contains($fileCompact, $token))) {
|
|
$score = max($score, 90);
|
|
}
|
|
|
|
return $score;
|
|
}
|
|
|
|
private static function overrideImagePath(string $name): ?string
|
|
{
|
|
$overrideFile = self::IMAGE_OVERRIDES[self::compactString($name)] ?? null;
|
|
|
|
if (! $overrideFile) {
|
|
return null;
|
|
}
|
|
|
|
$relativePath = self::IMAGE_DIRECTORY.'/'.$overrideFile;
|
|
|
|
return File::exists(public_path($relativePath)) ? $relativePath : null;
|
|
}
|
|
|
|
private static function compactVariants(string $value): array
|
|
{
|
|
$compact = self::compactString($value);
|
|
|
|
if ($compact === '') {
|
|
return [];
|
|
}
|
|
|
|
$variants = [];
|
|
$queue = [$compact];
|
|
|
|
while ($queue !== []) {
|
|
$variant = array_shift($queue);
|
|
|
|
if ($variant === '' || in_array($variant, $variants, true)) {
|
|
continue;
|
|
}
|
|
|
|
$variants[] = $variant;
|
|
|
|
foreach (['canon', 'sony', 'fujifilm', 'nikon'] as $brand) {
|
|
if (Str::startsWith($variant, $brand)) {
|
|
$queue[] = Str::after($variant, $brand);
|
|
}
|
|
}
|
|
|
|
if (Str::contains($variant, 'mark')) {
|
|
$queue[] = str_replace('mark', 'mk', $variant);
|
|
}
|
|
|
|
if (Str::contains($variant, 'mk')) {
|
|
$queue[] = str_replace('mk', 'mark', $variant);
|
|
}
|
|
}
|
|
|
|
return $variants;
|
|
}
|
|
|
|
private static function compactString(string $value): string
|
|
{
|
|
return (string) preg_replace('/[^a-z0-9]+/', '', Str::lower(Str::ascii($value)));
|
|
}
|
|
|
|
private static function meaningfulTokens(string $compact): array
|
|
{
|
|
preg_match_all('/[a-z]+|\d+/', $compact, $matches);
|
|
|
|
return array_values(array_filter(
|
|
$matches[0] ?? [],
|
|
fn ($token) => strlen($token) >= 2 || ctype_digit($token)
|
|
));
|
|
}
|
|
|
|
private static function extensionPriority(string $extension): int
|
|
{
|
|
return match (strtolower($extension)) {
|
|
'png' => 1,
|
|
'jpg', 'jpeg' => 2,
|
|
'webp' => 3,
|
|
default => 9,
|
|
};
|
|
}
|
|
}
|