1
0

Комментарии, файлы и права проекта

- Система комментариев к задачам с вложенными ответами
- Редактирование и удаление комментариев
- Прикрепление файлов к задачам и комментариям (картинки, архивы до 10 МБ)
- Система прав проекта: админ проекта может удалять чужие комментарии и файлы
- Универсальный класс FileUpload для загрузки файлов
- Защита загрузки: только автор комментария может добавлять файлы
- Каскадное удаление: задача → комментарии → файлы
- Автообновление комментариев в реальном времени
This commit is contained in:
2026-01-15 06:40:47 +07:00
parent 8ac497df63
commit 3bfa1e9e1b
25 changed files with 3353 additions and 904 deletions

View File

@@ -0,0 +1,246 @@
<?php
class Comment extends BaseEntity {
protected $db_name = 'comments';
// Свойства комментария
public $id;
public $id_task;
public $id_accounts;
public $id_answer; // ID родительского комментария (ответ на)
public $text;
public $date_create;
// Создание комментария
public function create() {
static::$error_message = [];
// Валидация
if (!$this->id_task) {
$this->addError('id_task', 'Задача не указана');
}
if (!$this->id_accounts) {
$this->addError('id_accounts', 'Пользователь не указан');
}
if (!$this->text || trim($this->text) === '') {
$this->addError('text', 'Текст комментария не может быть пустым');
}
if ($errors = $this->getErrors()) {
return $errors;
}
// Проверяем что задача существует
Task::check_task($this->id_task);
// Если это ответ — проверяем что родительский комментарий существует
if ($this->id_answer) {
self::checkComment($this->id_answer);
}
// Вставляем в базу
Database::insert($this->db_name, [
'id_task' => $this->id_task,
'id_accounts' => $this->id_accounts,
'id_answer' => $this->id_answer ?: null,
'text' => $this->text,
'date_create' => date('Y-m-d H:i:s'),
'file_img' => '[]'
]);
$this->id = Database::id();
// Возвращаем созданный комментарий с данными пользователя
return [
'success' => true,
'comment' => $this->getById($this->id)
];
}
// Обновление комментария
public function update() {
static::$error_message = [];
// Валидация
if (!$this->id) {
$this->addError('id', 'ID комментария не указан');
}
if (!$this->text || trim($this->text) === '') {
$this->addError('text', 'Текст комментария не может быть пустым');
}
if ($errors = $this->getErrors()) {
return $errors;
}
// Проверяем что комментарий существует
$comment = self::checkComment($this->id);
// Проверяем что пользователь — автор комментария
if ((int)$comment['id_accounts'] !== (int)$this->id_accounts) {
$this->addError('access', 'Вы можете редактировать только свои комментарии');
return $this->getErrors();
}
// Обновляем в БД
Database::update($this->db_name, [
'text' => $this->text
], [
'id' => $this->id
]);
return [
'success' => true,
'comment' => $this->getById($this->id)
];
}
// Удаление комментария (с дочерними)
public static function delete($id, $id_accounts) {
// Проверяем что комментарий существует
$comment = self::checkComment($id);
// Получаем задачу для проверки админа проекта
$task = Database::get('cards_task', ['id_project'], ['id' => $comment['id_task']]);
// Проверяем права: автор комментария ИЛИ админ проекта
$isAuthor = (int)$comment['id_accounts'] === (int)$id_accounts;
$isProjectAdmin = $task && Project::isAdmin($task['id_project'], $id_accounts);
if (!$isAuthor && !$isProjectAdmin) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Нет прав на удаление комментария']
], 403);
}
// Рекурсивно удаляем все дочерние комментарии
self::deleteWithChildren($id);
return ['success' => true];
}
// Рекурсивное удаление комментария и всех его дочерних
private static function deleteWithChildren($id) {
// Находим все дочерние комментарии
$children = Database::select('comments', ['id'], ['id_answer' => $id]);
// Рекурсивно удаляем дочерние
if ($children) {
foreach ($children as $child) {
self::deleteWithChildren($child['id']);
}
}
// Удаляем папку с файлами комментария
FileUpload::deleteFolder('comment', $id);
// Удаляем сам комментарий
Database::delete('comments', ['id' => $id]);
}
// === МЕТОДЫ ДЛЯ РАБОТЫ С ФАЙЛАМИ ===
// Загрузка файла к комментарию (только автор может загружать)
public static function uploadFile($comment_id, $file_base64, $file_name, $user_id) {
// Проверка что комментарий существует
$comment = self::checkComment($comment_id);
// Проверка что пользователь — автор комментария
if ((int)$comment['id_accounts'] !== (int)$user_id) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Вы можете загружать файлы только к своим комментариям']
], 403);
}
return FileUpload::upload('comment', $comment_id, $file_base64, $file_name);
}
// Удаление файлов комментария (автор или админ проекта)
public static function deleteFile($comment_id, $file_names, $user_id) {
// Проверка что комментарий существует
$comment = self::checkComment($comment_id);
// Получаем задачу для проверки админа проекта
$task = Database::get('cards_task', ['id_project'], ['id' => $comment['id_task']]);
// Проверка прав: автор комментария ИЛИ админ проекта
$isAuthor = (int)$comment['id_accounts'] === (int)$user_id;
$isProjectAdmin = $task && Project::isAdmin($task['id_project'], $user_id);
if (!$isAuthor && !$isProjectAdmin) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Нет прав на удаление файлов']
], 403);
}
return FileUpload::delete('comment', $comment_id, $file_names);
}
// Получение комментария по ID (с данными пользователя)
public function getById($id) {
$comment = Database::get($this->db_name, [
'[>]accounts' => ['id_accounts' => 'id']
], [
'comments.id',
'comments.id_task',
'comments.id_accounts',
'comments.id_answer',
'comments.text',
'comments.date_create',
'comments.file_img',
'accounts.name(author_name)',
'accounts.avatar_url(author_avatar)'
], [
'comments.id' => $id
]);
// Декодируем JSON файлов
if ($comment) {
$comment['file_img'] = $comment['file_img'] ? json_decode($comment['file_img'], true) : [];
}
return $comment;
}
// Получение всех комментариев задачи
public function getByTask($id_task) {
// Проверяем что задача существует
Task::check_task($id_task);
$comments = Database::select($this->db_name, [
'[>]accounts' => ['id_accounts' => 'id']
], [
'comments.id',
'comments.id_task',
'comments.id_accounts',
'comments.id_answer',
'comments.text',
'comments.date_create',
'comments.file_img',
'accounts.name(author_name)',
'accounts.avatar_url(author_avatar)'
], [
'comments.id_task' => $id_task,
'ORDER' => ['comments.date_create' => 'ASC']
]);
// Декодируем JSON файлов для каждого комментария
return array_map(function($comment) {
$comment['file_img'] = $comment['file_img'] ? json_decode($comment['file_img'], true) : [];
return $comment;
}, $comments ?: []);
}
// Проверка и получение комментария (при ошибке — сразу ответ и exit)
public static function checkComment($id) {
$comment = Database::get('comments', '*', ['id' => $id]);
if (!$id || !$comment) {
RestApi::response(['success' => false, 'errors' => ['comment' => 'Комментарий не найден']], 404);
}
return $comment;
}
}
?>