diff --git a/admin.php b/admin.php index 57f6cd6..394154d 100644 --- a/admin.php +++ b/admin.php @@ -125,6 +125,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + if ($action === 'rotate_photo_file') { + $photoId = (int)($_POST['photo_id'] ?? 0); + $kind = (string)($_POST['kind'] ?? ''); + $direction = (string)($_POST['direction'] ?? 'right'); + if ($photoId < 1) throw new RuntimeException('Некорректный photo_id'); + if (!in_array($kind, ['before', 'after'], true)) throw new RuntimeException('Некорректный тип файла'); + + $photo = photoById($photoId); + if (!$photo) throw new RuntimeException('Фото не найдено'); + + $pathKey = $kind === 'before' ? 'before_path' : 'after_path'; + $relPath = (string)($photo[$pathKey] ?? ''); + if ($relPath === '') throw new RuntimeException('Файл отсутствует'); + + $absPath = __DIR__ . '/' . ltrim($relPath, '/'); + if (!is_file($absPath)) throw new RuntimeException('Файл не найден на диске'); + + $degrees = $direction === 'left' ? -90 : 90; + rotateImageOnDisk($absPath, $degrees); + + $st = db()->prepare('UPDATE photo_files SET updated_at=CURRENT_TIMESTAMP WHERE photo_id=:pid AND kind=:kind'); + $st->execute(['pid' => $photoId, 'kind' => $kind]); + + $message = 'Изображение повернуто'; + } + if ($action === 'create_commenter') { $displayName = trim((string)($_POST['display_name'] ?? '')); if ($displayName === '') throw new RuntimeException('Укажи имя комментатора'); @@ -186,6 +212,7 @@ if ($adminMode === 'media') { if (!in_array($adminMode, ['sections', 'photos', 'comments', 'welcome'], true)) { $adminMode = 'photos'; } +$previewVersion = (string)time(); function h(string $v): string { return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function assetUrl(string $path): string { $f=__DIR__ . '/' . ltrim($path,'/'); $v=is_file($f)?(string)filemtime($f):(string)time(); return $path . '?v=' . rawurlencode($v); } @@ -333,6 +360,70 @@ function removeSectionImageFiles(int $sectionId): void } } +function rotateImageOnDisk(string $path, int $degrees): void +{ + $mime = mime_content_type($path) ?: ''; + if (!in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true)) { + throw new RuntimeException('Недопустимый тип файла для поворота'); + } + + if (extension_loaded('imagick')) { + $im = new Imagick($path); + $im->setImageOrientation(Imagick::ORIENTATION_TOPLEFT); + $im->rotateImage(new ImagickPixel('none'), $degrees); + $im->setImagePage(0, 0, 0, 0); + if ($mime === 'image/jpeg') { + $im->setImageCompressionQuality(92); + } + $im->writeImage($path); + $im->clear(); + $im->destroy(); + return; + } + + $src = match ($mime) { + 'image/jpeg' => @imagecreatefromjpeg($path), + 'image/png' => @imagecreatefrompng($path), + 'image/webp' => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($path) : false, + 'image/gif' => @imagecreatefromgif($path), + default => false, + }; + if (!$src) { + throw new RuntimeException('Не удалось открыть изображение'); + } + + $bgColor = 0; + if ($mime === 'image/png' || $mime === 'image/webp') { + $bgColor = imagecolorallocatealpha($src, 0, 0, 0, 127); + } + + $rotated = imagerotate($src, -$degrees, $bgColor); + if (!$rotated) { + imagedestroy($src); + throw new RuntimeException('Не удалось повернуть изображение'); + } + + if ($mime === 'image/png' || $mime === 'image/webp') { + imagealphablending($rotated, false); + imagesavealpha($rotated, true); + } + + $ok = match ($mime) { + 'image/jpeg' => imagejpeg($rotated, $path, 92), + 'image/png' => imagepng($rotated, $path), + 'image/webp' => function_exists('imagewebp') ? imagewebp($rotated, $path, 92) : false, + 'image/gif' => imagegif($rotated, $path), + default => false, + }; + + imagedestroy($src); + imagedestroy($rotated); + + if (!$ok) { + throw new RuntimeException('Не удалось сохранить повернутое изображение'); + } +} + function nextSortOrderForSection(int $sectionId): int { $st = db()->prepare('SELECT COALESCE(MAX(sort_order),0)+10 FROM photos WHERE section_id=:sid'); @@ -362,7 +453,7 @@ function nextUniqueCodeName(string $base): string