prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(); return $user ?: null; } function redirect(string $url): void { header("Location: $url"); exit; } function getUserForms(PDO $pdo, int $userId): array { $stmt = $pdo->prepare("SELECT * FROM forms WHERE user_id = ? ORDER BY created_at DESC"); $stmt->execute([$userId]); return $stmt->fetchAll(); } function createLocalForm(PDO $pdo, ?int $userId, string $type, array $data, string $status = 'pending', ?string $priority = null, ?int $locationId = null): int { $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE); $stmt = $pdo->prepare("INSERT INTO forms (user_id, type, data, status, priority, location_id) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$userId, $type, $jsonData, $status, $priority, $locationId]); return (int)$pdo->lastInsertId(); } function uploadFile(array $file): string|false { if ($file['error'] !== UPLOAD_ERR_OK) return false; if ($file['size'] > MAX_FILE_SIZE) return false; $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']; if (!in_array($file['type'], $allowedTypes)) return false; $ext = pathinfo($file['name'], PATHINFO_EXTENSION); $basename = bin2hex(random_bytes(16)) . '.' . $ext; $destination = UPLOAD_DIR . $basename; if (move_uploaded_file($file['tmp_name'], $destination)) { return 'uploads/' . $basename; } return false; } function syncFormWithGlpi(PDO $pdo, array &$form, GlpiApi $glpi): bool { $data = json_decode($form['data'], true); $glpiId = $data['glpi_ticket_id'] ?? null; if (!$glpiId) return true; $ticket = $glpi->getTicket($glpiId); if (!$ticket) { $stmt = $pdo->prepare("DELETE FROM forms WHERE id = ?"); $stmt->execute([$form['id']]); return false; } $data['glpi_status'] = $ticket['status'] ?? null; $data['glpi_priority'] = $ticket['priority'] ?? null; $data['glpi_name'] = $ticket['name'] ?? ''; $newData = json_encode($data, JSON_UNESCAPED_UNICODE); // ----- МАППИНГ СТАТУСОВ GLPI В ДОПУСТИМЫЕ ЗНАЧЕНИЯ ----- $statusGlpi = strtolower($ticket['status_name'] ?? $ticket['status'] ?? 'unknown'); $statusMap = [ 'new' => 'pending', 'incoming' => 'pending', 'waiting' => 'pending', 'processing' => 'processing', 'assigned' => 'processing', 'solved' => 'completed', 'closed' => 'completed', 'rejected' => 'rejected', 'pending' => 'pending' ]; $newStatus = $statusMap[$statusGlpi] ?? 'pending'; // Дополнительная проверка на допустимость $allowedStatuses = ['pending', 'processing', 'completed', 'rejected']; if (!in_array($newStatus, $allowedStatuses)) { $newStatus = 'pending'; } // ----- МАППИНГ ПРИОРИТЕТОВ (ЧИСЛО -> 'low','medium','high') ----- $priorityGlpi = $ticket['priority'] ?? 3; // число 1..5 $priorityMap = [ 1 => 'low', 2 => 'low', 3 => 'medium', 4 => 'high', 5 => 'high' ]; $newPriority = $priorityMap[$priorityGlpi] ?? 'medium'; // Обновляем запись в БД $stmt = $pdo->prepare("UPDATE forms SET data = ?, status = ?, priority = ? WHERE id = ?"); $stmt->execute([$newData, $newStatus, $newPriority, $form['id']]); $form['data'] = $newData; $form['status'] = $newStatus; $form['priority'] = $newPriority; return true; } /* ========== БАЗА ЗНАНИЙ (исправленная) ========== */ // Получение всех статей function getAllLocalKnowledgeArticles(PDO $pdo): array { return $pdo->query("SELECT * FROM knowledge_base ORDER BY updated_at DESC")->fetchAll(); } // Поиск с FULLTEXT или LIKE function searchLocalKnowledgeFulltext(PDO $pdo, string $query, int $limit = 30): array { if (strlen($query) < 3) { $stmt = $pdo->prepare("SELECT id, title, SUBSTRING(content, 1, 200) AS preview, updated_at FROM knowledge_base WHERE title LIKE :q1 OR content LIKE :q2 ORDER BY updated_at DESC LIMIT $limit"); $like = '%' . $query . '%'; $stmt->execute(['q1' => $like, 'q2' => $like]); return $stmt->fetchAll(); } $stmt = $pdo->prepare("SELECT id, title, SUBSTRING(content, 1, 200) AS preview, updated_at, MATCH(title, content) AGAINST(:query IN NATURAL LANGUAGE MODE) AS relevance FROM knowledge_base WHERE MATCH(title, content) AGAINST(:query IN NATURAL LANGUAGE MODE) ORDER BY relevance DESC LIMIT $limit"); $stmt->execute(['query' => $query]); return $stmt->fetchAll(); } // Инкрементальная синхронизация (с проверкой существования колонки) function syncKnowledgeBaseIncremental(PDO $pdo, GlpiApi $glpi, array &$log = []): void { $log[] = "=== Инкрементальная синхронизация ==="; // Проверяем наличие колонки glpi_updated_at try { $pdo->query("SELECT glpi_updated_at FROM knowledge_base LIMIT 1"); } catch (PDOException $e) { $log[] = "Ошибка: колонка glpi_updated_at отсутствует. Выполните ALTER TABLE knowledge_base ADD COLUMN glpi_updated_at DATETIME DEFAULT NULL;"; return; } $stmt = $pdo->query("SELECT MAX(glpi_updated_at) as last FROM knowledge_base"); $lastSync = $stmt->fetch()['last']; $since = $lastSync ? $lastSync : '1970-01-01 00:00:00'; $glpiItems = $glpi->searchKnowbaseItemsSince($since); $log[] = "Найдено изменённых статей в GLPI: " . count($glpiItems); foreach ($glpiItems as $glpiArticle) { $glpiId = (int)$glpiArticle['id']; $fullArticle = $glpi->getKnowbaseItem($glpiId); if (!$fullArticle) continue; $title = $fullArticle['name']; $content = $fullArticle['answer'] ?? ''; $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $content = strip_tags($content, '