Automattic/woocommerce-subscriptions-core

PHP Fatal error in cron when $subscription_ids is an empty array.

Closed this issue · 1 comments

I ran into an issue when updating an old subscription where the $subscription_ids variable turned out to contain an empty array. Which triggers a PHP Fatal error that prevents wp cron from finishing the rest of the queue of subscriptions.

PHP Fatal error: Uncaught TypeError: rsort(): Argument #1 ($array) must be of type array, bool given in /mnt/persist/www/arbetaren.se/release/web/app/plugins/woocommerce-subscriptions/vendor/woocommerce/subscriptions-core/includes/data-stores/class-wcs-customer-store-cached-cpt.php:117

Perhaps error handling with empty would be in order here to ensure wp cron doesn't abort?

if ( ! empty( $subscription_ids ) ) {
	rsort( $subscription_ids );
}

Im getting a similar error on'

PHP Fatal error: Uncaught TypeError: array_sum(): Argument #1 ($array) must be of type array, null given in /wp-content/plugins/woocommerce-subscriptions/vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php:449

Adding an extra check in the "get_cart_item_from_session" function (@class-wcs-cart-renewal.php) to see if the line item ID exists in the subscription items array seems to fix this too. Checks added are commented with HOTFIX:

	/**
	 * Restore renewal flag when cart is reset and modify Product object with renewal order related info
	 *
	 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
	 */

	 public function get_cart_item_from_session( $cart_item_session_data, $cart_item, $key ) {

		if ( $this->should_honor_subscription_prices( $cart_item ) ) {
			$cart_item_session_data[ $this->cart_item_key ] = $cart_item[ $this->cart_item_key ];

			$_product = $cart_item_session_data['data'];

			// Need to get the original subscription or order price, not the current price
			$subscription = $this->get_order( $cart_item );

			if ( $subscription ) {
				$subscription_items = $subscription->get_items();

				// HOTFIX - Check if the line item ID exists in the subscription items array to prevent a fatal error from wp core
				if ( isset( $cart_item_session_data[ $this->cart_item_key ]['line_item_id'] ) && isset( $subscription_items[ $cart_item_session_data[ $this->cart_item_key ]['line_item_id'] ] ) ) {
					$item_to_renew = $subscription_items[ $cart_item_session_data[ $this->cart_item_key ]['line_item_id'] ];

					$price = $item_to_renew['line_subtotal'];

					if ( $_product->is_taxable() && $subscription->get_prices_include_tax() ) {

						// If this item's subtracted tax data hasn't been repaired, do that now.
						if ( isset( $item_to_renew['_subtracted_base_location_tax'] ) ) {
							WC_Subscriptions_Upgrader::repair_subtracted_base_taxes( $item_to_renew->get_id() );

							// The item has been updated so get a refreshed version of the item.
							$item_to_renew = WC_Order_Factory::get_order_item( $item_to_renew->get_id() );
						}

						if ( isset( $item_to_renew['_subtracted_base_location_taxes'] ) ) {
							$price += array_sum( $item_to_renew['_subtracted_base_location_taxes'] ) * $item_to_renew['qty'];
						} else {
							// HOTFIX - Check if the taxes array and subtotal key exist before accessing them to prevent fatal errors from wp core
							if ( isset( $item_to_renew['taxes']['subtotal'] ) ) {
								$price += array_sum( $item_to_renew['taxes']['subtotal'] ); // Use the taxes array items here as they contain taxes to a more accurate number of decimals.
							}
						}
					}

					// In rare cases quantity can be zero. Check first to prevent triggering a fatal error in php8+
					if ( isset( $item_to_renew['qty'] ) && 0 !== (int) $item_to_renew['qty'] ) {
						$_product->set_price( $price / $item_to_renew['qty'] );
					}

					// Don't carry over any sign up fee
					wcs_set_objects_property( $_product, 'subscription_sign_up_fee', 0, 'set_prop_only' );

					// Allow plugins to add additional strings to the product name for renewals
					$line_item_name = is_callable( $item_to_renew, 'get_name' ) ? $item_to_renew->get_name() : $item_to_renew['name'];
					wcs_set_objects_property( $_product, 'name', apply_filters( 'woocommerce_subscriptions_renewal_product_title', $line_item_name, $_product ), 'set_prop_only' );

					// Make sure the same quantity is renewed
					$cart_item_session_data['quantity'] = $item_to_renew['qty'];
				}
			}
		}

		return $cart_item_session_data;
	}