add: full multi-tenancy control
This commit is contained in:
438
packages/Webkul/Automation/src/Services/WebhookService.php
Normal file
438
packages/Webkul/Automation/src/Services/WebhookService.php
Normal file
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\Automation\Services;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Message;
|
||||
use Webkul\Contact\Repositories\PersonRepository;
|
||||
|
||||
class WebhookService
|
||||
{
|
||||
/**
|
||||
* The GuzzleHttp client instance.
|
||||
*/
|
||||
protected Client $client;
|
||||
|
||||
/**
|
||||
* Create a new webhook service instance.
|
||||
*/
|
||||
public function __construct(protected PersonRepository $personRepository)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'timeout' => 30,
|
||||
'connect_timeout' => 10,
|
||||
'verify' => true,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the webhook.
|
||||
*/
|
||||
public function triggerWebhook(mixed $data): array
|
||||
{
|
||||
if (
|
||||
! isset($data['method'])
|
||||
|| ! isset($data['end_point'])
|
||||
) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'response' => 'Missing required fields: method or end_point',
|
||||
];
|
||||
}
|
||||
|
||||
$headers = isset($data['headers']) ? $this->parseJsonField($data['headers']) : [];
|
||||
$payload = isset($data['payload']) ? $data['payload'] : null;
|
||||
$data['end_point'] = $this->appendQueryParams($data['end_point'], $data['query_params'] ?? '');
|
||||
|
||||
$formattedHeaders = $this->formatHeaders($headers);
|
||||
|
||||
$options = $this->buildRequestOptions($data['method'], $formattedHeaders, $payload);
|
||||
|
||||
try {
|
||||
$response = $this->client->request(
|
||||
strtoupper($data['method']),
|
||||
$data['end_point'],
|
||||
$options,
|
||||
);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'response' => $response->getBody()->getContents(),
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'headers' => $response->getHeaders(),
|
||||
];
|
||||
} catch (RequestException $e) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'response' => $e->hasResponse() ? Message::toString($e->getResponse()) : $e->getMessage(),
|
||||
'status_code' => $e->hasResponse() ? $e->getResponse()->getStatusCode() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON field safely.
|
||||
*/
|
||||
protected function parseJsonField(mixed $field): array
|
||||
{
|
||||
if (is_array($field)) {
|
||||
return $field;
|
||||
}
|
||||
|
||||
if (is_string($field)) {
|
||||
$decoded = json_decode($field, true);
|
||||
|
||||
if (
|
||||
json_last_error() === JSON_ERROR_NONE
|
||||
&& is_array($decoded)
|
||||
) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build request options based on method and content type.
|
||||
*/
|
||||
protected function buildRequestOptions(string $method, array $headers, mixed $payload): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if (! empty($headers)) {
|
||||
$options['headers'] = $headers;
|
||||
}
|
||||
|
||||
if (
|
||||
$payload !== null
|
||||
&& ! in_array(strtoupper($method), ['GET', 'HEAD'])
|
||||
) {
|
||||
$contentType = $this->getContentType($headers);
|
||||
|
||||
switch ($contentType) {
|
||||
case 'application/json':
|
||||
$options['json'] = $this->prepareJsonPayload($payload);
|
||||
|
||||
break;
|
||||
|
||||
case 'application/x-www-form-urlencoded':
|
||||
$options['form_params'] = $this->prepareFormPayload($payload);
|
||||
|
||||
break;
|
||||
|
||||
case 'multipart/form-data':
|
||||
$options['multipart'] = $this->prepareMultipartPayload($payload);
|
||||
|
||||
break;
|
||||
|
||||
case 'text/plain':
|
||||
case 'text/xml':
|
||||
case 'application/xml':
|
||||
$options['body'] = $this->prepareRawPayload($payload);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$options = array_merge($options, $this->autoDetectPayloadFormat($payload));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare JSON payload.
|
||||
*/
|
||||
protected function prepareJsonPayload(mixed $payload): mixed
|
||||
{
|
||||
if (is_string($payload)) {
|
||||
$decoded = json_decode($payload, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
if (is_array($payload)) {
|
||||
return $this->formatPayload($payload);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare form payload.
|
||||
*/
|
||||
protected function prepareFormPayload(mixed $payload): array
|
||||
{
|
||||
if (is_string($payload)) {
|
||||
$decoded = json_decode($payload, true);
|
||||
|
||||
if (
|
||||
json_last_error() === JSON_ERROR_NONE
|
||||
&& is_array($decoded)
|
||||
) {
|
||||
return $this->formatPayload($decoded);
|
||||
}
|
||||
|
||||
parse_str($payload, $parsed);
|
||||
|
||||
return $parsed ?: [];
|
||||
}
|
||||
|
||||
if (is_array($payload)) {
|
||||
return $this->formatPayload($payload);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare multipart payload.
|
||||
*/
|
||||
protected function prepareMultipartPayload(mixed $payload): array
|
||||
{
|
||||
$formattedPayload = $this->prepareFormPayload($payload);
|
||||
|
||||
return $this->buildMultipartData($formattedPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare raw payload.
|
||||
*/
|
||||
protected function prepareRawPayload(mixed $payload): string
|
||||
{
|
||||
if (is_string($payload)) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
if (is_array($payload)) {
|
||||
return json_encode($payload);
|
||||
}
|
||||
|
||||
return (string) $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-detect payload format when no content-type is specified.
|
||||
*/
|
||||
protected function autoDetectPayloadFormat(mixed $payload): array
|
||||
{
|
||||
if (is_string($payload)) {
|
||||
$decoded = json_decode($payload, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return ['json' => $decoded];
|
||||
}
|
||||
|
||||
if (
|
||||
strpos($payload, '=') !== false
|
||||
&& strpos($payload, '&') !== false
|
||||
) {
|
||||
parse_str($payload, $parsed);
|
||||
|
||||
return ['form_params' => $parsed];
|
||||
}
|
||||
|
||||
return ['body' => $payload];
|
||||
}
|
||||
|
||||
if (is_array($payload)) {
|
||||
$formatted = $this->formatPayload($payload);
|
||||
|
||||
return ['json' => $formatted];
|
||||
}
|
||||
|
||||
return ['body' => (string) $payload];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type from headers.
|
||||
*/
|
||||
protected function getContentType(array $headers): string
|
||||
{
|
||||
foreach ($headers as $key => $value) {
|
||||
if (strtolower($key) === 'content-type') {
|
||||
$contentType = strtolower(trim(explode(';', $value)[0]));
|
||||
|
||||
return $contentType;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build multipart data array.
|
||||
*/
|
||||
protected function buildMultipartData(array $payload): array
|
||||
{
|
||||
$multipart = [];
|
||||
|
||||
foreach ($payload as $key => $value) {
|
||||
$multipart[] = [
|
||||
'name' => $key,
|
||||
'contents' => is_array($value) ? json_encode($value) : (string) $value,
|
||||
];
|
||||
}
|
||||
|
||||
return $multipart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format headers array.
|
||||
*/
|
||||
protected function formatHeaders(array $headers): array
|
||||
{
|
||||
if (empty($headers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$formattedHeaders = [];
|
||||
|
||||
if ($this->isKeyValuePairArray($headers)) {
|
||||
foreach ($headers as $header) {
|
||||
if (
|
||||
isset($header['key'])
|
||||
&& array_key_exists('value', $header)
|
||||
) {
|
||||
if (
|
||||
isset($header['disabled'])
|
||||
&& $header['disabled']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($header['enabled'])
|
||||
&& ! $header['enabled']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$formattedHeaders[$header['key']] = $header['value'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$formattedHeaders = $headers;
|
||||
}
|
||||
|
||||
return $formattedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format any incoming payload into a clean associative array.
|
||||
*/
|
||||
protected function formatPayload(mixed $payload): array
|
||||
{
|
||||
if (empty($payload)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (
|
||||
is_array($payload)
|
||||
&& isset($payload['key'])
|
||||
&& array_key_exists('value', $payload)
|
||||
) {
|
||||
return [$payload['key'] => $payload['value']];
|
||||
}
|
||||
|
||||
if (
|
||||
is_array($payload)
|
||||
&& array_is_list($payload)
|
||||
&& $this->isKeyValuePairArray($payload)
|
||||
) {
|
||||
$formatted = [];
|
||||
|
||||
foreach ($payload as $item) {
|
||||
if (
|
||||
isset($item['key'])
|
||||
&& array_key_exists('value', $item)
|
||||
) {
|
||||
if (
|
||||
isset($item['disabled'])
|
||||
&& $item['disabled']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($item['enabled'])
|
||||
&& ! $item['enabled']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$formatted[$item['key']] = $item['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if array is a key-value pair array.
|
||||
*/
|
||||
protected function isKeyValuePairArray(array $array): bool
|
||||
{
|
||||
if (empty($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($array['key'])
|
||||
&& array_key_exists('value', $array)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_is_list($array)) {
|
||||
return collect($array)->every(fn ($item) => is_array($item) && isset($item['key']) && array_key_exists('value', $item)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append query parameters to the endpoint URL.
|
||||
*/
|
||||
protected function appendQueryParams(string $endPoint, string $queryParamsJson): string
|
||||
{
|
||||
$queryParams = json_decode($queryParamsJson, true);
|
||||
|
||||
if (
|
||||
json_last_error() !== JSON_ERROR_NONE
|
||||
|| ! is_array($queryParams)
|
||||
) {
|
||||
return $endPoint;
|
||||
}
|
||||
|
||||
$queryArray = [];
|
||||
|
||||
foreach ($queryParams as $param) {
|
||||
if (
|
||||
isset($param['key'])
|
||||
&& array_key_exists('value', $param)
|
||||
) {
|
||||
$queryArray[$param['key']] = $param['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$queryString = http_build_query($queryArray);
|
||||
|
||||
$glue = str_contains($endPoint, '?') ? '&' : '?';
|
||||
|
||||
return $endPoint.($queryString ? $glue.$queryString : '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user