Skip to content

Commit

Permalink
feat: allow decorating payload url with placeholders (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
thorbrink committed Dec 1, 2023
1 parent 4c125ac commit 4176929
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 15 deletions.
8 changes: 4 additions & 4 deletions source/php/AcfFields/json/webhooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"name": "payload_url",
"aria-label": "",
"type": "url",
"instructions": "Target url used for the request made when the selected hook is fired.",
"instructions": "Target url used for the request made when the selected hook is fired. The url can contain placeholders that will be replaced with hook arguments before request is sent. Placeholder format is the \"$\" character followed by the index of the hooks parameter. E.g. to use the first parameter in the hook, the placeholder should be \"$1\".\r\nNote that only hook arguments of type string or int can be used by placeholders, all other will be omitted.",
"required": 1,
"conditional_logic": 0,
"wrapper": {
Expand All @@ -39,7 +39,7 @@
"id": ""
},
"default_value": "",
"placeholder": "",
"placeholder": "https:\/\/my.client.site\/api\/$1",
"parent_repeater": "field_6565af01d3812"
},
{
Expand Down Expand Up @@ -93,7 +93,7 @@
{
"key": "field_6566fe78ee13f",
"label": "Action Priority",
"name": "action_prority",
"name": "action_priority",
"aria-label": "",
"type": "number",
"instructions": "Priority passed to add_action for the selected action hook.",
Expand All @@ -120,7 +120,7 @@
"aria-label": "",
"type": "true_false",
"instructions": "Wether to send the data passed to the selected action hook in the request to the payload ur or not.",
"required": 1,
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "25",
Expand Down
9 changes: 5 additions & 4 deletions source/php/AcfFields/php/webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
'name' => 'payload_url',
'aria-label' => '',
'type' => 'url',
'instructions' => __('Target url used for the request made when the selected hook is fired.', 'webhooks-manager'),
'instructions' => __('Target url used for the request made when the selected hook is fired. The url can contain placeholders that will be replaced with hook arguments before request is sent. Placeholder format is the "$" character followed by the index of the hooks parameter. E.g. to use the first parameter in the hook, the placeholder should be "$1".
Note that only hook arguments of type string or int can be used by placeholders, all other will be omitted.', 'webhooks-manager'),
'required' => 1,
'conditional_logic' => 0,
'wrapper' => array(
Expand All @@ -42,7 +43,7 @@
'id' => '',
),
'default_value' => '',
'placeholder' => '',
'placeholder' => __('https://my.client.site/api/$1', 'webhooks-manager'),
'parent_repeater' => 'field_6565af01d3812',
),
1 => array(
Expand Down Expand Up @@ -98,7 +99,7 @@
3 => array(
'key' => 'field_6566fe78ee13f',
'label' => __('Action Priority', 'webhooks-manager'),
'name' => 'action_prority',
'name' => 'action_priority',
'aria-label' => '',
'type' => 'number',
'instructions' => __('Priority passed to add_action for the selected action hook.', 'webhooks-manager'),
Expand All @@ -125,7 +126,7 @@
'aria-label' => '',
'type' => 'true_false',
'instructions' => __('Wether to send the data passed to the selected action hook in the request to the payload ur or not.', 'webhooks-manager'),
'required' => 1,
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '25',
Expand Down
4 changes: 3 additions & 1 deletion source/php/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use WebhooksManager\WebhookActionBinder\WebhookActionBinder;
use WebhooksManager\WebhookDispatcher\WebhookDispatcher;
use WebhooksManager\Options\Options;
use WebhooksManager\UrlDecorator\UrlDecorator;
use WebhooksManager\WebhooksRegistry\WebhooksRegistry;

/**
Expand Down Expand Up @@ -39,7 +40,8 @@ public function initialize()
// Bind webhooks to actions on plugins loaded
add_action('plugins_loaded', function () use ($webhooksRegistry) {
foreach ($webhooksRegistry->getWebhooks() as $webhook) {
$dispatcher = new WebhookDispatcher();
$urlDecorator = new UrlDecorator();
$dispatcher = new WebhookDispatcher($urlDecorator);
$webhookActionBinder = new WebhookActionBinder($webhook, $dispatcher);
$webhookActionBinder->bindWebhookToAction();
}
Expand Down
54 changes: 54 additions & 0 deletions source/php/UrlDecorator/UrlDecorator.Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace WebhooksManager\UrlDecorator\Test;

use PHPUnit\Framework\TestCase;
use WebhooksManager\UrlDecorator\UrlDecorator;

class UrlDecoratorTest extends TestCase
{
/**
* @dataProvider dataProvider
*/
public function testDecorateUrlWithReplacesSpecifiersWithArguments($url, $arguments, $expected)
{
$urlDecorator = new UrlDecorator();
$actual = $urlDecorator->decorateUrlWith($url, $arguments);

$this->assertEquals($expected, $actual);
}

public function dataProvider()
{
return [
['https://example.com/$1', [1], 'https://example.com/1'],
['https://example.com/$1', ['foo'], 'https://example.com/foo'],
['https://example.com/$1/$2', [1, 'foo'], 'https://example.com/1/foo']
];
}

public function testDecorateUrlWithReturnsUrlWhenArgumentsIsEmptyArray()
{
$urlDecorator = new UrlDecorator();
$result = $urlDecorator->decorateUrlWith('https://example.com', []);

$this->assertEquals('https://example.com', $result);
}

public function testDecorateUrlWithReturnsUrlWhenArgumentsAreNull()
{
$urlDecorator = new UrlDecorator();
$result = $urlDecorator->decorateUrlWith('https://example.com', null);

$this->assertEquals('https://example.com', $result);
}

// Test that arguments that are not strings or numbers are not included in the URL.
public function testDecorateUrlWithDoesNotIncludeArgumentsThatAreNotStringsOrNumbers()
{
$urlDecorator = new UrlDecorator();
$result = $urlDecorator->decorateUrlWith('https://example.com/$1/$2', [1, 'foo', ['bar']]);

$this->assertEquals('https://example.com/1/foo', $result);
}
}
31 changes: 31 additions & 0 deletions source/php/UrlDecorator/UrlDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace WebhooksManager\UrlDecorator;

class UrlDecorator implements UrlDecoratorInterface
{
/**
* Decorates the given URL with the given arguments.
*
* @param string $url The URL to be decorated.
* Should be formatted with sprintf specifiers. https://www.php.net/manual/en/function.sprintf.php
* @param mixed $arguments The arguments to be included in the URL.
* @return string The decorated URL.
*/
public function decorateUrlWith(string $url, ?array $arguments): string
{
if (empty($arguments)) {
return $url;
}

foreach ($arguments as $key => $value) {
if (!is_string($value) && !is_numeric($value)) {
continue;
}

$url = str_replace('$' . $key + 1, urlencode($value), $url);
}

return $url;
}
}
15 changes: 15 additions & 0 deletions source/php/UrlDecorator/UrlDecoratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace WebhooksManager\UrlDecorator;

interface UrlDecoratorInterface
{
/**
* Decorates the given URL with the given arguments.
*
* @param string $url The URL to be decorated.
* @param mixed $arguments The arguments to be included in the URL.
* @return string The decorated URL.
*/
public function decorateUrlWith(string $url, ?array $arguments): string;
}
14 changes: 12 additions & 2 deletions source/php/WebhookDispatcher/WebhookDispatcher.Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace WebhooksManager\WebhookDispatcher\Test;

use PHPUnit\Framework\MockObject\MockObject;
use WebhooksManager\UrlDecorator\UrlDecoratorInterface;
use WebhooksManager\Webhook\WebhookInterface;
use WebhooksManager\WebhookDispatcher\WebhookDispatcher;
use WP_Mock;
Expand All @@ -17,12 +18,14 @@ public function testDispatchMakesGetRequestIfHttpMethodIsGet()
'args' => ['https://example.com?foo=bar', ['blocking' => false]],
]);

$urlDecoratorMock = $this->getUrlDecoratorMock();
$urlDecoratorMock->method('decorateUrlWith')->willReturn('https://example.com');
$webhook = $this->getWebhookMock();
$webhook->method('getHttpMethod')->willReturn('GET');
$webhook->method('getPayloadUrl')->willReturn('https://example.com');
$webhook->method('shouldSendPayload')->willReturn(true);

$webhookDispatcher = new WebhookDispatcher();
$webhookDispatcher = new WebhookDispatcher($urlDecoratorMock);
$webhookDispatcher->dispatch($webhook, ['foo' => 'bar']);

$this->assertConditionsMet();
Expand All @@ -37,12 +40,14 @@ public function testDispatchMakesPostRequestIfHttpMethodIsPost()
'args' => ['https://example.com', ['blocking' => false, 'data_format' => 'body', 'body' => $body]],
]);

$urlDecoratorMock = $this->getUrlDecoratorMock();
$urlDecoratorMock->method('decorateUrlWith')->willReturn('https://example.com');
$webhook = $this->getWebhookMock();
$webhook->method('getHttpMethod')->willReturn('POST');
$webhook->method('getPayloadUrl')->willReturn('https://example.com');
$webhook->method('shouldSendPayload')->willReturn(true);

$webhookDispatcher = new WebhookDispatcher();
$webhookDispatcher = new WebhookDispatcher($urlDecoratorMock);
$webhookDispatcher->dispatch($webhook, $actionArguments);

$this->assertConditionsMet();
Expand All @@ -52,4 +57,9 @@ private function getWebhookMock(): MockObject|WebhookInterface
{
return $this->createMock(WebhookInterface::class);
}

private function getUrlDecoratorMock(): MockObject|UrlDecoratorInterface
{
return $this->createMock(UrlDecoratorInterface::class);
}
}
15 changes: 11 additions & 4 deletions source/php/WebhookDispatcher/WebhookDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace WebhooksManager\WebhookDispatcher;

use WebhooksManager\UrlDecorator\UrlDecoratorInterface;
use WebhooksManager\Webhook\WebhookInterface;

/**
Expand All @@ -11,6 +12,10 @@
*/
class WebhookDispatcher implements WebhookDispatcherInterface
{
public function __construct(private UrlDecoratorInterface $urlDecorator)
{
}

/**
* Dispatches the given webhook by sending an HTTP request.
*
Expand All @@ -20,12 +25,13 @@ class WebhookDispatcher implements WebhookDispatcherInterface
*/
public function dispatch(WebhookInterface $webhook, $hookArguments): void
{
$payload = $webhook->shouldSendPayload() ? $hookArguments : null;
$payload = $webhook->shouldSendPayload() ? $hookArguments : null;
$payloadUrl = $this->urlDecorator->decorateUrlWith($webhook->getPayloadUrl(), $hookArguments);

if ($webhook->getHttpMethod() === 'GET') {
$this->dispatchGetRequest($webhook->getPayloadUrl(), $payload);
$this->dispatchGetRequest($payloadUrl, $payload);
} else {
$this->dispatchPostRequest($webhook->getPayloadUrl(), $payload);
$this->dispatchPostRequest($payloadUrl, $payload);
}
}

Expand Down Expand Up @@ -54,7 +60,8 @@ private function dispatchGetRequest($url, $payload)
*/
private function dispatchPostRequest(string $url, $payload)
{
$arguments = [ 'body' => json_encode($payload), 'data_format' => 'body', 'blocking' => false ];
$bodyArguments = is_array($payload) ? ['body' => json_encode($payload)] : [];
$arguments = [ 'data_format' => 'body', 'blocking' => false, ...$bodyArguments ];
wp_remote_post($url, $arguments);
}
}

0 comments on commit 4176929

Please sign in to comment.