# Example code

The code for support integrations can be found in:

```
app/Framework/Core/Support/TicketProviders
```

For each ticket provider, you have a php class, for example the FreeScoutTicketProvider.php. To get started with a new ticket provider, you can just copy one and remove the code.&#x20;

## Example (FreeScout)

```php
<?php

namespace App\Framework\Core\Support\TicketProviders;

use App\Enums\TicketDefaultStatuses;
use App\Framework\Core\Support\TicketProviders\Responses\TicketAnsweredResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketCreatedResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketDepartmentResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketMessageAttachmentResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketMessageResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketResponse;
use App\Framework\Core\Support\TicketProviders\Responses\TicketStatusResponse;
use App\Framework\Core\Utils\HtmlToText;
use App\Models\Customer;
use App\Models\Domain\Domain;
use App\Models\ProductHosting;
use App\Models\SalesChannel;
use App\Models\Support\Ticket;
use App\Models\Support\TicketAttachment;
use App\Models\Support\TicketDepartment;
use Cache;
use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class FreeScoutTicketProvider extends SupportTicketProviderAbstract implements SupportTicketProviderInterface {

	// Tickets, which where opened by the customer and need admin-response
	public mixed $openStatusId = "active";

	// Tickets, which where responded by the customer and need admin-response
	public mixed $answeredStatusId = "active";

	// Tickets, which where closed
	public mixed $closedStatusId = "closed";

	public function getStatuses(): Collection {
		return collect([
			new TicketStatusResponse("active", "Aktiv", "var(--bs-success)"),
			new TicketStatusResponse("pending", "In Bearbeitung", "var(--bs-warning)"),
			new TicketStatusResponse("closed", "Geschlossen", "var(--bs-danger)"),
			new TicketStatusResponse("spam", "Spam", "var(--bs-secondary)"),
		]);
	}

	public function getTickets(): Collection {
		$response = $this->makeRequest("conversations", "GET", [
			"sortField" => "updatedAt",
			"sortOrder" => "desc",
			"pageSize" => 150
		]);
		$tickets = [];

//		dd(count($response->_embedded->conversations));

		foreach ($response->_embedded->conversations as $conversation) {
			if ($conversation->customer === null) {
				continue;
			}

			$tickets[] = new TicketResponse(
				$conversation->id,
				$conversation->number,
				$conversation->customer->id,
				$conversation->customer->email,
				$conversation->subject,
				$conversation->status,
				Carbon::parse($conversation->createdAt, "UTC")->setTimezone("Europe/Berlin"),
				Carbon::parse($conversation->updatedAt, "UTC")->setTimezone("Europe/Berlin"),
				$conversation->mailboxId,
				null
			);
		}

		return collect($tickets);
	}

	public function getTicketMessages(mixed $ticketId): Collection {
		$response = $this->makeRequest("conversations/{$ticketId}");
		$admins = $this->_getAdminMails();

		$messages = [];
		foreach ($response->_embedded->threads as $thread) {

			if (!in_array($thread->type, ["message", "customer"])) {
				continue;
			}

			$attachments = [];

			$re = '/<img(?s).*src="(?<image_url>[A-Za-z0-9-_.:\/?=&;]*)"(?s).*alt="(?<image_name>[A-Za-z0-9-_.]*)">/m';
			$str = $thread->body;

			preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);

			foreach ($matches as $index => $match) {
				$match['image_url'] = str_replace("&amp;", "&", $match['image_url']);

				$attachments[] = new TicketMessageAttachmentResponse(
					"INLINE-{$index}",
					$match['image_name'],
					-1,
					$match['image_url'],
				);
			}


			foreach ($thread->_embedded->attachments as $attachment) {
				$attachments[] = new TicketMessageAttachmentResponse(
					$attachment->id,
					$attachment->fileName,
					$attachment->size,
					$attachment->fileUrl,
				);
			}

			$adminId = $thread->createdBy->type === "user" ? ($admins->get($thread->createdBy->email, $admins->first())) : null;

//			dump($thread);
//			dump($thread->body);
//			dump($thread->createdAt);
//			dump(Carbon::parse($thread->createdAt)->addHours(2)->format("d.m.Y H:i:s"));
//			dd("A");

			$messages[] = new TicketMessageResponse(
				$thread->id,
				$thread->body,
				Carbon::parse($thread->createdAt, "UTC")->setTimezone("Europe/Berlin"),
				$adminId,
				collect($attachments)
			);
		}

		return collect($messages);
	}

	public function closeTicket(Ticket $ticket) {
		$response = $this->makeRequest("conversations/{$ticket->ticket_provider_external_id}", "PUT", [
			"status" => "closed",
			"byUser" => $this->_getFreescoutUserId()
		]);
	}


	public function createTicket(
		SalesChannel      $salesChannel,
		?Customer         $customer,
		string            $subject,
		string            $message,
		mixed             $product,
		?TicketDepartment $department = null,
		array             $attachments = [],
		?string           $guestName = null,
		?string           $guestMail = null,
	): TicketCreatedResponse {
		$mailboxId = $department->provider_department_id["freescout"] ?? $this->provider->default_department_id;
		$customerId = $this->getCustomerId($customer, $guestName, $guestMail);
		$additionalTexts = [];


		if($customer !== null) {
			$customerViewUrl = $salesChannel->url . route('admin.customer.edit', $customer->id, false);
			$additionalTexts = [
				"Ticket aus hostware in Freescout erstellt",
				"Verkaufskanal: {$salesChannel->name}",
				"<a href='{$customerViewUrl}'>Kunde ansehen (#{$customer->customer_number})</a>"
			];

			if ($product !== null) {
				$productRoute = "N-A";
				$productText = "N/A";

				if ($product instanceof Domain) {
					$productRoute = route('admin.domain.view', $product->fqdn, false);
					$productText = "{$product->fqdn} (Domain)";
				}
				if ($product instanceof ProductHosting) {
					$productRoute = route('admin.hosting.hostings.view', $product->id, false);
					$productText = "{$product->product->name} #{$product->product_hosting_number} (Hosting)";
				}

				$completeUrl = $salesChannel->url . $productRoute;

				$additionalTexts[] = '<a href="' . $completeUrl . '">Verlinktes Produkt ansehen (' . $productText . ')</a>';
			}
		}

		$threadAttachments = [];
		foreach ($attachments as $attachment) {
			$threadAttachments[] = [
				'fileName' => $attachment->getClientOriginalName(),
				'mimeType' => $attachment->getMimeType(),
				'data' => base64_encode($attachment->getContent()),
			];
		}


		$response = $this->makeRequest("conversations", "POST", [
			"type" => "email",
			"mailboxId" => $mailboxId,
			"subject" => $subject,
			"customer" => [
				'id' => $customerId,
			],
			"threads" => [[
				'text' => $message,
				'type' => 'customer',
				'customer' => [
					'id' => $customerId,
				],
				"attachments" => $threadAttachments
			], [
				'text' => implode("<br>", $additionalTexts),
				'type' => 'note',
				'user' => $this->_getFreescoutUserId()
			]],
			"createdAt" => now()->setTimezone("UTC")->toISOString(),
			"status" => "active"
		]);

		/**
		 * Get the attachment IDs
		 */
		$attachmentResponse = [];
		foreach ($response->_embedded->threads[0]->_embedded->attachments as $file) {
			$attachmentResponse[] = $file->id;
		}


		Log::debug("Freescout ticket creation response", ["res" => $response]);

		return new TicketCreatedResponse(
			$response->id,
			$response->number,
			$response->_embedded->threads[0]->id,
			$attachmentResponse
		);
	}

	public function answerTicket(Ticket $ticket, string $message, array $attachments): TicketAnsweredResponse {
		$threadAttachments = [];
		foreach ($attachments as $attachment) {
			$threadAttachments[] = [
				'fileName' => $attachment->getClientOriginalName(),
				'mimeType' => $attachment->getMimeType(),
				'data' => base64_encode($attachment->getContent()),
			];
		}

		$customerId = $this->getCustomerId($ticket->customer);

		$response = $this->makeRequest("conversations/{$ticket->ticket_provider_external_id}/threads", "POST", [
			'text' => $message,
			'type' => 'customer',
			'customer' => [
				'id' => $customerId,
			],
			"attachments" => $threadAttachments
		]);

		/**
		 * Get the attachment IDs
		 */
		$attachmentResponse = [];
		foreach ($response->_embedded->attachments as $file) {
			$attachmentResponse[] = $file->id;
		}

		return new TicketAnsweredResponse(
			$response->id,
			$attachmentResponse,
		);
	}

	public function downloadAttachment(Ticket $ticket, TicketAttachment $attachment): BinaryFileResponse {
		$ticketMessages = $this->getTicketMessages($ticket->ticket_provider_external_id);

		/** @var TicketMessageAttachmentResponse $attachmentDetails */
		$attachmentDetails = $ticketMessages->pluck('attachments')->flatten()->where('id', $attachment->file_path)->first();

		$tempImage = tempnam(sys_get_temp_dir(), $attachment->name);
		copy($attachmentDetails->url, $tempImage);

		return response()->download($tempImage, $attachment->name);

	}

	public function getDepartments(): Collection {
		$response = $this->makeRequest("mailboxes");
		$result = collect();

		foreach ($response->_embedded->mailboxes as $mailbox) {
			$result->add(new TicketDepartmentResponse($mailbox->id, $mailbox->name));
		}

		return $result;
	}

	/**
	 * @throws GuzzleException
	 */
	private function makeRequest(string $url, string $method = "GET", array $payload = []) {
		$client = new Client();

		$data = [
			'headers' => [
				"X-FreeScout-API-Key" => $this->provider->api_key,
				"Content-Type" => "application/json",
			],
			'json' => $payload,
			'connect_timeout' => 10
		];

		if($method === "GET") {
			$data['query'] = $payload;
		}

		$response = $client->request($method, "{$this->provider->hostname}/api/{$url}", $data);

		return json_decode($response->getBody()->getContents());
	}

	private function getCustomerId(?Customer $customer, ?string $guestName = null, ?string $guestMail = null,) {
		if($customer === null) {
			/**
			 * Create a guest in freescout
			 */
			$allCustomers = $this->makeRequest("customers", "GET", [
				"email" => $guestMail
			]);

			if (count($allCustomers->_embedded->customers) === 1) {
				return $allCustomers->_embedded->customers[0]->id;
			}

			$response = $this->makeRequest("customers", "POST", [
				"firstName" => $guestName,
				"lastName" => "-",
				"notes" => "Aus hostware als Gast erstellt",
				"emails" => [[
					'value' => $guestMail,
					'type' => 'home',
				]]
			]);

			return $response->id;
		}

		if (isset($customer->support_ticket_providers['freescout'])) {
			return $customer->support_ticket_providers['freescout'];
		}


		/**
		 * Search existing customers for the email
		 */
		$allCustomers = $this->makeRequest("customers", "GET", [
			"email" => $customer->email
		]);

		if (count($allCustomers->_embedded->customers) === 1) {
			$customer->addTicketProviderId("freescout", $allCustomers->_embedded->customers[0]->id);

			return $allCustomers->_embedded->customers[0]->id;
		}


		$firstAddress = $customer->firstAddress();
		$customerViewUrl = $customer->salesChannel->url . route('admin.customer.edit', $customer->id, false);

		$response = $this->makeRequest("customers", "POST", [
			"firstName" => $customer->first_name,
			"lastName" => $customer->last_name,
			"phone" => $customer->phone,
			"photoUrl" => null,
			"jobTitle" => null,
			"photoType" => null,
			"address" => array(
				'city' => $firstAddress->city,
				'state' => '-',
				'zip' => $firstAddress->zipcode,
				'country' => $firstAddress->country,
				'address' => $firstAddress->street_full,
			),
			"notes" => "Aus hostware importiert\n\n{$customerViewUrl}",
			"company" => $customer->company,
			"emails" => [[
				'value' => $customer->email,
				'type' => 'home',
			]],
			"phones" => [[
				'value' => $customer->phone,
				'type' => 'home',
			]]
		]);

		$customer->addTicketProviderId("freescout", $response->id);
		return $response->id;
	}

	private function _getFreescoutUserId() {
		return Cache::remember("hw-freescout-systemuser-id", 3600, function () {
			$hostwareUser = $this->makeRequest("users", "GET", [
				"email" => "host@ware.io"
			]);

			if (count($hostwareUser->_embedded->users) === 1) {
				return $hostwareUser->_embedded->users[0]->id;
			}

			$response = $this->makeRequest("users", "POST", [
				"firstName" => "hostware",
				"lastName" => "System",
				"email" => "host@ware.io"
			]);

			return $response->id;
		});
	}

	public function getTicketUrl(Ticket $ticket): string {
		$ticketId = str_replace("FS", "", $ticket->ticket_number);

		return "{$this->provider->hostname}/conversation/{$ticket->ticket_provider_external_id}";
		return "{$this->provider->hostname}/conversation/{$ticketId}";
	}
}

```
