url = rtrim($url, '/'); $this->appToken = $appToken; $this->username = $username; $this->password = $password; } private function log(string $message) { error_log('[GLPI API] ' . $message); } public function getSessionToken(): ?string { return $this->sessionToken; } public function initSession(): bool { $endpoint = $this->url . '/initSession'; $ch = curl_init($endpoint); $auth = base64_encode($this->username . ':' . $this->password); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Authorization: Basic ' . $auth, 'Content-Type: application/json', ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $data = json_decode($response, true); $this->sessionToken = $data['session_token'] ?? null; return true; } return false; } public function getItems(string $itemType, array $params = []): array { if (!$this->sessionToken && !$this->initSession()) return []; $endpoint = $this->url . '/' . $itemType; if (!empty($params)) { $endpoint .= '?' . http_build_query($params); } $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true) ?? []; } $this->log("getItems($itemType) HTTP $httpCode"); return []; } public function getItem(string $itemType, int $id): ?array { if (!$this->sessionToken && !$this->initSession()) return null; $endpoint = $this->url . '/' . $itemType . '/' . $id; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true); } $this->log("getItem($itemType/$id) HTTP $httpCode"); return null; } public function createTicket(array $ticketData): ?array { if (!$this->sessionToken && !$this->initSession()) return null; $endpoint = $this->url . '/Ticket'; $ch = curl_init($endpoint); $payload = json_encode(['input' => $ticketData]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, 'Content-Type: application/json', ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 201) { return json_decode($response, true); } $this->log("createTicket HTTP $httpCode: " . substr($response, 0, 500)); return null; } public function getTicket(int $id): ?array { if (!$this->sessionToken && !$this->initSession()) return null; $endpoint = $this->url . '/Ticket/' . $id . '?expand_dropdowns=true'; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true); } $this->log("getTicket($id) HTTP $httpCode"); return null; } public function getTicketFollowups(int $ticketId): array { if (!$this->sessionToken && !$this->initSession()) return []; $endpoint = $this->url . '/Ticket/' . $ticketId . '/ITILFollowup?expand_dropdowns=true&range=0-100'; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true) ?? []; } $this->log("getTicketFollowups($ticketId) HTTP $httpCode"); return []; } public function getKnowbaseItems(int $start = 0, int $limit = 100): array { if (!$this->sessionToken && !$this->initSession()) return []; $params = ['range' => $start . '-' . ($start + $limit - 1), 'expand_dropdowns' => 'true']; $endpoint = $this->url . '/KnowbaseItem?' . http_build_query($params); $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true) ?? []; } return []; } public function getKnowbaseItem(int $id): ?array { if (!$this->sessionToken && !$this->initSession()) return null; $endpoint = $this->url . '/KnowbaseItem/' . $id . '?expand_dropdowns=true'; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { return json_decode($response, true); } return null; } public function searchKnowbaseItems(string $query, int $limit = 30): array { if (!$this->sessionToken && !$this->initSession()) return []; $endpoint = $this->url . '/search/KnowbaseItem'; $criteria = [ 'criteria' => [ ['link' => 'OR', 'field' => 1, 'searchtype' => 'contains', 'value' => $query], ['link' => 'OR', 'field' => 2, 'searchtype' => 'contains', 'value' => $query] ], 'range' => "0-$limit", 'sort' => 2, 'order' => 'DESC' ]; $payload = json_encode($criteria); $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, 'Content-Type: application/json', ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $data = json_decode($response, true); return $data['data'] ?? []; } $this->log("searchKnowbaseItems HTTP $httpCode"); return []; } public function searchKnowbaseItemsSince(string $since, int $limit = 1000): array { if (!$this->sessionToken && !$this->initSession()) return []; $endpoint = $this->url . '/search/KnowbaseItem'; $criteria = [ 'criteria' => [ ['field' => 15, 'searchtype' => 'morethan', 'value' => $since] // 15 = date_mod ], 'range' => "0-$limit", 'sort' => 15, 'order' => 'ASC' ]; $payload = json_encode($criteria); $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, 'Content-Type: application/json', ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $data = json_decode($response, true); return $data['data'] ?? []; } $this->log("searchKnowbaseItemsSince HTTP $httpCode"); return []; } /** * Получение документов, привязанных к статье базы знаний (улучшенная версия) */ public function getKnowbaseItemDocuments(int $kbItemId): array { if (!$this->sessionToken && !$this->initSession()) return []; $documents = []; // Способ 1: через прямой фильтр Document $params = [ 'filter[itemtype]' => 'KnowbaseItem', 'filter[items_id]' => $kbItemId, 'range' => '0-100' ]; $endpoint = $this->url . '/Document?' . http_build_query($params); $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $docs = json_decode($response, true); if (is_array($docs) && count($docs) > 0) { $this->log("Got " . count($docs) . " documents for KB $kbItemId via Document filter"); return $docs; } } // Способ 2: через подресурс KnowbaseItem/{id}/Document $endpoint = $this->url . '/KnowbaseItem/' . $kbItemId . '/Document?expand_dropdowns=true&range=0-100'; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $docs = json_decode($response, true); if (is_array($docs) && count($docs) > 0) { $this->log("Got " . count($docs) . " documents for KB $kbItemId via subresource"); return $docs; } } // Способ 3: через Document_Item (если GLPI версии 10+) $endpoint = $this->url . '/Document_Item?filter[itemtype]=KnowbaseItem&filter[items_id]=' . $kbItemId; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode === 200) { $links = json_decode($response, true); if (is_array($links) && count($links) > 0) { // Извлекаем ID документов $docIds = array_column($links, 'documents_id'); if (!empty($docIds)) { $ids = implode(',', $docIds); $endpoint2 = $this->url . '/Document?filter[id]=' . $ids . '&range=0-100'; $ch2 = curl_init($endpoint2); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch2, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $resp2 = curl_exec($ch2); if (curl_getinfo($ch2, CURLINFO_HTTP_CODE) === 200) { $docs = json_decode($resp2, true); $this->log("Got " . count($docs) . " documents for KB $kbItemId via Document_Item"); return $docs; } } } } $this->log("No documents found for KB $kbItemId (tried all methods)"); return []; } public function getDocumentContent(int $documentId): ?string { if (!$this->sessionToken && !$this->initSession()) return null; $endpoint = $this->url . '/Document/' . $documentId . '?download=true'; $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'App-Token: ' . $this->appToken, 'Session-Token: ' . $this->sessionToken, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode !== 200 || $response === false) { $this->log("getDocumentContent($documentId) failed HTTP $httpCode"); return null; } $json = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { return $response; } if (isset($json['content'])) { $data = base64_decode($json['content'], true); if ($data !== false) { return $data; } return $json['content']; } $this->log("getDocumentContent($documentId) returned unexpected JSON"); return null; } /** * Получить имя компьютера по ID */ public function getComputerName(int $computerId): string { $computer = $this->getItem('Computer', $computerId); if ($computer) { $name = $computer['name'] ?? ''; $serial = $computer['serial'] ?? $computer['otherserial'] ?? ''; if ($serial) { return "$name (Инв.№ $serial)"; } return $name; } return "Компьютер #$computerId"; } /** * Получить полное имя категории ITIL по ID */ public function getCategoryName(int $categoryId): string { $category = $this->getItem('ITILCategory', $categoryId); if ($category) { return $category['completename'] ?? $category['name'] ?? "Категория #$categoryId"; } return "Категория #$categoryId"; } }