<?php
|
namespace JVBase\integrations;
|
|
use Exception;
|
use WP_REST_Request;
|
use WP_REST_Response;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
trait Webhooks {
|
use _Base;
|
protected bool $hasWebhooks = true;
|
|
abstract protected function validateWebhook(array $payload):bool;
|
abstract protected function processWebhook(array $payload):bool;
|
abstract protected function extractWebhookId(array $payload):string;
|
|
/**
|
* Register webhook endpoint
|
*/
|
protected function registerWebhookEndpoint(): void
|
{
|
// Register REST API endpoint for webhooks
|
add_action('rest_api_init', function() {
|
register_rest_route('jvb/v1', '/webhooks/' . $this->service_name, [
|
'methods' => 'POST',
|
'callback' => [$this, 'handleWebhookRequest'],
|
'permission_callback' => '__return_true', // Webhooks come from external services
|
]);
|
});
|
}
|
|
/**
|
* Handle webhook REST API request
|
*/
|
public function handleWebhookRequest(WP_REST_Request $request): WP_REST_Response
|
{
|
$payload = $request->get_params();
|
$headers = $request->get_headers();
|
|
// Include headers in payload for signature validation
|
$payload['_headers'] = $headers;
|
|
$success = $this->handleWebhook($payload);
|
|
return new WP_REST_Response([
|
'success' => $success
|
], $success ? 200 : 400);
|
}
|
|
public function handleWebhook(array $payload):bool
|
{
|
error_log('[Integrations]::handleWebhook for '.$this->service_name.': '.print_r($payload, true));
|
|
if (!$this->validateWebhook($payload)) {
|
$this->logError('handleWebhook', 'Webhook validation failed.');
|
return false;
|
}
|
|
//Check for duplicate processing
|
if ($this->isWebhookProcessed($payload)) {
|
return true;
|
}
|
|
try {
|
$result = $this->processWebhook($payload);
|
|
if ($result) {
|
$this->markWebhookProcessed($payload);
|
} else {
|
$this->logError('handleWebhook', 'Webhook processing returned false.');
|
}
|
return $result;
|
} catch (Exception $e) {
|
$this->logError('handleWebhook', 'Webhook processing failed', [
|
'error' => $e->getMessage(),
|
'webhook_id' => $this->extractWebhookId($payload),
|
'trace' => $e->getTraceAsString()
|
]);
|
|
Auth::getInstance()->markUnhealthy($this->service_name, 'oauth', $this->userID);
|
return false;
|
}
|
}
|
|
|
protected function renderWebhookUrl(): string
|
{
|
if (!$this->hasWebhooks || !$this->isSetUp()) {
|
return '';
|
}
|
|
$webhook_url = rest_url('jvb/v1/webhooks/' . $this->service_name);
|
|
return sprintf(
|
'<div class="webhook-info">
|
<h4>Webhook URL</h4>
|
<code id="webhook-url-%s">%s</code>
|
<p class="hint">Add this URL to your %s webhook settings</p>
|
</div>',
|
esc_attr($this->service_name),
|
esc_html($webhook_url),
|
esc_html($this->title)
|
);
|
}
|
|
protected function isWebhookProcessed(array $payload):bool
|
{
|
$id = $this->extractWebhookId($payload);
|
$key = BASE."webhook_processed_{$this->service_name}_{$id}";
|
return get_transient($key)!==false;
|
}
|
protected function markWebhookProcessed(array $payload):void
|
{
|
$id = $this->extractWebhookId($payload);
|
if (!empty($id)) {
|
$key = BASE."webhook_processed_{$this->service_name}_{$id}";
|
set_transient($key, true, DAY_IN_SECONDS);
|
}
|
|
|
}
|
}
|