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

81
backend/api/comment.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
$comment = new Comment();
// Получаем ID текущего пользователя из сессии
$current_user_id = RestApi::getCurrentUserId();
// Создание комментария
if ($action === 'create') {
$comment->id_task = $data['id_task'] ?? null;
$comment->id_accounts = $current_user_id;
$comment->id_answer = $data['id_answer'] ?? null; // Ответ на комментарий
$comment->text = $data['text'] ?? '';
$result = $comment->create();
RestApi::response($result);
}
// Обновление комментария
if ($action === 'update') {
$comment->id = $data['id'] ?? null;
$comment->id_accounts = $current_user_id;
$comment->text = $data['text'] ?? '';
$result = $comment->update();
RestApi::response($result);
}
// Удаление комментария
if ($action === 'delete') {
$id = $data['id'] ?? null;
$result = Comment::delete($id, $current_user_id);
RestApi::response($result);
}
// Загрузка файла к комментарию (только автор)
if ($action === 'upload_image') {
$comment_id = $data['comment_id'] ?? null;
$file_base64 = $data['file_data'] ?? null;
$file_name = $data['file_name'] ?? null;
$result = Comment::uploadFile($comment_id, $file_base64, $file_name, $current_user_id);
RestApi::response($result);
}
// Удаление файлов комментария (автор или админ проекта)
if ($action === 'delete_image') {
$comment_id = $data['comment_id'] ?? null;
$file_names = $data['file_names'] ?? $data['file_name'] ?? null;
$result = Comment::deleteFile($comment_id, $file_names, $current_user_id);
RestApi::response($result);
}
// Метод не указан
if (!$action) {
RestApi::response(['success' => false, 'error' => 'Укажите метод'], 400);
}
}
if ($method === 'GET') {
// Получение комментариев задачи
// ?id_task=X (обязательный)
$id_task = $_GET['id_task'] ?? null;
if (!$id_task) {
RestApi::response(['success' => false, 'errors' => ['id_task' => 'Задача не указана']], 400);
}
$comment = new Comment();
$comments = $comment->getByTask($id_task);
RestApi::response(['success' => true, 'data' => $comments]);
}
?>

25
backend/api/server.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
// Получение настроек сервера (публичный action)
if ($action === 'get_settings') {
$timezone = date_default_timezone_get();
$offset = date('P'); // +03:00 формат
RestApi::response([
'success' => true,
'data' => [
'timezone' => $timezone, // Europe/Moscow
'timezone_offset' => $offset, // +03:00
'server_time' => date('c') // 2026-01-15T18:30:00+03:00
]
]);
}
}
?>

View File

@@ -13,7 +13,7 @@ if ($method === 'POST') {
$file_base64 = $data['file_data'] ?? null;
$file_name = $data['file_name'] ?? null;
$result = TaskImage::upload($task_id, $file_base64, $file_name);
$result = Task::uploadFile($task_id, $file_base64, $file_name);
RestApi::response($result);
}
@@ -22,7 +22,7 @@ if ($method === 'POST') {
$task_id = $data['task_id'] ?? null;
$file_names = $data['file_names'] ?? $data['file_name'] ?? null;
$result = TaskImage::delete($task_id, $file_names);
$result = Task::deleteFile($task_id, $file_names);
RestApi::response($result);
}

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;
}
}
?>

View File

@@ -0,0 +1,223 @@
<?php
class FileUpload {
protected static $base_path = __DIR__ . '/../../../public/';
protected static $base_url = '/public/';
// === Маппинг сущностей (все параметры обязательны) ===
protected static $entities = [
'task' => [
'table' => 'cards_task',
'folder' => 'task',
'field' => 'file_img',
'allowed_ext' => ['png', 'jpg', 'jpeg', 'zip', 'rar'],
'archive_ext' => ['zip', 'rar'],
'max_size' => 10 * 1024 * 1024 // 10 MB
],
'comment' => [
'table' => 'comments',
'folder' => 'comment',
'field' => 'file_img',
'allowed_ext' => ['png', 'jpg', 'jpeg', 'zip', 'rar'],
'archive_ext' => ['zip', 'rar'],
'max_size' => 10 * 1024 * 1024 // 10 MB
]
];
// Получение конфигурации сущности
protected static function getConfig($entity_type) {
if (!isset(self::$entities[$entity_type])) {
return null;
}
return self::$entities[$entity_type];
}
// Получение данных сущности из БД
protected static function getEntityData($config, $entity_id) {
return Database::get($config['table'], '*', ['id' => $entity_id]);
}
// Обновление файлов сущности в БД
protected static function updateEntityFiles($config, $entity_id, $files) {
Database::update($config['table'], [
$config['field'] => json_encode($files, JSON_UNESCAPED_UNICODE)
], [
'id' => $entity_id
]);
}
// Генерация уникального имени файла
protected static function getUniqueName($upload_dir, $file_name) {
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$base_name = pathinfo($file_name, PATHINFO_FILENAME);
$final_name = $file_name;
$counter = 1;
while (file_exists($upload_dir . '/' . $final_name)) {
$final_name = $base_name . '_' . $counter . '.' . $ext;
$counter++;
}
return $final_name;
}
// Форматирование размера файла для ошибки
protected static function formatSize($bytes) {
if ($bytes >= 1048576) {
return round($bytes / 1048576, 1) . ' МБ';
}
return round($bytes / 1024, 1) . ' КБ';
}
// === ЗАГРУЗКА ФАЙЛА ===
public static function upload($entity_type, $entity_id, $file_base64, $file_name) {
// Получаем конфигурацию
$config = self::getConfig($entity_type);
if (!$config) {
return ['success' => false, 'errors' => ['entity' => 'Неизвестный тип сущности']];
}
// Проверяем что сущность существует
$entity = self::getEntityData($config, $entity_id);
if (!$entity) {
return ['success' => false, 'errors' => ['entity' => 'Сущность не найдена']];
}
// Декодируем base64
$file_data = base64_decode(preg_replace('/^data:[^;]+;base64,/', '', $file_base64));
if (!$file_data) {
return ['success' => false, 'errors' => ['file' => 'Ошибка декодирования файла']];
}
// Проверка расширения
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
if (!in_array($ext, $config['allowed_ext'])) {
$allowed = strtoupper(implode(', ', $config['allowed_ext']));
return ['success' => false, 'errors' => ['file' => "Разрешены только: $allowed"]];
}
// Проверка размера
$file_size = strlen($file_data);
if ($file_size > $config['max_size']) {
$max_formatted = self::formatSize($config['max_size']);
return ['success' => false, 'errors' => ['file' => "Файл слишком большой. Максимум $max_formatted"]];
}
// Путь к папке
$upload_dir = self::$base_path . $config['folder'] . '/' . $entity_id;
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// Уникальное имя
$final_name = self::getUniqueName($upload_dir, $file_name);
// Сохранение файла
$file_path = $upload_dir . '/' . $final_name;
if (!file_put_contents($file_path, $file_data)) {
return ['success' => false, 'errors' => ['file' => 'Ошибка сохранения файла']];
}
// Формируем URL
$file_url = self::$base_url . $config['folder'] . '/' . $entity_id . '/' . $final_name;
// Обновляем file_img в базе
$current_files = $entity[$config['field']] ? json_decode($entity[$config['field']], true) : [];
$current_files[] = [
'url' => $file_url,
'name' => $final_name,
'size' => $file_size
];
self::updateEntityFiles($config, $entity_id, $current_files);
return [
'success' => true,
'file' => [
'url' => $file_url,
'name' => $final_name,
'size' => $file_size
]
];
}
// === УДАЛЕНИЕ ФАЙЛОВ ===
public static function delete($entity_type, $entity_id, $file_names) {
// Получаем конфигурацию
$config = self::getConfig($entity_type);
if (!$config) {
return ['success' => false, 'errors' => ['entity' => 'Неизвестный тип сущности']];
}
// Проверяем что сущность существует
$entity = self::getEntityData($config, $entity_id);
if (!$entity) {
return ['success' => false, 'errors' => ['entity' => 'Сущность не найдена']];
}
// Приводим к массиву если передана строка
if (!is_array($file_names)) {
$file_names = [$file_names];
}
// Получаем текущие файлы
$current_files = $entity[$config['field']] ? json_decode($entity[$config['field']], true) : [];
$upload_dir = self::$base_path . $config['folder'] . '/' . $entity_id;
$deleted = [];
// Удаляем каждый файл
foreach ($file_names as $file_name) {
foreach ($current_files as $index => $file) {
if ($file['name'] === $file_name) {
// Удаляем файл с диска
$file_path = $upload_dir . '/' . $file_name;
if (file_exists($file_path)) {
unlink($file_path);
}
// Удаляем из массива
array_splice($current_files, $index, 1);
$deleted[] = $file_name;
break;
}
}
}
// Обновляем в базе
self::updateEntityFiles($config, $entity_id, $current_files);
// Удаляем папку если она пустая
if (is_dir($upload_dir) && count(scandir($upload_dir)) === 2) {
rmdir($upload_dir);
}
return ['success' => true, 'deleted' => $deleted];
}
// === УДАЛЕНИЕ ПАПКИ СУЩНОСТИ (при удалении самой сущности) ===
public static function deleteFolder($entity_type, $entity_id) {
// Получаем конфигурацию
$config = self::getConfig($entity_type);
if (!$config) {
return false;
}
$upload_dir = self::$base_path . $config['folder'] . '/' . $entity_id;
if (is_dir($upload_dir)) {
// Удаляем все файлы в папке
$files = glob($upload_dir . '/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
// Удаляем папку
rmdir($upload_dir);
}
return true;
}
}
?>

View File

@@ -6,24 +6,56 @@ class Project extends BaseEntity {
// Получение всех проектов
public function getAll() {
return Database::select($this->db_name, [
$current_user_id = RestApi::getCurrentUserId();
$projects = Database::select($this->db_name, [
'id',
'id_order',
'name',
'id_ready'
'id_ready',
'id_admin'
], [
'ORDER' => ['id_order' => 'ASC']
]);
// Обрабатываем id_admin для каждого проекта
return array_map(function($project) use ($current_user_id) {
$admins = $project['id_admin'] ? json_decode($project['id_admin'], true) : [];
if ($current_user_id && in_array((int)$current_user_id, $admins, true)) {
$project['id_admin'] = true;
} else {
unset($project['id_admin']);
}
return $project;
}, $projects);
}
// Получение одного проекта
public static function get($id) {
return Database::get('project', [
// $current_user_id — ID текущего пользователя для проверки админства
public static function get($id, $current_user_id = null) {
$project = Database::get('project', [
'id',
'id_order',
'name',
'id_ready'
'id_ready',
'id_admin'
], ['id' => $id]);
if ($project) {
$admins = $project['id_admin'] ? json_decode($project['id_admin'], true) : [];
// Если передан user_id — проверяем админство
if ($current_user_id && in_array((int)$current_user_id, $admins, true)) {
$project['id_admin'] = true;
} else {
// Не админ — убираем поле
unset($project['id_admin']);
}
}
return $project;
}
// Получение id_ready (ID колонки "Готово") по ID проекта
@@ -58,7 +90,10 @@ class Project extends BaseEntity {
// Получение всех данных проекта (проект + колонки + отделы + метки)
public static function getProjectData($project_id) {
$project = self::get($project_id);
// Получаем ID текущего пользователя для проверки админства
$current_user_id = RestApi::getCurrentUserId();
$project = self::get($project_id, $current_user_id);
if (!$project) {
return null;
}
@@ -78,6 +113,17 @@ class Project extends BaseEntity {
'labels' => $labels
];
}
// Проверка является ли пользователь админом проекта
public static function isAdmin($project_id, $user_id): bool {
$project = Database::get('project', ['id_admin'], ['id' => $project_id]);
if (!$project || !$project['id_admin']) {
return false;
}
$admins = json_decode($project['id_admin'], true) ?: [];
return in_array((int)$user_id, $admins, true);
}
}
?>

View File

@@ -84,7 +84,7 @@ class Task extends BaseEntity {
$uploaded_files = [];
if (!empty($files)) {
foreach ($files as $file) {
$result = TaskImage::upload($this->id, $file['data'], $file['name']);
$result = FileUpload::upload('task', $this->id, $file['data'], $file['name']);
if ($result['success']) {
$uploaded_files[] = $result['file'];
}
@@ -154,24 +154,40 @@ class Task extends BaseEntity {
// Проверка что задача существует
self::check_task($id);
// Удаляем папку с файлами если есть
$upload_dir = __DIR__ . '/../../../public/task/' . $id;
if (is_dir($upload_dir)) {
$files = glob($upload_dir . '/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
// Удаляем папку с файлами задачи
FileUpload::deleteFolder('task', $id);
// Удаляем все комментарии задачи и их файлы
$comments = Database::select('comments', ['id'], ['id_task' => $id]);
if ($comments) {
foreach ($comments as $comment) {
FileUpload::deleteFolder('comment', $comment['id']);
}
rmdir($upload_dir);
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) {
@@ -251,8 +267,20 @@ class Task extends BaseEntity {
'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) {
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
@@ -263,6 +291,9 @@ class Task extends BaseEntity {
$task['avatar_img'] = null;
}
// Количество комментариев
$task['comments_count'] = $comments_counts[$task['id']] ?? 0;
return $task;
}, $tasks);
}

View File

@@ -1,156 +0,0 @@
<?php
class TaskImage {
protected static $db_name = 'cards_task';
protected static $upload_path = '/public/task/';
// Валидация всех данных для загрузки
protected static function validate($task_id, $file_base64, $file_name) {
// Проверка и получение задачи
$task = Task::check_task($task_id);
// Декодируем base64 (убираем любой data: префикс)
$file_data = base64_decode(preg_replace('/^data:[^;]+;base64,/', '', $file_base64));
if (!$file_data) {
return ['success' => false, 'errors' => ['file' => 'Ошибка декодирования файла']];
}
// Проверка расширения
$allowed_ext = ['png', 'jpg', 'jpeg', 'zip', 'rar'];
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext)) {
return ['success' => false, 'errors' => ['file' => 'Разрешены только PNG, JPG, JPEG, ZIP, RAR']];
}
// Проверка размера (10 МБ)
$max_size = 10 * 1024 * 1024;
if (strlen($file_data) > $max_size) {
return ['success' => false, 'errors' => ['file' => 'Файл слишком большой. Максимум 10 МБ']];
}
// Всё ок — возвращаем данные
return [
'task' => $task,
'file_data' => $file_data,
'is_archive' => in_array($ext, ['zip', 'rar'])
];
}
// Генерация уникального имени файла
protected static function getUniqueName($upload_dir, $file_name) {
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$base_name = pathinfo($file_name, PATHINFO_FILENAME);
$final_name = $file_name;
$counter = 1;
while (file_exists($upload_dir . '/' . $final_name)) {
$final_name = $base_name . '_' . $counter . '.' . $ext;
$counter++;
}
return $final_name;
}
// Загрузка изображения
public static function upload($task_id, $file_base64, $file_name) {
// Валидация
$validation = self::validate($task_id, $file_base64, $file_name);
if (isset($validation['success'])) return $validation;
$task = $validation['task'];
$file_data = $validation['file_data'];
// Путь к папке
$upload_dir = __DIR__ . '/../../../public/task/' . $task_id;
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// Уникальное имя
$final_name = self::getUniqueName($upload_dir, $file_name);
// Сохранение файла
$file_path = $upload_dir . '/' . $final_name;
if (!file_put_contents($file_path, $file_data)) {
return ['success' => false, 'errors' => ['file' => 'Ошибка сохранения файла']];
}
// Формируем URL
$file_url = self::$upload_path . $task_id . '/' . $final_name;
$file_size = strlen($file_data);
// Обновляем file_img в базе
$current_files = $task['file_img'] ? json_decode($task['file_img'], true) : [];
$current_files[] = [
'url' => $file_url,
'name' => $final_name,
'size' => $file_size
];
Database::update(self::$db_name, [
'file_img' => json_encode($current_files, JSON_UNESCAPED_UNICODE)
], [
'id' => $task_id
]);
return [
'success' => true,
'file' => [
'url' => $file_url,
'name' => $final_name,
'size' => $file_size
]
];
}
// Удаление изображений (принимает строку или массив имён файлов)
public static function delete($task_id, $file_names) {
// Проверка и получение задачи
$task = Task::check_task($task_id);
// Приводим к массиву если передана строка
if (!is_array($file_names)) {
$file_names = [$file_names];
}
// Получаем текущие файлы
$current_files = $task['file_img'] ? json_decode($task['file_img'], true) : [];
$upload_dir = __DIR__ . '/../../../public/task/' . $task_id;
$deleted = [];
// Удаляем каждый файл
foreach ($file_names as $file_name) {
// Ищем файл в массиве
foreach ($current_files as $index => $file) {
if ($file['name'] === $file_name) {
// Удаляем файл с диска
$file_path = $upload_dir . '/' . $file_name;
if (file_exists($file_path)) {
unlink($file_path);
}
// Удаляем из массива
array_splice($current_files, $index, 1);
$deleted[] = $file_name;
break;
}
}
}
// Обновляем в базе
Database::update(self::$db_name, [
'file_img' => json_encode($current_files, JSON_UNESCAPED_UNICODE)
], [
'id' => $task_id
]);
// Удаляем папку если она пустая
if (is_dir($upload_dir) && count(scandir($upload_dir)) === 2) {
rmdir($upload_dir);
}
return ['success' => true, 'deleted' => $deleted];
}
}
?>

View File

@@ -1,5 +1,8 @@
<?php
// Часовой пояс сервера
date_default_timezone_set('Europe/Moscow');
// Подключение классов базы данных
require_once __DIR__ . '/class/database/class_Medoo.php';
require_once __DIR__ . '/class/database/class_Database.php';
@@ -11,10 +14,11 @@
// подключение классов REST API и Entity
require_once __DIR__ . '/restAPI/class_restApi.php';
require_once __DIR__ . '/class/enity/class_base.php';
require_once __DIR__ . '/class/enity/class_fileUpload.php';
require_once __DIR__ . '/class/enity/class_user.php';
require_once __DIR__ . '/class/enity/class_project.php';
require_once __DIR__ . '/class/enity/class_task.php';
require_once __DIR__ . '/class/enity/class_taskImage.php';
require_once __DIR__ . '/class/enity/class_comment.php';
// Данные подключения к БД
define('DB_HOST', '192.168.1.9');
@@ -31,8 +35,10 @@
'/api/user' => __DIR__ . '/../api/user.php',
'/api/task' => __DIR__ . '/../api/task.php',
'/api/project' => __DIR__ . '/../api/project.php',
'/api/comment' => __DIR__ . '/../api/comment.php',
'/api/server' => __DIR__ . '/../api/server.php',
];
$publicActions = ['auth_login', 'check_session'];
$publicActions = ['auth_login', 'check_session', 'get_settings'];
?>

View File

@@ -18,6 +18,21 @@ class RestApi {
exit;
}
// Получить ID текущего авторизованного пользователя
public static function getCurrentUserId(): ?int {
$session = $_COOKIE['session'] ?? null;
if (!$session) {
return null;
}
$sessionData = Database::get('accounts_session', ['id_accounts'], [
'keycookies' => $session,
'data_closed[>]' => date('Y-m-d H:i:s')
]);
return $sessionData ? (int)$sessionData['id_accounts'] : null;
}
}
?>