#!/usr/bin/env php isConfigured()) { echo "ERROR: BC connection not configured.\n"; exit(1); } $baseUrl = $settings->getODataBaseUrl(); $companyUrl = $baseUrl . "/Company('" . rawurlencode($settings->getCompanyName()) . "')"; echo "=== BC OData Entity Explorer ===\n\n"; echo "Base URL: {$baseUrl}\n"; echo "Company: {$settings->getCompanyName()}\n\n"; // ── Helper: raw cURL request ── function bcRequest(string $url, HelpdeskSettingsGateway $settings): array { $ch = curl_init(); if ($ch === false) return ['error' => 'curl_init failed']; curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ['Accept: application/json', 'OData-Version: 4.0'], CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, ]); // Apply auth if ($settings->getAuthMode() === 'basic') { curl_setopt($ch, CURLOPT_USERPWD, ($settings->getBasicUser() ?? '') . ':' . ($settings->getBasicPassword() ?? '')); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } $body = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); unset($ch); if (!is_string($body)) { return ['http_code' => $httpCode, 'error' => $curlError ?: 'Empty response']; } if ($httpCode < 200 || $httpCode >= 300) { return ['http_code' => $httpCode, 'error' => mb_substr($body, 0, 300)]; } $decoded = json_decode($body, true); if (!is_array($decoded)) { return ['http_code' => $httpCode, 'error' => 'Invalid JSON']; } return ['http_code' => $httpCode, 'data' => $decoded]; } function printResult(string $label, string $url, array $result): void { echo "── {$label} ──\n"; echo " URL: {$url}\n"; echo " HTTP: " . ($result['http_code'] ?? '?') . "\n"; if (!empty($result['error'])) { echo " Error: " . $result['error'] . "\n\n"; return; } $data = $result['data'] ?? []; $values = $data['value'] ?? []; if (!is_array($values)) { echo " Response has no 'value' array.\n\n"; return; } echo " Count: " . count($values) . "\n"; if (count($values) > 0) { $first = $values[0]; echo " Fields: " . implode(', ', array_keys($first)) . "\n"; echo " 1st record:\n"; foreach ($first as $field => $value) { $display = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE); if (strlen($display) > 120) $display = mb_substr($display, 0, 117) . '...'; echo " {$field}: {$display}\n"; } } echo "\n"; } // ──────────────────────────────────────────── // Step 1: Fetch $metadata to find all entities // ──────────────────────────────────────────── echo str_repeat('═', 60) . "\n"; echo "Step 1: Scanning all entity sets from \$metadata...\n"; echo str_repeat('═', 60) . "\n\n"; $metadataUrl = $baseUrl . '/$metadata'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $metadataUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ['Accept: application/xml'], CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, ]); if ($settings->getAuthMode() === 'basic') { curl_setopt($ch, CURLOPT_USERPWD, ($settings->getBasicUser() ?? '') . ':' . ($settings->getBasicPassword() ?? '')); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } $metadataBody = curl_exec($ch); $metadataCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); unset($ch); if (!is_string($metadataBody) || $metadataCode !== 200) { echo "Could not fetch \$metadata (HTTP {$metadataCode})\n\n"; } else { // Parse XML for EntitySet names preg_match_all('/EntitySet\s+Name="([^"]+)"/', $metadataBody, $matches); $allEntities = $matches[1] ?? []; // Filter for interesting entities $ticketLogEntities = array_filter($allEntities, fn($e) => stripos($e, 'ticket') !== false || stripos($e, 'log') !== false || stripos($e, 'communication') !== false || stripos($e, 'chat') !== false || stripos($e, 'message') !== false || stripos($e, 'comment') !== false || stripos($e, 'note') !== false || stripos($e, 'task') !== false || stripos($e, 'time') !== false ); $contractEntities = array_filter($allEntities, fn($e) => stripos($e, 'contract') !== false || stripos($e, 'vertrag') !== false || stripos($e, 'agreement') !== false || stripos($e, 'service') !== false || stripos($e, 'subscription') !== false || stripos($e, 'wartung') !== false || stripos($e, 'support') !== false ); echo "Total entities found: " . count($allEntities) . "\n\n"; echo "🎫 Ticket/Log/Communication related:\n"; if (empty($ticketLogEntities)) { echo " (none found)\n"; } else { foreach ($ticketLogEntities as $e) { echo " - {$e}\n"; } } echo "\n📋 Contract/Service related:\n"; if (empty($contractEntities)) { echo " (none found)\n"; } else { foreach ($contractEntities as $e) { echo " - {$e}\n"; } } echo "\n"; } // ──────────────────────────────────────────── // Step 2: Probe ticket log entities // ──────────────────────────────────────────── echo str_repeat('═', 60) . "\n"; echo "Step 2: Probing ticket log/communication entities...\n"; echo str_repeat('═', 60) . "\n\n"; $ticketLogCandidates = [ 'PBI_FP_TicketLog', 'PBI_LV_SupportTicketLog', 'TicketsICI_Support_Time_Log_Subpage', 'TicketsICI_Support_Task_Subpage', ]; // Also add any entities from metadata scan if (!empty($ticketLogEntities)) { foreach ($ticketLogEntities as $e) { if (!in_array($e, $ticketLogCandidates, true)) { $ticketLogCandidates[] = $e; } } } foreach ($ticketLogCandidates as $entity) { $url = $companyUrl . '/' . $entity . '?$top=3'; $result = bcRequest($url, $settings); printResult($entity . ' (top 3)', $url, $result); // If we got results and have a ticket number, try filtering if ($ticketNo !== '' && !empty($result['data']['value'])) { $fields = array_keys($result['data']['value'][0]); // Try common filter fields $filterFields = array_intersect($fields, [ 'Ticket_No', 'No', 'Ticket_No_', 'TicketNo', 'Ticket_Number', 'Support_Ticket_No', 'Support_Ticket_No_', 'Document_No', 'Document_No_', ]); foreach ($filterFields as $filterField) { $filterUrl = $companyUrl . '/' . $entity . '?$filter=' . rawurlencode("{$filterField} eq '{$ticketNo}'") . '&$top=10'; $filterResult = bcRequest($filterUrl, $settings); printResult("{$entity} filtered by {$filterField}='{$ticketNo}'", $filterUrl, $filterResult); } } } // ──────────────────────────────────────────── // Step 3: Probe contract entities // ──────────────────────────────────────────── echo str_repeat('═', 60) . "\n"; echo "Step 3: Probing contract/service entities...\n"; echo str_repeat('═', 60) . "\n\n"; $contractCandidates = [ 'Service_Contract', 'Service_Contracts', 'ServiceContract', 'Service_Contract_List', 'Service_Contract_Card', 'Customer_Contract', 'PBI_FP_ServiceContract', ]; // Also add any entities from metadata scan if (!empty($contractEntities)) { foreach ($contractEntities as $e) { if (!in_array($e, $contractCandidates, true)) { $contractCandidates[] = $e; } } } foreach ($contractCandidates as $entity) { $url = $companyUrl . '/' . $entity . '?$top=3'; $result = bcRequest($url, $settings); printResult($entity . ' (top 3)', $url, $result); // If we got results and have a customer name, try filtering if ($customerName !== '' && !empty($result['data']['value'])) { $fields = array_keys($result['data']['value'][0]); $filterFields = array_intersect($fields, [ 'Customer_No', 'Customer_No_', 'CustomerNo', 'Customer_Name', 'Name', 'Bill_to_Customer_No_', 'Company_Name', 'Company_Contact_Name', ]); $escapedName = str_replace("'", "''", $customerName); foreach ($filterFields as $filterField) { $filterUrl = $companyUrl . '/' . $entity . '?$filter=' . rawurlencode("{$filterField} eq '{$escapedName}'") . '&$top=10'; $filterResult = bcRequest($filterUrl, $settings); printResult("{$entity} filtered by {$filterField}='{$customerName}'", $filterUrl, $filterResult); } } } echo str_repeat('═', 60) . "\n"; echo "Done.\n";