GeoNetAgent, LDAPWeb, server-audit, server-connection
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

182 lines
5.6 KiB

<?php
namespace App\Services;
use App\Models\Feedback;
use App\Support\ApiActor;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class ZammadTicketService
{
public function isConfigured(): bool
{
if (! config('zammad.enabled')) {
return false;
}
return config('zammad.base_url') !== ''
&& config('zammad.api_token') !== '';
}
/**
* @return array{zammad_ticket_id: int, zammad_ticket_number: string, zammad_synced_at: \Illuminate\Support\Carbon}|null
*/
public function createTicketFromFeedback(Feedback $feedback, ?ApiActor $actor = null): ?array
{
if (! $this->isConfigured()) {
$this->markSyncError($feedback, 'Zammad integration is not configured.');
return null;
}
$customerEmail = $this->resolveCustomerEmail($feedback, $actor);
if ($customerEmail === null) {
$this->markSyncError($feedback, 'No valid customer email for Zammad ticket.');
return null;
}
$payload = [
'title' => $this->buildTitle($feedback),
'group' => config('zammad.default_group'),
'customer_id' => 'guess:'.$customerEmail,
'article' => [
'subject' => $this->buildTitle($feedback),
'body' => $this->buildArticleBody($feedback),
'type' => 'web',
'internal' => false,
],
];
try {
$response = Http::timeout((int) config('zammad.timeout', 30))
->acceptJson()
->withHeaders([
'Authorization' => 'Token token='.config('zammad.api_token'),
])
->post(config('zammad.base_url').'/api/v1/tickets', $payload);
} catch (\Throwable $e) {
$this->markSyncError($feedback, 'Zammad request failed: '.$e->getMessage());
Log::warning('Zammad ticket create failed', [
'feedback_id' => $feedback->id,
'error' => $e->getMessage(),
]);
return null;
}
if (! $response->successful()) {
$message = data_get($response->json(), 'error_human')
?? data_get($response->json(), 'error')
?? 'HTTP '.$response->status();
$this->markSyncError($feedback, (string) $message);
Log::warning('Zammad ticket create rejected', [
'feedback_id' => $feedback->id,
'status' => $response->status(),
'body' => $response->json(),
]);
return null;
}
$ticketId = data_get($response->json(), 'id');
$ticketNumber = data_get($response->json(), 'number');
if (! is_numeric($ticketId)) {
$this->markSyncError($feedback, 'Zammad response missing ticket id.');
return null;
}
$syncedAt = now();
$feedback->forceFill([
'zammad_ticket_id' => (int) $ticketId,
'zammad_ticket_number' => is_scalar($ticketNumber) ? (string) $ticketNumber : null,
'zammad_synced_at' => $syncedAt,
'zammad_sync_error' => null,
])->save();
return [
'zammad_ticket_id' => (int) $ticketId,
'zammad_ticket_number' => is_scalar($ticketNumber) ? (string) $ticketNumber : (string) $ticketId,
'zammad_synced_at' => $syncedAt,
];
}
public function ticketUrl(Feedback $feedback): ?string
{
if ($feedback->zammad_ticket_id === null) {
return null;
}
return config('zammad.base_url').'/#ticket/zoom/'.$feedback->zammad_ticket_id;
}
private function resolveCustomerEmail(Feedback $feedback, ?ApiActor $actor): ?string
{
foreach ([$feedback->reporter, $actor?->identifier] as $candidate) {
if (is_string($candidate) && filter_var($candidate, FILTER_VALIDATE_EMAIL)) {
return Str::lower($candidate);
}
}
$fallback = (string) config('zammad.fallback_customer');
if ($fallback !== '' && filter_var($fallback, FILTER_VALIDATE_EMAIL)) {
return Str::lower($fallback);
}
return null;
}
private function buildTitle(Feedback $feedback): string
{
$typeLabel = match ($feedback->type) {
'bug' => 'Bug',
'suggestion' => 'Saran',
default => 'Feedback',
};
return sprintf('[%s] %s', $typeLabel, $feedback->title);
}
private function buildArticleBody(Feedback $feedback): string
{
$lines = [
$feedback->message,
'',
'---',
'Feedback #'.$feedback->id,
'Tipe: '.$feedback->type,
];
if ($feedback->reporter) {
$lines[] = 'Pelapor: '.$feedback->reporter;
}
if ($feedback->reporter_name) {
$lines[] = 'Nama: '.$feedback->reporter_name;
}
if ($feedback->page) {
$lines[] = 'Halaman: '.$feedback->page;
}
if ($feedback->url) {
$lines[] = 'URL: '.$feedback->url;
}
if ($feedback->user_agent) {
$lines[] = 'User-Agent: '.$feedback->user_agent;
}
if ($feedback->reported_at) {
$lines[] = 'Dilaporkan: '.$feedback->reported_at->toIso8601String();
}
return implode("\n", $lines);
}
private function markSyncError(Feedback $feedback, string $message): void
{
$feedback->forceFill([
'zammad_sync_error' => Str::limit($message, 1000),
])->save();
}
}