Files
f3-simple-blog/app/Models/Media.php
2026-03-30 15:05:13 +02:00

176 lines
5.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
class Media extends DB\SQL\Mapper
{
public function __construct()
{
parent::__construct(Base::instance()->get('DB'), 'media');
}
public static function bootstrap(DB\SQL $db): void
{
if ($db->schema('media', null, 0)) {
return;
}
$db->exec('CREATE TABLE media (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_name TEXT NOT NULL UNIQUE,
alt TEXT NOT NULL DEFAULT \'\',
width INTEGER NOT NULL,
height INTEGER NOT NULL,
created_at TEXT NOT NULL
)');
$db->exec('CREATE INDEX idx_media_created_at ON media(created_at DESC)');
}
public function page(int $page, int $perPage): array
{
$result = $this->paginate(max(0, $page - 1), $perPage, null, ['order' => 'created_at DESC, id DESC']);
$items = array_map(fn(self $row): array => $this->decorate($row->cast()), $result['subset'] ?: []);
return [
'items' => $items,
'pagination' => [
'page' => max(1, min($page, $result['count'] ?: 1)),
'pages' => max(1, (int) ($result['count'] ?: 1)),
],
];
}
public function findById(int $id): ?array
{
if ($id <= 0) {
return null;
}
$this->load(['id = ?', $id]);
return $this->dry() ? null : $this->decorate($this->cast());
}
public function findByFileName(string $fileName): ?array
{
$this->load(['file_name = ?', $fileName]);
return $this->dry() ? null : $this->decorate($this->cast());
}
public function upload(string $temporaryPath, string $originalName = ''): int
{
$f3 = Base::instance();
if (!is_file($temporaryPath)) {
throw new RuntimeException('Fichier image introuvable.');
}
$binary = $f3->read($temporaryPath);
$image = new Image();
if ($binary === '' || $image->load($binary) === false) {
@unlink($temporaryPath);
throw new RuntimeException('Fichier image invalide.');
}
$info = @getimagesizefromstring($binary);
$mime = strtolower((string) ($info['mime'] ?? ''));
$extension = match ($mime) {
'image/jpeg' => 'jpg',
'image/png' => 'png',
default => null,
};
if ($extension === null) {
@unlink($temporaryPath);
throw new RuntimeException('Format non supporté. Utilise JPG ou PNG.');
}
$encoded = match ($extension) {
'jpg' => $image->dump('jpeg', 90),
'png' => $image->dump('png'),
};
$fileName = bin2hex(random_bytes(16)) . '.' . $extension;
$target = $this->storagePath($fileName);
try {
$f3->write($target, $encoded);
} catch (Throwable $e) {
@unlink($temporaryPath);
throw new RuntimeException('Impossible denregistrer cette image.', 0, $e);
}
@unlink($temporaryPath);
try {
$this->reset();
$this->file_name = $fileName;
$this->alt = $this->guessAlt($originalName);
$this->width = $image->width();
$this->height = $image->height();
$this->created_at = app_now();
$this->save();
} catch (Throwable $e) {
@unlink($target);
throw new RuntimeException('Impossible de finaliser cette image.', 0, $e);
}
return (int) $this->id;
}
public function updateAlt(int $id, string $alt): void
{
$this->load(['id = ?', $id]);
if ($this->dry()) {
throw new RuntimeException('Image introuvable.');
}
$this->alt = trim($alt);
$this->save();
}
public function deleteById(int $id): void
{
$this->load(['id = ?', $id]);
if ($this->dry()) {
throw new RuntimeException('Image introuvable.');
}
try {
$this->erase();
} catch (Throwable $e) {
throw new RuntimeException('Impossible de supprimer cette image.', 0, $e);
}
}
private function decorate(array $row): array
{
$fileName = (string) $row['file_name'];
$alt = (string) $row['alt'];
$base = rtrim((string) Base::instance()->get('BASE'), '/');
$mediaBase = rtrim((string) Base::instance()->get('paths.media_base'), '/');
return [
'id' => (int) $row['id'],
'file_name' => $fileName,
'alt' => $alt,
'width' => (int) $row['width'],
'height' => (int) $row['height'],
'created_at' => (string) $row['created_at'],
'url' => $base . $mediaBase . '/' . rawurlencode($fileName),
'markdown' => '![' . $alt . '](media:' . $fileName . ')',
];
}
private function storagePath(string $fileName): string
{
return rtrim((string) Base::instance()->get('paths.media_dir'), '/\\') . DIRECTORY_SEPARATOR . $fileName;
}
private function guessAlt(string $originalName): string
{
$label = trim(pathinfo($originalName, PATHINFO_FILENAME));
$label = preg_replace('/[-_]+/', ' ', $label) ?: '';
return trim($label);
}
}