e will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping_tax' => [ 'description' => __( 'Total tax on shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_price' => [ 'description' => __( 'Total price the customer will pay.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax applied to items and shipping.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'tax_lines' => [ 'description' => __( 'Lines of taxes applied to items and shipping.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The name of the tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'The amount of tax charged.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'rate' => [ 'description' => __( 'The rate at which tax is applied.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ] ), ], 'errors' => [ 'description' => __( 'List of cart item errors, for example, items in the cart which are out of stock.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->error_schema->get_properties() ), ], ], 'payment_requirements' => [ 'description' => __( 'List of required payment gateway features to process the order.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a woo cart into an object suitable for the response. * * @param \WC_Cart $cart Cart class instance. * @return array */ public function get_item_response( $cart ) { $controller = new CartController(); // Get cart errors first so if recalculations are performed, it's reflected in the response. $cart_errors = $this->get_cart_errors( $cart ); // The core cart class will not include shipping in the cart totals if `show_shipping()` returns false. This can // happen if an address is required, or through the use of hooks. This tracks if shipping has actually been // calculated so we can avoid returning costs and rates prematurely. $has_calculated_shipping = $cart->show_shipping(); // Get shipping packages to return in the response from the cart. $shipping_packages = $has_calculated_shipping ? $controller->get_shipping_packages() : []; // Get visible cross sells products. $cross_sells = array_filter( array_map( 'wc_get_product', $cart->get_cross_sells() ), 'wc_products_array_filter_visible' ); return [ 'coupons' => $this->get_item_responses_from_schema( $this->coupon_schema, $cart->get_applied_coupons() ), 'shipping_rates' => $this->get_item_responses_from_schema( $this->shipping_rate_schema, $shipping_packages ), 'shipping_address' => $this->shipping_address_schema->get_item_response( wc()->customer ), 'billing_address' => $this->billing_address_schema->get_item_response( wc()->customer ), 'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart->get_cart() ), 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), 'cross_sells' => $this->get_item_responses_from_schema( $this->cross_sells_item_schema, $cross_sells ), 'needs_payment' => $cart->needs_payment(), 'needs_shipping' => $cart->needs_shipping(), 'has_calculated_shipping' => $has_calculated_shipping, 'fees' => $this->get_item_responses_from_schema( $this->fee_schema, $cart->get_fees() ), 'totals' => (object) $this->prepare_currency_response( [ 'total_items' => $this->prepare_money_response( $cart->get_subtotal(), wc_get_price_decimals() ), 'total_items_tax' => $this->prepare_money_response( $cart->get_subtotal_tax(), wc_get_price_decimals() ), 'total_fees' => $this->prepare_money_response( $cart->get_fee_total(), wc_get_price_decimals() ), 'total_fees_tax' => $this->prepare_money_response( $cart->get_fee_tax(), wc_get_price_decimals() ), 'total_discount' => $this->prepare_money_response( $cart->get_discount_total(), wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( $cart->get_discount_tax(), wc_get_price_decimals() ), 'total_shipping' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_total(), wc_get_price_decimals() ) : null, 'total_shipping_tax' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_tax(), wc_get_price_decimals() ) : null, // Explicitly request context='edit'; default ('view') will render total as markup. 'total_price' => $this->prepare_money_response( $cart->get_total( 'edit' ), wc_get_price_decimals() ), 'total_tax' => $this->prepare_money_response( $cart->get_total_tax(), wc_get_price_decimals() ), 'tax_lines' => $this->get_tax_lines( $cart ), ] ), 'errors' => $cart_errors, 'payment_requirements' => $this->extend->get_payment_requirements(), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * Get tax lines from the cart and format to match schema. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_tax_lines( $cart ) { $tax_lines = []; if ( 'itemized' !== get_option( 'woocommerce_tax_total_display' ) ) { return $tax_lines; } $cart_tax_totals = $cart->get_tax_totals(); foreach ( $cart_tax_totals as $cart_tax_total ) { $tax_lines[] = array( 'name' => $cart_tax_total->label, 'price' => $this->prepare_money_response( $cart_tax_total->amount, wc_get_price_decimals() ), 'rate' => WC_Tax::get_rate_percent( $cart_tax_total->tax_rate_id ), ); } return $tax_lines; } /** * Get cart validation errors. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_cart_errors( $cart ) { $controller = new CartController(); $errors = $controller->get_cart_errors(); $cart_errors = []; foreach ( (array) $errors->errors as $code => $messages ) { foreach ( (array) $messages as $message ) { $cart_errors[] = new \WP_Error( $code, $message, $errors->get_error_data( $code ) ); } } return array_values( array_map( [ $this->error_schema, 'get_item_response' ], $cart_errors ) ); } } o build query used to fetch data from source meta table. * * @param array $entity_ids List of entity IDs to build meta query for. * * @return string Query that can be used to fetch data. */ private function build_meta_table_query( array $entity_ids ): string { global $wpdb; $source_meta_table = $this->schema_config['source']['meta']['table_name']; $source_meta_key_column = $this->schema_config['source']['meta']['meta_key_column']; $source_meta_value_column = $this->schema_config['source']['meta']['meta_value_column']; $source_entity_id_column = $this->schema_config['source']['meta']['entity_id_column']; $order_by = "source.$source_entity_id_column ASC"; $where_clause = "source.`$source_entity_id_column` IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; $entity_table = $this->schema_config['source']['entity']['table_name']; $entity_id_column = $this->schema_config['source']['entity']['id_column']; $entity_meta_id_mapping_column = $this->schema_config['source']['entity']['source_id_column']; if ( $this->schema_config['source']['excluded_keys'] ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_key_column is escaped for backticks, $key_placeholder is hardcoded. $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); $where_clause = "$where_clause AND $exclude_clause"; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return $wpdb->prepare( " SELECT source.`$source_entity_id_column` as source_entity_id, entity.`$entity_id_column` as entity_id, source.`$source_meta_key_column` as meta_key, source.`$source_meta_value_column` as meta_value FROM `$source_meta_table` source JOIN `$entity_table` entity ON entity.`$entity_meta_id_mapping_column` = source.`$source_entity_id_column` WHERE $where_clause ORDER BY $order_by ", $entity_ids ); // phpcs:enable } }