prepare('UPDATE photos SET code_name=:c, sort_order=:s, description=:d WHERE id=:id');
$st->execute(['c' => $code, 's' => $sort, 'd' => $descr, 'id' => $photoId]);
if (isset($_FILES['after']) && (int)($_FILES['after']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) {
$p = photoById($photoId);
if (!$p) throw new RuntimeException('Фото не найдено');
$up = saveSingleImage($_FILES['after'], $code . 'р', (int)$p['section_id']);
photoFileUpsert($photoId, 'after', $up['path'], $up['mime'], $up['size']);
}
$message = 'Фото обновлено';
if ($isAjax) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => true, 'message' => $message], JSON_UNESCAPED_UNICODE);
exit;
}
}
if ($action === 'photo_delete') {
$photoId = (int)($_POST['photo_id'] ?? 0);
if ($photoId > 0) {
$p = photoById($photoId);
if ($p) {
foreach (['before_path', 'after_path'] as $k) {
if (!empty($p[$k])) {
$abs = __DIR__ . '/' . ltrim((string)$p[$k], '/');
if (is_file($abs)) @unlink($abs);
}
}
}
$st = db()->prepare('DELETE FROM photos WHERE id=:id');
$st->execute(['id' => $photoId]);
$message = 'Фото удалено';
}
}
if ($action === 'create_commenter') {
$displayName = trim((string)($_POST['display_name'] ?? ''));
if ($displayName === '') throw new RuntimeException('Укажи имя комментатора');
$u = commenterCreate($displayName);
$link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/?viewer=' . urlencode($u['token']);
$message = 'Комментатор создан: ' . $u['display_name'] . ' | ссылка: ' . $link;
}
if ($action === 'delete_commenter') {
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
commenterDelete($id);
$message = 'Комментатор удалён (доступ отозван)';
}
}
if ($action === 'regenerate_commenter_token') {
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
$token = commenterRegenerateToken($id);
$link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/?viewer=' . urlencode($token);
$message = 'Токен обновлён | ссылка: ' . $link;
}
}
if ($action === 'delete_comment') {
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
commentDelete($id);
$message = 'Комментарий удалён';
}
}
} catch (Throwable $e) {
if ($isAjax) {
header('Content-Type: application/json; charset=utf-8');
http_response_code(400);
echo json_encode(['ok' => false, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
exit;
}
$errors[] = $e->getMessage();
}
}
$sections = sectionsAll();
$activeSectionId = (int)($_GET['section_id'] ?? ($_POST['section_id'] ?? ($sections[0]['id'] ?? 0)));
$photos = $activeSectionId > 0 ? photosBySection($activeSectionId) : [];
$commenters = commentersAll();
$latestComments = commentsLatest(80);
$welcomeText = settingGet('welcome_text', 'Добро пожаловать в галерею. Выберите раздел слева, чтобы посмотреть фотографии.');
$adminMode = (string)($_GET['mode'] ?? 'media');
if (!in_array($adminMode, ['media', 'comments', 'welcome'], true)) {
$adminMode = 'media';
}
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); }
function saveBulkBefore(array $files, int $sectionId): array
{
$ok = 0;
$errors = [];
$names = $files['name'] ?? [];
$tmp = $files['tmp_name'] ?? [];
$sizes = $files['size'] ?? [];
$errs = $files['error'] ?? [];
if (!is_array($names)) {
$names = [$names];
$tmp = [$tmp];
$sizes = [$sizes];
$errs = [$errs];
}
foreach ($names as $i => $orig) {
$file = [
'name' => $orig,
'tmp_name' => $tmp[$i] ?? '',
'size' => $sizes[$i] ?? 0,
'error' => $errs[$i] ?? UPLOAD_ERR_NO_FILE,
];
try {
$base = (string)pathinfo((string)$orig, PATHINFO_FILENAME);
$base = trim(preg_replace('/[^\p{L}\p{N}._-]+/u', '_', $base) ?? 'photo', '._-');
if ($base === '') $base = 'photo';
$codeName = nextUniqueCodeName($base);
$photoId = photoCreate($sectionId, $codeName, null, nextSortOrderForSection($sectionId));
$saved = saveSingleImage($file, $codeName, $sectionId);
photoFileUpsert($photoId, 'before', $saved['path'], $saved['mime'], $saved['size']);
$ok++;
} catch (Throwable $e) {
$errors[] = (string)$orig . ': ' . $e->getMessage();
}
}
return ['ok' => $ok, 'errors' => $errors];
}
function saveSingleImage(array $file, string $baseName, int $sectionId): array
{
$allowedMime = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif'];
$err = (int)($file['error'] ?? UPLOAD_ERR_NO_FILE);
if ($err !== UPLOAD_ERR_OK) throw new RuntimeException('Ошибка загрузки');
$size = (int)($file['size'] ?? 0);
if ($size < 1 || $size > MAX_UPLOAD_BYTES) throw new RuntimeException('Превышен лимит 3 МБ');
$tmp = (string)($file['tmp_name'] ?? '');
if (!is_uploaded_file($tmp)) throw new RuntimeException('Некорректный источник');
$mime = mime_content_type($tmp) ?: '';
if (!isset($allowedMime[$mime])) throw new RuntimeException('Недопустимый тип файла');
$safeBase = preg_replace('/[^\p{L}\p{N}._-]+/u', '_', $baseName) ?? 'photo';
$safeBase = trim($safeBase, '._-');
if ($safeBase === '') $safeBase = 'photo';
$ext = $allowedMime[$mime];
$dir = __DIR__ . '/photos/section_' . $sectionId;
if (!is_dir($dir)) mkdir($dir, 0775, true);
$name = uniqueName($dir, $safeBase, $ext);
$dest = $dir . '/' . $name;
if (!move_uploaded_file($tmp, $dest)) throw new RuntimeException('Не удалось сохранить файл');
return [
'path' => 'photos/section_' . $sectionId . '/' . $name,
'mime' => $mime,
'size' => $size,
];
}
function uniqueName(string $dir, string $base, string $ext): string
{
$i = 0;
do {
$name = $i === 0 ? "{$base}.{$ext}" : "{$base}_{$i}.{$ext}";
$i++;
} while (is_file($dir . '/' . $name));
return $name;
}
function nextSortOrderForSection(int $sectionId): int
{
$st = db()->prepare('SELECT COALESCE(MAX(sort_order),0)+10 FROM photos WHERE section_id=:sid');
$st->execute(['sid' => $sectionId]);
return (int)$st->fetchColumn();
}
function nextUniqueCodeName(string $base): string
{
$candidate = $base;
$i = 1;
while (true) {
$st = db()->prepare('SELECT 1 FROM photos WHERE code_name=:c LIMIT 1');
$st->execute(['c' => $candidate]);
if (!$st->fetchColumn()) {
return $candidate;
}
$candidate = $base . '_' . $i;
$i++;
}
}
?>
Админка
Админка
= h($message) ?>
= h($e) ?>
Приветственное сообщение (публичная часть)
Загрузка фото “до” в выбранный раздел
0): ?>
После загрузки имя (code_name) заполняется автоматически из имени файла — затем можно отредактировать.
Сначала выбери раздел слева.
Комментаторы и комментарии
| Фото | Пользователь | Комментарий | |
| = h((string)$c['code_name']) ?> |
= h((string)($c['display_name'] ?? '—')) ?> |
= h((string)$c['comment_text']) ?> |
|