1
0
Files
TaskBoard/backend/app/class/enity/class_task.php
Falknat 3bfa1e9e1b Комментарии, файлы и права проекта
- Система комментариев к задачам с вложенными ответами
- Редактирование и удаление комментариев
- Прикрепление файлов к задачам и комментариям (картинки, архивы до 10 МБ)
- Система прав проекта: админ проекта может удалять чужие комментарии и файлы
- Универсальный класс FileUpload для загрузки файлов
- Защита загрузки: только автор комментария может добавлять файлы
- Каскадное удаление: задача → комментарии → файлы
- Автообновление комментариев в реальном времени
2026-01-15 06:40:47 +07:00

379 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
class Task extends BaseEntity {
protected $db_name = 'cards_task';
// Свойства задачи
public $id;
public $id_project;
public $id_department;
public $id_label;
public $order;
public $column_id;
public $date;
public $date_closed;
public $id_account;
public $title;
public $descript;
public $descript_full;
public $archive;
// Валидация данных
protected function validate() {
static::$error_message = [];
if (!$this->id) {
$this->addError('id', 'ID задачи не указан');
}
if (!$this->title) {
$this->addError('title', 'Название не может быть пустым');
}
return $this->getErrors();
}
// Валидация данных (для create)
protected function validateCreate() {
static::$error_message = [];
if (!$this->title) {
$this->addError('title', 'Название не может быть пустым');
}
if (!$this->column_id) {
$this->addError('column_id', 'Колонка не указана');
}
if (!$this->id_department) {
$this->addError('id_department', 'Департамент не указан');
}
if (!$this->id_project) {
$this->addError('id_project', 'Проект не указан');
}
return $this->getErrors();
}
// Создание задачи (с файлами)
public function create($files = []) {
// Валидация
if ($errors = $this->validateCreate()) {
return $errors;
}
// Вставляем в базу
Database::insert($this->db_name, [
'id_project' => $this->id_project,
'id_department' => $this->id_department,
'id_label' => $this->id_label,
'order' => $this->order ?? 0,
'column_id' => $this->column_id,
'date' => $this->date ?: null,
'id_account' => $this->id_account,
'title' => $this->title,
'descript' => $this->descript ?: null,
'descript_full' => $this->descript_full ?: null,
'archive' => 0,
'date_create' => date('Y-m-d H:i:s'),
'file_img' => '[]'
]);
// Получаем ID созданной задачи
$this->id = Database::id();
// Загружаем файлы если есть
$uploaded_files = [];
if (!empty($files)) {
foreach ($files as $file) {
$result = FileUpload::upload('task', $this->id, $file['data'], $file['name']);
if ($result['success']) {
$uploaded_files[] = $result['file'];
}
}
}
return [
'success' => true,
'id' => $this->id,
'files' => $uploaded_files
];
}
// Обновление задачи
public function update() {
// Валидация
if ($errors = $this->validate()) {
return $errors;
}
// Проверка что задача существует и получаем текущие данные
$task = Database::get($this->db_name, ['id', 'column_id', 'order', 'id_project'], ['id' => $this->id]);
if (!$task) {
$this->addError('task', 'Задача не найдена');
return $this->getErrors();
}
// Получаем текущую колонку
$old_column_id = (int)$task['column_id'];
// Если column_id не передан — оставляем текущий
$new_column_id = $this->column_id !== null ? (int)$this->column_id : $old_column_id;
// Получаем id_ready (колонка "Готово") из проекта
$done_column_id = Project::getReadyColumnId($task['id_project']);
// Формируем данные для обновления
$update_data = [
'id_department' => $this->id_department,
'id_label' => $this->id_label,
'order' => $this->order ?? $task['order'],
'column_id' => $new_column_id,
'date' => $this->date ?: null,
'id_account' => $this->id_account,
'title' => $this->title,
'descript' => $this->descript ?: null,
'descript_full' => $this->descript_full ?: null
];
// Обновляем date_closed при смене колонки
if ($done_column_id && $new_column_id === $done_column_id && $old_column_id !== $done_column_id) {
$update_data['date_closed'] = date('Y-m-d H:i:s');
} elseif ($done_column_id && $old_column_id === $done_column_id && $new_column_id !== $done_column_id) {
$update_data['date_closed'] = null;
}
// Обновляем в БД
Database::update($this->db_name, $update_data, [
'id' => $this->id
]);
return ['success' => true];
}
// Удаление задачи
public static function delete($id) {
// Проверка что задача существует
self::check_task($id);
// Удаляем папку с файлами задачи
FileUpload::deleteFolder('task', $id);
// Удаляем все комментарии задачи и их файлы
$comments = Database::select('comments', ['id'], ['id_task' => $id]);
if ($comments) {
foreach ($comments as $comment) {
FileUpload::deleteFolder('comment', $comment['id']);
}
Database::delete('comments', ['id_task' => $id]);
}
// Удаляем задачу из базы
Database::delete('cards_task', ['id' => $id]);
return ['success' => true];
}
// === МЕТОДЫ ДЛЯ РАБОТЫ С ФАЙЛАМИ ===
// Загрузка файла к задаче
public static function uploadFile($task_id, $file_base64, $file_name) {
// Проверка что задача существует
self::check_task($task_id);
return FileUpload::upload('task', $task_id, $file_base64, $file_name);
}
// Удаление файлов задачи
public static function deleteFile($task_id, $file_names) {
// Проверка что задача существует
self::check_task($task_id);
return FileUpload::delete('task', $task_id, $file_names);
}
// Изменение порядка и колонки задачи (с пересчётом order)
public static function updateOrder($id, $column_id, $to_index) {
// Проверка что задача существует
$task = self::check_task($id);
$old_column_id = (int)$task['column_id'];
$new_column_id = (int)$column_id;
$archive = (int)$task['archive'];
// Получаем id_ready (колонка "Готово") из проекта
$done_column_id = Project::getReadyColumnId($task['id_project']);
// Получаем все карточки целевой колонки с тем же статусом архивации (кроме перемещаемой)
$cards = Database::select('cards_task', ['id', 'order'], [
'column_id' => $column_id,
'archive' => $archive,
'id[!]' => $id,
'ORDER' => ['order' => 'ASC']
]) ?? [];
// Вставляем перемещаемую карточку в нужную позицию
array_splice($cards, $to_index, 0, [['id' => $id]]);
// Пересчитываем order для всех карточек
foreach ($cards as $index => $card) {
$update_data = [
'order' => $index,
'column_id' => $column_id
];
// Только для перемещаемой карточки обновляем date_closed
if ($card['id'] == $id && $done_column_id) {
// Перемещаем В колонку "Готово" — устанавливаем дату закрытия
if ($new_column_id === $done_column_id && $old_column_id !== $done_column_id) {
$update_data['date_closed'] = date('Y-m-d H:i:s');
}
// Перемещаем ИЗ колонки "Готово" — обнуляем дату
elseif ($old_column_id === $done_column_id && $new_column_id !== $done_column_id) {
$update_data['date_closed'] = null;
}
}
Database::update('cards_task', $update_data, [
'id' => $card['id']
]);
}
return ['success' => true];
}
// Получение всех задач проекта
// $id_project: ID проекта (обязательный)
// $archive: 0 = неархивные, 1 = архивные, null = все
public function getAll($id_project, $archive = 0) {
$where = [
'id_project' => $id_project
];
if ($archive !== null) {
$where['archive'] = $archive ? 1 : 0;
}
$tasks = Database::select($this->db_name, [
'id',
'id_project',
'id_department',
'id_label',
'id_account',
'order',
'column_id',
'date',
'date_create',
'date_closed',
'file_img',
'title',
'descript',
'descript_full',
'archive'
], $where);
// Получаем количество комментариев для всех задач одним запросом
$task_ids = array_column($tasks, 'id');
$comments_counts = [];
if (!empty($task_ids)) {
$counts = Database::query(
"SELECT id_task, COUNT(*) as cnt FROM comments WHERE id_task IN (" . implode(',', $task_ids) . ") GROUP BY id_task"
)->fetchAll(\PDO::FETCH_ASSOC);
foreach ($counts as $row) {
$comments_counts[$row['id_task']] = (int)$row['cnt'];
}
}
// Декодируем JSON и получаем avatar_url из accounts
return array_map(function($task) use ($comments_counts) {
$task['file_img'] = $task['file_img'] ? json_decode($task['file_img'], true) : [];
// Получаем avatar_url из accounts по id_account
if ($task['id_account']) {
$account = Database::get('accounts', ['avatar_url'], ['id' => $task['id_account']]);
$task['avatar_img'] = $account['avatar_url'] ?? null;
} else {
$task['avatar_img'] = null;
}
// Количество комментариев
$task['comments_count'] = $comments_counts[$task['id']] ?? 0;
return $task;
}, $tasks);
}
// Получение колонок проекта
public function getColumns($id_project) {
return Database::select('columns', [
'id',
'name_columns',
'color',
'id_order'
], [
'id_project' => $id_project,
'ORDER' => ['id_order' => 'ASC']
]);
}
// Получение отделов проекта
public function getDepartments($id_project) {
return Database::select('departments', [
'id',
'name_departments',
'color'
], [
'id_project' => $id_project
]);
}
// Получение всех меток
public function getLabels() {
return Database::select('labels', [
'id',
'name_labels',
'icon',
'color'
]);
}
// Проверка и получение задачи (при ошибке — сразу ответ и exit)
public static function check_task($task_id) {
$task = Database::get('cards_task', '*', ['id' => $task_id]);
if (!$task_id || !$task) {
RestApi::response(['success' => false, 'errors' => ['task' => 'Задача не найдена']], 400);
}
return $task;
}
// Установка статуса архивации задачи (только для задач в колонке "Готово")
public static function setArchive($id, $archive = 1) {
// Проверка что задача существует
$task = self::check_task($id);
// Получаем id_ready (колонка "Готово") из проекта
$done_column_id = Project::getReadyColumnId($task['id_project']);
// Архивировать можно только задачи в колонке "Готово"
if ($archive && $done_column_id && (int)$task['column_id'] !== $done_column_id) {
RestApi::response([
'success' => false,
'errors' => ['column' => 'Архивировать можно только задачи из колонки "Готово"']
], 400);
}
// Данные для обновления
$update_data = [
'archive' => $archive ? 1 : 0
];
// При разархивировании — возвращаем в колонку "Готово"
if (!$archive && $done_column_id) {
$update_data['column_id'] = $done_column_id;
}
// Обновляем в БД
Database::update('cards_task', $update_data, [
'id' => $id
]);
return ['success' => true, 'archive' => $archive ? 1 : 0];
}
}
?>