Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Form Webhooks: add logging and filter flag for initialization
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@ public function process_form_submission() {
);
}

if ( ! empty( $form->attributes['webhooks'] ) ) {
if ( Jetpack_Forms::is_webhooks_enabled() && ! empty( $form->attributes['webhooks'] ) ) {
Form_Webhooks::init();
}
// Process the form
Expand Down
10 changes: 9 additions & 1 deletion projects/packages/forms/src/service/class-form-webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,24 @@ private function get_enabled_webhooks( $attributes = array() ) {
);

// Validate webhook configuration
if ( empty( $setup['enabled'] ) || empty( $setup['url'] ) ) {
if ( empty( $setup['enabled'] ) ) {
continue;
}
// Validate webhook configuration
if ( empty( $setup['url'] ) ) {
do_action( 'jetpack_forms_log', 'webhook_skipped', 'url_empty' );
continue;
}

// Validate format
if ( ! array_key_exists( strtolower( $setup['format'] ), self::VALID_FORMATS_MAP ) ) {
do_action( 'jetpack_forms_log', 'webhook_skipped', 'format_invalid', $setup );
continue;
}

// Validate method
if ( ! in_array( strtoupper( $setup['method'] ), self::VALID_METHODS, true ) ) {
do_action( 'jetpack_forms_log', 'webhook_skipped', 'method_invalid', $setup );
continue;
}

Expand Down Expand Up @@ -232,6 +239,7 @@ private function send_webhook( $data, $webhook ) {
),
'sslverify' => true,
);

return wp_remote_request( $url, $args );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4014,4 +4014,33 @@ public function test_parse_handles_null_fields_without_fatal_error() {
$this->assertIsString( $result5, 'Parse should return a string with multiple fields' );
$this->assertStringNotContainsString( 'is-single-input-form', $result5, 'Should not have single-input-form class with multiple fields' );
}

/**
* Test is_webhooks_enabled returns false by default.
*/
public function test_is_webhooks_enabled_default() {
$this->assertFalse( \Automattic\Jetpack\Forms\Jetpack_Forms::is_webhooks_enabled() );
}

/**
* Test is_webhooks_enabled filter can be used to enable webhooks.
*/
public function test_is_webhooks_enabled_filter_enable() {
add_filter( 'jetpack_forms_webhooks_enabled', '__return_true' );

$this->assertTrue( \Automattic\Jetpack\Forms\Jetpack_Forms::is_webhooks_enabled() );

remove_filter( 'jetpack_forms_webhooks_enabled', '__return_true' );
}

/**
* Test is_webhooks_enabled filter can be used to keep webhooks disabled.
*/
public function test_is_webhooks_enabled_filter_disable() {
add_filter( 'jetpack_forms_webhooks_enabled', '__return_false' );

$this->assertFalse( \Automattic\Jetpack\Forms\Jetpack_Forms::is_webhooks_enabled() );

remove_filter( 'jetpack_forms_webhooks_enabled', '__return_false' );
}
}
181 changes: 181 additions & 0 deletions projects/packages/forms/tests/php/service/Form_Webhooks_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,187 @@ function ( $preempt, $args, $url ) use ( &$captured_request ) {
$this->assertEquals( 'summer_2025', $body_data['utm_campaign'] );
}

/**
* Test logging action is triggered when webhook URL is empty.
*/
public function test_send_webhooks_logs_empty_url() {
$form = $this->create_mock_form(
array(
'webhooks' => array(
array(
'webhook_id' => 'test-webhook',
'url' => '',
'format' => 'json',
'method' => 'POST',
'enabled' => true,
),
),
)
);
$fields = array( $this->create_mock_field( $form, 'test-field', 'test value' ) );

$logged_events = array();
add_action(
'jetpack_forms_log',
function ( $event, $reason, $data = null ) use ( &$logged_events ) {
$logged_events[] = array(
'event' => $event,
'reason' => $reason,
'data' => $data,
);
},
10,
3
);

$webhooks = Form_Webhooks::init();
$webhooks->send_webhooks( 123, $fields, false, array() );

$this->assertCount( 1, $logged_events );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'webhook_skipped', $logged_events[0]['event'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'url_empty', $logged_events[0]['reason'] );
}

/**
* Test logging action is triggered when webhook format is invalid.
*/
public function test_send_webhooks_logs_invalid_format() {
$form = $this->create_mock_form(
array(
'webhooks' => array(
array(
'webhook_id' => 'test-webhook',
'url' => 'https://example.com/webhook',
'format' => 'xml',
'method' => 'POST',
'enabled' => true,
),
),
)
);
$fields = array( $this->create_mock_field( $form, 'test-field', 'test value' ) );

$logged_events = array();
add_action(
'jetpack_forms_log',
function ( $event, $reason, $data = null ) use ( &$logged_events ) {
$logged_events[] = array(
'event' => $event,
'reason' => $reason,
'data' => $data,
);
},
10,
3
);

$webhooks = Form_Webhooks::init();
$webhooks->send_webhooks( 123, $fields, false, array() );

$this->assertCount( 1, $logged_events );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'webhook_skipped', $logged_events[0]['event'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'format_invalid', $logged_events[0]['reason'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertIsArray( $logged_events[0]['data'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'xml', $logged_events[0]['data']['format'] );
}

/**
* Test logging action is triggered when webhook method is invalid.
*/
public function test_send_webhooks_logs_invalid_method() {
$form = $this->create_mock_form(
array(
'webhooks' => array(
array(
'webhook_id' => 'test-webhook',
'url' => 'https://example.com/webhook',
'format' => 'json',
'method' => 'DELETE',
'enabled' => true,
),
),
)
);
$fields = array( $this->create_mock_field( $form, 'test-field', 'test value' ) );

$logged_events = array();
add_action(
'jetpack_forms_log',
function ( $event, $reason, $data = null ) use ( &$logged_events ) {
$logged_events[] = array(
'event' => $event,
'reason' => $reason,
'data' => $data,
);
},
10,
3
);

$webhooks = Form_Webhooks::init();
$webhooks->send_webhooks( 123, $fields, false, array() );

$this->assertCount( 1, $logged_events );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'webhook_skipped', $logged_events[0]['event'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'method_invalid', $logged_events[0]['reason'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertIsArray( $logged_events[0]['data'] );
// @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset
$this->assertEquals( 'DELETE', $logged_events[0]['data']['method'] );
}

/**
* Test webhook method validation is case-insensitive.
*/
public function test_send_webhooks_accepts_lowercase_method() {
$form = $this->create_mock_form(
array(
'webhooks' => array(
array(
'webhook_id' => 'test-webhook',
'url' => 'https://example.com/webhook',
'format' => 'json',
'method' => 'post', // lowercase should be accepted
'enabled' => true,
),
),
)
);
$fields = array( $this->create_mock_field( $form, 'test-field', 'test value' ) );

$captured_request = null;
add_filter(
'pre_http_request',
function ( $preempt, $args, $url ) use ( &$captured_request ) {
$captured_request = array(
'url' => $url,
'args' => $args,
);
return array(
'response' => array( 'code' => 200 ),
'body' => '{"success":true}',
'headers' => new CaseInsensitiveDictionary( array( 'Content-Type' => 'application/json' ) ),
);
},
10,
3
);

$webhooks = Form_Webhooks::init();
$webhooks->send_webhooks( 123, $fields, false, array() );

$this->assertNotNull( $captured_request, 'HTTP request should be made even with lowercase method' );
$this->assertEquals( 'post', $captured_request['args']['method'] );
}

/**
* Helper method to create a mock form.
*
Expand Down
Loading