Commit 1d9cd3211f9 for woocommerce
commit 1d9cd3211f999418032eb0d6ddaaa9f05a918869
Author: Miguel Gasca <miguel.gasca@automattic.com>
Date: Thu Oct 15 16:45:35 2026 +0200
Fix webhooks REST links skipping webhooks after an unaligned offset (#69832)
* Fix webhooks REST links skipping webhooks after an unaligned offset
#68499 made the webhooks list apply `offset` and built the prev and
next links from the page the offset falls on. An offset that is not a
multiple of per_page falls between two pages, so `next` skipped the
rest of that page: per_page=2&offset=3 returned webhooks 4 and 5, and
its next link returned 7. The first hop skips per_page minus
(offset mod per_page) webhooks, and prev overlaps the current slice.
diff --git a/plugins/woocommerce/changelog/fix-WOOAIRR-514-webhooks-offset-links b/plugins/woocommerce/changelog/fix-WOOAIRR-514-webhooks-offset-links
new file mode 100644
index 00000000000..91af2be2462
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-WOOAIRR-514-webhooks-offset-links
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Fix webhooks REST pagination links for unaligned offsets from unreleased #68499; no merchant change.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-webhooks-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-webhooks-v1-controller.php
index 1b61e5a1d84..80a908c33fe 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-webhooks-v1-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-webhooks-v1-controller.php
@@ -279,18 +279,30 @@ class WC_REST_Webhooks_V1_Controller extends WC_REST_Controller {
$response = rest_ensure_response( $webhooks );
$per_page = (int) $prepared_args['limit'];
- $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
+ $offset = (int) $prepared_args['offset'];
+ $page = ceil( ( $offset / $per_page ) + 1 );
$total_webhooks = $results->total;
$max_pages = $results->max_num_pages;
- // The links below navigate by page, so the caller's offset must not ride along in
- // them: it takes precedence over the page and would return this same slice again.
- $link_params = $request->get_query_params();
- unset( $link_params['offset'] );
+ $link_params = $request->get_query_params();
+ unset( $link_params['offset'], $link_params['page'] );
$base = add_query_arg( $link_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
$response->header( 'X-WP-Total', $total_webhooks );
$response->header( 'X-WP-TotalPages', $max_pages );
+ // An offset that is not a multiple of per_page falls between two pages, so offset requests get offset links.
+ if ( ! empty( $request['offset'] ) ) {
+ $prev_end = min( $offset, $total_webhooks );
+ if ( $prev_end > 0 ) {
+ $response->link_header( 'prev', add_query_arg( 'offset', max( 0, $prev_end - $per_page ), $base ) );
+ }
+ if ( $offset + $per_page < $total_webhooks ) {
+ $response->link_header( 'next', add_query_arg( 'offset', $offset + $per_page, $base ) );
+ }
+
+ return $response;
+ }
+
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-webhooks-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-webhooks-controller-tests.php
index 3c284a376ee..1a742e74a54 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-webhooks-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-webhooks-controller-tests.php
@@ -325,24 +325,28 @@ class WC_REST_Webhooks_Controller_Tests extends WC_REST_Unit_Test_Case {
}
/**
- * @testdox The links an offset request advertises lead to the pages either side of it.
+ * @testdox The links an offset request advertises lead to the webhooks either side of it, even when the offset is not a multiple of per_page.
+ *
+ * @testWith [2, 0, 4]
+ * [1, 0, 3]
+ *
+ * @param int $offset The offset to request, over pages of 2.
+ * @param int $prev_start Position of the first webhook the prev link should return.
+ * @param int $next_start Position of the first webhook the next link should return.
*/
- public function test_offset_pagination_links_lead_to_the_adjacent_pages(): void {
+ public function test_offset_pagination_links_lead_to_the_adjacent_pages( int $offset, int $prev_start, int $next_start ): void {
wp_set_current_user( 1 );
$webhook_ids = $this->create_webhooks( 5 );
$headers = $this->request_webhooks(
array(
'per_page' => 2,
- 'offset' => 2,
+ 'offset' => $offset,
)
)->get_headers();
- // Offset 2 over pages of 2 is page 2, so the links either side are pages 1 and 3.
- // Following them has to move: an offset left in the link would win over its page
- // and hand back the offset-2 slice again.
- $this->assertSame( array_slice( $webhook_ids, 0, 2 ), $this->follow_link( $headers['Link'], 'prev' ) );
- $this->assertSame( array_slice( $webhook_ids, 4, 2 ), $this->follow_link( $headers['Link'], 'next' ) );
+ $this->assertSame( array_slice( $webhook_ids, $prev_start, 2 ), $this->follow_link( $headers['Link'], 'prev' ) );
+ $this->assertSame( array_slice( $webhook_ids, $next_start, 2 ), $this->follow_link( $headers['Link'], 'next' ) );
}
/**