Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
148
app/Services/PrivateMediaService.php
Normal file
148
app/Services/PrivateMediaService.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class PrivateMediaService
|
||||
{
|
||||
public static function root(): string
|
||||
{
|
||||
return dirname(__DIR__, 2) . '/data/private-media';
|
||||
}
|
||||
public static function animalDir(int $animalId): string
|
||||
{
|
||||
return self::root() . '/animals/' . $animalId;
|
||||
}
|
||||
public static function medicalDir(int $animalId): string
|
||||
{
|
||||
return self::animalDir($animalId) . '/meds';
|
||||
}
|
||||
public static function publicDir(int $animalId): string
|
||||
{
|
||||
return dirname(__DIR__, 2) . '/public/media/animals/' . $animalId;
|
||||
}
|
||||
|
||||
public static function ensure(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir) && !mkdir($dir, 0700, true) && !is_dir($dir)) {
|
||||
throw new RuntimeException(t('service.image.directory_failed'));
|
||||
}
|
||||
@chmod($dir, 0700);
|
||||
}
|
||||
|
||||
public static function moveVisibility(int $animalId, string $filename, bool $toPublic): void
|
||||
{
|
||||
$filename = basename($filename);
|
||||
if ($filename === '' || $filename === '.' || $filename === '..') {
|
||||
throw new RuntimeException(t('service.private_media.invalid_name'));
|
||||
}
|
||||
$from = $toPublic ? self::animalDir($animalId) : self::publicDir($animalId);
|
||||
$to = $toPublic ? self::publicDir($animalId) : self::animalDir($animalId);
|
||||
$source = $from . '/' . $filename;
|
||||
$target = $to . '/' . $filename;
|
||||
if (!is_file($source)) {
|
||||
if (is_file($target)) {
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException(t('service.private_media.not_found'));
|
||||
}
|
||||
self::ensure($to);
|
||||
if (!rename($source, $target)) {
|
||||
throw new RuntimeException(t('service.private_media.move_failed'));
|
||||
}
|
||||
@chmod($target, $toPublic ? 0644 : 0600);
|
||||
}
|
||||
|
||||
public static function migrateLegacy(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$private = $db
|
||||
->query('SELECT animal_id,filename FROM animal_photos WHERE is_public=0')
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($private as $row) {
|
||||
self::moveIfPresent(
|
||||
self::publicDir((int) $row['animal_id']) . '/' . basename((string) $row['filename']),
|
||||
self::animalDir((int) $row['animal_id']) . '/' . basename((string) $row['filename']),
|
||||
);
|
||||
}
|
||||
$medical = $db->query('SELECT mp.animal_id,mp.filename FROM medical_photos mp')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($medical as $row) {
|
||||
self::moveIfPresent(
|
||||
self::publicDir((int) $row['animal_id']) . '/meds/' . basename((string) $row['filename']),
|
||||
self::medicalDir((int) $row['animal_id']) . '/' . basename((string) $row['filename']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function moveIfPresent(string $source, string $target): void
|
||||
{
|
||||
if (!is_file($source) || is_file($target)) {
|
||||
return;
|
||||
}
|
||||
self::ensure(dirname($target));
|
||||
if (rename($source, $target)) {
|
||||
@chmod($target, 0600);
|
||||
}
|
||||
}
|
||||
|
||||
public static function send(): void
|
||||
{
|
||||
$kind = (string) ($_GET['kind'] ?? 'photo');
|
||||
$id = (int) ($_GET['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
http_response_code(404);
|
||||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
if ($kind === 'medical') {
|
||||
if (!PermissionService::can('medical', 'view')) {
|
||||
http_response_code(403);
|
||||
return;
|
||||
}
|
||||
$s = $db->prepare(
|
||||
'SELECT mp.id,mp.animal_id,mp.filename FROM medical_photos mp JOIN animals a ON a.id=mp.animal_id WHERE mp.id=? AND a.deleted_at IS NULL',
|
||||
);
|
||||
$s->execute([$id]);
|
||||
$row = $s->fetch(PDO::FETCH_ASSOC);
|
||||
$path = $row ? self::medicalDir((int) $row['animal_id']) . '/' . basename((string) $row['filename']) : '';
|
||||
} else {
|
||||
if (!PermissionService::can('animals', 'view')) {
|
||||
http_response_code(403);
|
||||
return;
|
||||
}
|
||||
$s = $db->prepare(
|
||||
'SELECT ap.id,ap.animal_id,ap.filename,ap.care_round_id FROM animal_photos ap JOIN animals a ON a.id=ap.animal_id WHERE ap.id=? AND ap.is_public=0 AND a.deleted_at IS NULL',
|
||||
);
|
||||
$s->execute([$id]);
|
||||
$row = $s->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row && !empty($row['care_round_id']) && !PermissionService::can('medical', 'view')) {
|
||||
http_response_code(403);
|
||||
return;
|
||||
}
|
||||
$path = $row ? self::animalDir((int) $row['animal_id']) . '/' . basename((string) $row['filename']) : '';
|
||||
}
|
||||
if (empty($row) || !is_file($path)) {
|
||||
http_response_code(404);
|
||||
return;
|
||||
}
|
||||
$mime = new finfo(FILEINFO_MIME_TYPE)->file($path) ?: 'application/octet-stream';
|
||||
PrivacyService::logAccess(
|
||||
'private_' . $kind,
|
||||
$id,
|
||||
(int) $row['animal_id'],
|
||||
basename((string) $row['filename']),
|
||||
);
|
||||
AuditService::log(
|
||||
'private_file_viewed',
|
||||
'/private-media',
|
||||
'Média privé consulté',
|
||||
'animal',
|
||||
(int) $row['animal_id'],
|
||||
['kind' => $kind, 'media_id' => $id],
|
||||
);
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Content-Length: ' . filesize($path));
|
||||
header('Cache-Control: private,no-store');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
readfile($path);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue