fix: downloadsRemaining returns null for numeric string values (#1000)

* fix: downloadsRemaining returns null for valid numeric string values

Use is_numeric() + intval() instead of 'integer' === gettype() to handle
cases where WooCommerce stores downloads_remaining as a numeric string.
Mirrors WooCommerce's own approach in its download templates.

Co-authored-by: Nestor Vera <hacknug@users.noreply.github.com>
Closes #937

* fix: Match CI phpcov php-code-coverage version to Docker container

The Docker container uses php-code-coverage 9.2.x which serializes
coverage with v9 classes (e.g. Xdebug3Driver). phpcov v9 bundles
php-code-coverage v11 with different classes, causing
__PHP_Incomplete_Class errors. Pin to phpcov v8 + php-code-coverage v9
to match the Docker environment.

---------

Co-authored-by: Nestor Vera <hacknug@users.noreply.github.com>
This commit is contained in:
Geoff Taylor
2026-03-26 23:03:30 -04:00
committed by GitHub
co-authored by Nestor Vera
parent cfd5115498
commit 506d352292
6 changed files with 89 additions and 15 deletions
@@ -219,8 +219,8 @@ class CustomerHelper extends WCG_Helper {
'url' => $item['download_url'],
'accessExpires' => $item['access_expires'],
'downloadId' => $item['download_id'],
'downloadsRemaining' => isset( $item['downloads_remaining'] ) && 'integer' === gettype( $item['downloads_remaining'] )
? $item['downloads_remaining']
'downloadsRemaining' => isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: null,
'name' => $item['download_name'],
'product' => array( 'databaseId' => $item['product_id'] ),
+2 -2
View File
@@ -456,8 +456,8 @@ class OrderHelper extends WCG_Helper {
'url' => $item['download_url'],
'accessExpires' => $item['access_expires'],
'downloadId' => $item['download_id'],
'downloadsRemaining' => isset( $item['downloads_remaining'] ) && 'integer' === gettype( $item['downloads_remaining'] )
? $item['downloads_remaining']
'downloadsRemaining' => isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: null,
'name' => $item['download_name'],
'product' => array( 'databaseId' => $item['product_id'] ),
+78 -4
View File
@@ -83,8 +83,8 @@ class DownloadableItemQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\
$this->expectedField( 'downloadId', $item['download_id'] ),
$this->expectedField(
'downloadsRemaining',
isset( $item['downloads_remaining'] ) && 'integer' === gettype( $item['downloads_remaining'] )
? $item['downloads_remaining']
isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: static::IS_NULL
),
$this->expectedField( 'name', $item['download_name'] ),
@@ -360,8 +360,8 @@ class DownloadableItemQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\
$this->expectedField( 'downloadId', $item['download_id'] ),
$this->expectedField(
'downloadsRemaining',
isset( $item['downloads_remaining'] ) && 'integer' === gettype( $item['downloads_remaining'] )
? $item['downloads_remaining']
isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: static::IS_NULL
),
$this->expectedField( 'name', $item['download_name'] ),
@@ -376,4 +376,78 @@ class DownloadableItemQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\
$this->assertQuerySuccessful( $response, $expected );
}
public function testDownloadsRemainingHandlesNumericString() {
$downloadable_product = $this->factory->product->createSimple(
[
'download_limit' => 5,
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$order_id = $this->factory->order->createNew(
[
'status' => 'completed',
'customer_id' => $this->customer,
],
[
'line_items' => [
[
'product' => $downloadable_product,
'qty' => 1,
],
],
]
);
wc_downloadable_product_permissions( $order_id, true );
// Force downloads_remaining to a numeric string as WooCommerce sometimes stores it.
global $wpdb;
$wpdb->update(
"{$wpdb->prefix}woocommerce_downloadable_product_permissions",
[ 'downloads_remaining' => '3' ],
[
'order_id' => $order_id,
'product_id' => $downloadable_product,
]
);
$this->loginAsCustomer();
$query = '
query {
customer {
orders {
nodes {
downloadableItems(first: 1) {
nodes {
downloadsRemaining
}
}
}
}
}
}
';
$response = $this->graphql( compact( 'query' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[
$this->expectedField( 'downloadsRemaining', 3 ),
]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
}
}