Skip to content
Open
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
45 changes: 39 additions & 6 deletions includes/class-donations.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ public static function get_donation_product_child_products_ids() {
* @return boolean True if a donation product, false if not.
*/
public static function is_donation_product( $product_id ) {
// Check the meta flag first (fast path).
if ( function_exists( 'wc_bool_to_string' ) && get_post_meta( $product_id, WooCommerce_Products::DONATION_FLAG_META_KEY, true ) === wc_bool_to_string( true ) ) {
return true;
}

// Fall back to the legacy parent/child donation product check.
$parent_product = self::get_parent_donation_product();
if ( ! $parent_product ) {
return false;
Expand All @@ -286,6 +292,37 @@ public static function is_donation_product( $product_id ) {
return in_array( $product_id, $donation_product_ids, true ) || $product_id === $parent_product->get_id();
}

/**
* Get IDs of all products flagged as donations via the _newspack_is_donation meta.
*
* @return int[] Array of product IDs.
*/
public static function get_flagged_donation_product_ids() {
static $memo = null;
if ( null !== $memo ) {
return $memo;
}
if ( ! function_exists( 'wc_bool_to_string' ) ) {
return [];
}
$flagged_products = get_posts(
[
'post_type' => 'product',
'post_status' => 'any',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
[
'key' => WooCommerce_Products::DONATION_FLAG_META_KEY,
'value' => wc_bool_to_string( true ),
],
],
]
);
$memo = array_map( 'intval', $flagged_products );
return $memo;
}

/**
* Whether the order is a donation.
*
Expand All @@ -309,7 +346,7 @@ public static function is_donation_order( $order ) {
* @return int|false The donation product ID or false.
*/
public static function get_order_donation_product_id( $order_id ) {
$donation_products = self::get_donation_product_child_products_ids();
$donation_products = array_merge( self::get_donation_product_child_products_ids(), self::get_flagged_donation_product_ids() );
if ( empty( array_filter( $donation_products ) ) ) {
return;
}
Expand Down Expand Up @@ -1051,15 +1088,11 @@ public static function is_donation_cart() {
if ( ! self::is_platform_wc() ) {
return false;
}
$donation_products_ids = array_values( self::get_donation_product_child_products_ids() );
if ( empty( $donation_products_ids ) ) {
return false;
}
if ( ! WC()->cart || ! WC()->cart->cart_contents || ! is_array( WC()->cart->cart_contents ) ) {
return false;
}
foreach ( WC()->cart->cart_contents as $prod_in_cart ) {
if ( isset( $prod_in_cart['product_id'] ) && in_array( $prod_in_cart['product_id'], $donation_products_ids ) ) {
if ( isset( $prod_in_cart['product_id'] ) && self::is_donation_product( $prod_in_cart['product_id'] ) ) {
return true;
}
}
Expand Down
4 changes: 3 additions & 1 deletion includes/contribution-meter/class-contribution-meter.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,11 @@ public static function get_donation_revenue( $start_date, $end_date ) {
return new \WP_Error( 'woocommerce_inactive', __( 'WooCommerce is not active.', 'newspack-plugin' ) );
}

// Get all donation product IDs.
// Get all donation product IDs (default + flagged).
$donation_products = Donations::get_donation_product_child_products_ids();
$donation_product_ids = array_filter( array_map( 'intval', array_values( $donation_products ) ) );
$flagged_product_ids = Donations::get_flagged_donation_product_ids();
$donation_product_ids = array_unique( array_merge( $donation_product_ids, $flagged_product_ids ) );

if ( empty( $donation_product_ids ) ) {
return new \WP_Error( 'no_donation_products', __( 'No donation products found.', 'newspack-plugin' ) );
Expand Down
11 changes: 11 additions & 0 deletions includes/plugins/woocommerce/class-woocommerce-products.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* Connection with WooCommerce's features.
*/
class WooCommerce_Products {

const DONATION_FLAG_META_KEY = '_newspack_is_donation';
/**
* Initialize.
*
Expand Down Expand Up @@ -70,6 +72,15 @@ public static function get_custom_options() {
'product_types' => [ 'simple', 'variation', 'subscription', 'subscription_variation' ],
'type' => 'boolean',
],
'newspack_is_donation' => [
'id' => self::DONATION_FLAG_META_KEY,
'wrapper_class' => '',
'label' => __( 'Donation product', 'newspack-plugin' ),
'description' => __( 'Flag this product as a donation. Donation products use donation-specific checkout, reporting, and reader activation behaviors.', 'newspack-plugin' ),
'default' => 'no',
'product_types' => [ 'simple', 'subscription', 'grouped', 'variable', 'variation', 'subscription_variation', 'variable-subscription' ],
'type' => 'boolean',
],
];

/**
Expand Down
61 changes: 61 additions & 0 deletions tests/unit-tests/donations.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use Newspack\Donations;
use Newspack\WooCommerce_Products;

require_once __DIR__ . '/../mocks/wc-mocks.php';

Expand All @@ -28,4 +29,64 @@ public function test_donations_settings_wc() {
'WC is the default donations platform.'
);
}

/**
* Test that is_donation_product returns false for unflagged products.
*
* @group donations
*/
public function test_is_donation_product_unflagged() {
$product_id = self::factory()->post->create( [ 'post_type' => 'product' ] );
self::assertFalse(
Donations::is_donation_product( $product_id ),
'Unflagged product should not be a donation product.'
);
}

/**
* Test that is_donation_product returns true for products with _newspack_is_donation meta.
*
* @group donations
*/
public function test_is_donation_product_flagged() {
$product_id = self::factory()->post->create( [ 'post_type' => 'product' ] );
update_post_meta( $product_id, WooCommerce_Products::DONATION_FLAG_META_KEY, wc_bool_to_string( true ) );
self::assertTrue(
Donations::is_donation_product( $product_id ),
'Flagged product should be a donation product.'
);
}

/**
* Test that is_donation_product returns false when meta is removed.
*
* @group donations
*/
public function test_is_donation_product_unflagged_after_removal() {
$product_id = self::factory()->post->create( [ 'post_type' => 'product' ] );
update_post_meta( $product_id, WooCommerce_Products::DONATION_FLAG_META_KEY, wc_bool_to_string( true ) );
delete_post_meta( $product_id, WooCommerce_Products::DONATION_FLAG_META_KEY );
self::assertFalse(
Donations::is_donation_product( $product_id ),
'Product should not be a donation product after meta removal.'
);
}

/**
* Test get_flagged_donation_product_ids returns flagged product IDs.
*
* @group donations
*/
public function test_get_flagged_donation_product_ids() {
$product_1 = self::factory()->post->create( [ 'post_type' => 'product' ] );
$product_2 = self::factory()->post->create( [ 'post_type' => 'product' ] );
$product_3 = self::factory()->post->create( [ 'post_type' => 'product' ] );
update_post_meta( $product_1, WooCommerce_Products::DONATION_FLAG_META_KEY, wc_bool_to_string( true ) );
update_post_meta( $product_3, WooCommerce_Products::DONATION_FLAG_META_KEY, wc_bool_to_string( true ) );

$flagged_ids = Donations::get_flagged_donation_product_ids();
self::assertContains( $product_1, $flagged_ids, 'Flagged product 1 should be in the list.' );
self::assertNotContains( $product_2, $flagged_ids, 'Unflagged product 2 should not be in the list.' );
self::assertContains( $product_3, $flagged_ids, 'Flagged product 3 should be in the list.' );
}
}
Loading