mkach/board.php
2025-07-24 05:15:35 +03:00

187 lines
6.9 KiB
PHP

<?php
session_start();
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
if (!isset($_SESSION['authenticated'])) {
header('Location: index.php');
exit;
}
$config = require 'config.php';
try {
$db = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
$config['db']['user'],
$config['db']['pass']
);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Connection failed');
}
require_once 'RateLimiter.php';
$rateLimiter = new RateLimiter($db);
$rateLimiter->cleanup();
$ip = $_SERVER['REMOTE_ADDR'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$rateLimiter->isAllowed($ip)) {
$error = 'Слишком много запросов';
} else {
$message = trim($_POST['message'] ?? '');
$file = $_FILES['file'] ?? null;
if ($message || ($file && $file['error'] === UPLOAD_ERR_OK)) {
$postId = sprintf('%06d', mt_rand(1, 999999));
$fileName = null;
$fileSize = null;
$fileType = null;
if ($file && $file['error'] === UPLOAD_ERR_OK) {
$fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($fileExt, $config['allowed_types'])) {
$error = 'Неподдерживаемый тип файла';
} elseif ($file['size'] > $config['max_file_size']) {
$error = 'Файл слишком большой';
} else {
$uploadDir = $config['upload_path'];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$fileName = $postId . '.' . $fileExt;
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $filePath)) {
$fileSize = $file['size'];
$fileType = $fileExt;
} else {
$error = 'Ошибка загрузки файла';
}
}
}
if (empty($error)) {
$stmt = $db->prepare('
INSERT INTO posts (post_id, message, file_name, file_size, file_type, ip_address)
VALUES (?, ?, ?, ?, ?, ?)
');
$stmt->execute([$postId, $message, $fileName, $fileSize, $fileType, $ip]);
header('Location: board.php');
exit;
}
} else {
$error = 'Введите сообщение или загрузите файл';
}
}
}
if (isset($_GET['logout'])) {
session_destroy();
header('Location: index.php');
exit;
}
try {
$stmt = $db->query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 100');
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die('Database error');
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mkach</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>mkach</h1>
<div class="header-buttons">
<a href="?logout=1" class="logout-btn">Выход</a>
</div>
</div>
<div class="board-container">
<?php if (!empty($error)): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="posts-container" id="posts">
<?php foreach ($posts as $post): ?>
<div class="post">
<div class="post-header">
<span class="post-id">№<?= htmlspecialchars($post['post_id']) ?></span>
<span class="post-time"><?= date('d.m.Y H:i:s', strtotime($post['created_at'])) ?></span>
</div>
<?php if ($post['file_name']): ?>
<div class="post-file">
<a href="uploads/<?= htmlspecialchars($post['file_name']) ?>" target="_blank">
<img src="uploads/<?= htmlspecialchars($post['file_name']) ?>" alt="File" class="post-image">
</a>
<div class="file-info">
<?= htmlspecialchars($post['file_name']) ?>
(<?= number_format($post['file_size'] / 1024, 1) ?> KB)
</div>
</div>
<?php endif; ?>
<?php if ($post['message']): ?>
<div class="post-message"><?= nl2br(htmlspecialchars($post['message'])) ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="post-form">
<form method="post" enctype="multipart/form-data">
<div class="form-row">
<textarea name="message" placeholder="Сообщение (необязательно)" class="message-input"></textarea>
</div>
<div class="form-row">
<input type="file" name="file" accept=".jpg,.jpeg,.png,.gif,.webp" class="file-input">
<button type="submit" class="send-btn">Отправить</button>
</div>
</form>
</div>
</div>
<script>
let isScrolledToBottom = true;
const postsContainer = document.getElementById('posts');
postsContainer.addEventListener('scroll', () => {
const { scrollTop, scrollHeight, clientHeight } = postsContainer;
isScrolledToBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < 1;
});
setInterval(() => {
fetch(window.location.href)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const oldHeight = postsContainer.scrollHeight;
postsContainer.innerHTML = doc.getElementById('posts').innerHTML;
if (isScrolledToBottom) {
postsContainer.scrollTop = postsContainer.scrollHeight;
}
});
}, 10000);
</script>
</body>
</html>