Skip to content

Commit e14e712

Browse files
authored
Merge pull request #439 from kidunot89/feature/new-cart-mutations
New cart mutations and cart bugfixes.
2 parents b0d8df0 + 6c633be commit e14e712

29 files changed

+1269
-523
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"stripe/stripe-php": "^7.28",
4141
"wp-coding-standards/wpcs": "^2.3",
4242
"johnpbloch/wordpress": "~5.3",
43-
"wp-graphql/wp-graphql": "^1.1.2",
43+
"wp-graphql/wp-graphql": "^1.1.4",
4444
"wp-graphql/wp-graphql-jwt-authentication": "^0.4.1",
4545
"wpackagist-plugin/woocommerce": "^4.4",
4646
"wpackagist-plugin/woocommerce-gateway-stripe": "^4.5",
@@ -79,7 +79,7 @@
7979
},
8080
"scripts": {
8181
"installTestEnv": "bash bin/install-test-env.sh",
82-
"runWPUnitTest": "vendor/bin/codept run wpunit",
82+
"runWPUnitTest": "vendor/bin/codecept run wpunit",
8383
"dBuild": "env $(sed -e '/^#/d' .env.dist) docker-compose build",
8484
"dRunApp": "env $(sed -e '/^#/d' .env.dist) docker-compose up testable_app app_db mailhog",
8585
"dRunTestingDb":"if [ ! \"$(docker ps -a | grep testing_db)\" ]; then env $(sed -e '/^#/d' .env.dist) docker-compose up -d testing_db; fi",

includes/class-type-registry.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
4141
\WPGraphQL\WooCommerce\Type\WPEnum\Products_Orderby_Enum::register();
4242
\WPGraphQL\WooCommerce\Type\WPEnum\Orders_Orderby_Enum::register();
4343
\WPGraphQL\WooCommerce\Type\WPEnum\Id_Type_Enums::register();
44+
\WPGraphQL\WooCommerce\Type\WPEnum\Cart_Error_Type::register();
4445

4546
// InputObjects.
47+
\WPGraphQL\WooCommerce\Type\WPInputObject\Cart_Item_Input::register();
4648
\WPGraphQL\WooCommerce\Type\WPInputObject\Customer_Address_Input::register();
4749
\WPGraphQL\WooCommerce\Type\WPInputObject\Product_Attribute_Input::register();
4850
\WPGraphQL\WooCommerce\Type\WPInputObject\Tax_Rate_Connection_Orderby_Input::register();
@@ -59,6 +61,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
5961
// Interfaces.
6062
\WPGraphQL\WooCommerce\Type\WPInterface\Product::register_interface( $type_registry );
6163
\WPGraphQL\WooCommerce\Type\WPInterface\Product_Attribute::register_interface( $type_registry );
64+
\WPGraphQL\WooCommerce\Type\WPInterface\Cart_Error::register_interface( $type_registry );
6265

6366
// Objects.
6467
\WPGraphQL\WooCommerce\Type\WPObject\Coupon_Type::register();
@@ -80,6 +83,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
8083
\WPGraphQL\WooCommerce\Type\WPObject\Meta_Data_Type::register();
8184
\WPGraphQL\WooCommerce\Type\WPObject\Shipping_Package_Type::register();
8285
\WPGraphQL\WooCommerce\Type\WPObject\Shipping_Rate_Type::register();
86+
\WPGraphQL\WooCommerce\Type\WPObject\Cart_Error_Types::register();
8387

8488
// Object fields.
8589
\WPGraphQL\WooCommerce\Type\WPObject\Product_Category_Type::register_fields();
@@ -107,6 +111,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
107111
\WPGraphQL\WooCommerce\Mutation\Customer_Register::register_mutation();
108112
\WPGraphQL\WooCommerce\Mutation\Customer_Update::register_mutation();
109113
\WPGraphQL\WooCommerce\Mutation\Cart_Add_Item::register_mutation();
114+
\WPGraphQL\WooCommerce\Mutation\Cart_Add_Items::register_mutation();
110115
\WPGraphQL\WooCommerce\Mutation\Cart_Update_Item_Quantities::register_mutation();
111116
\WPGraphQL\WooCommerce\Mutation\Cart_Remove_Items::register_mutation();
112117
\WPGraphQL\WooCommerce\Mutation\Cart_Restore_Items::register_mutation();
@@ -115,6 +120,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
115120
\WPGraphQL\WooCommerce\Mutation\Cart_Remove_Coupons::register_mutation();
116121
\WPGraphQL\WooCommerce\Mutation\Cart_Add_Fee::register_mutation();
117122
\WPGraphQL\WooCommerce\Mutation\Cart_Update_Shipping_Method::register_mutation();
123+
\WPGraphQL\WooCommerce\Mutation\Cart_Fill::register_mutation();
118124
\WPGraphQL\WooCommerce\Mutation\Order_Create::register_mutation();
119125
\WPGraphQL\WooCommerce\Mutation\Order_Update::register_mutation();
120126
\WPGraphQL\WooCommerce\Mutation\Order_Delete::register_mutation();

includes/connection/class-coupons.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@ class Coupons {
2424
public static function register_connections() {
2525
// From RootQuery.
2626
register_graphql_connection( self::get_connection_config() );
27-
28-
// From Cart.
29-
register_graphql_connection(
30-
self::get_connection_config(
31-
array(
32-
'fromType' => 'Cart',
33-
'fromFieldName' => 'appliedCoupons',
34-
)
35-
)
36-
);
3727
}
3828

3929
/**

includes/data/connection/class-coupon-connection-resolver.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ public function get_node_by_id( $id ) {
7979
public function should_execute() {
8080
$post_type_obj = get_post_type_object( 'shop_coupon' );
8181
switch ( true ) {
82-
case 'appliedCoupons' === $this->info->fieldName:
8382
case current_user_can( $post_type_obj->cap->edit_posts ):
8483
return true;
8584
default:
@@ -145,19 +144,6 @@ public function get_query_args() {
145144
$query_args['order'] = ! empty( $last ) ? 'ASC' : 'DESC';
146145
}
147146

148-
if ( is_a( $this->source, \WC_Cart::class ) ) {
149-
// @codingStandardsIgnoreLine
150-
switch( $this->info->fieldName ) {
151-
case 'appliedCoupons':
152-
$ids = array();
153-
foreach ( $this->source->get_applied_coupons() as $code ) {
154-
$ids[] = \wc_get_coupon_id_by_code( $code );
155-
}
156-
$query_args['post__in'] = ! empty( $ids ) ? $ids : array( '0' );
157-
break;
158-
}
159-
}
160-
161147
/**
162148
* Filter the $query args to allow folks to customize queries programmatically
163149
*

includes/data/mutation/class-cart-mutation.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static function get_cart_field( $fallback = false ) {
2626
'type' => 'Cart',
2727
'resolve' => function ( $payload ) use ( $fallback ) {
2828
$cart = ! empty( $payload['cart'] ) ? $payload['cart'] : null;
29+
2930
if ( is_null( $cart ) && $fallback ) {
3031
$cart = Factory::resolve_cart();
3132
}
@@ -41,9 +42,19 @@ public static function get_cart_field( $fallback = false ) {
4142
* @param AppContext $context AppContext instance.
4243
* @param ResolveInfo $info Query info.
4344
*
45+
* @throws UserError Missing/Invalid input.
46+
*
4447
* @return array
4548
*/
4649
public static function prepare_cart_item( $input, $context, $info ) {
50+
if ( empty( $input['productId'] ) ) {
51+
throw new UserError( __( 'No product ID provided', 'wp-graphql-woocommerce' ) );
52+
}
53+
54+
if ( ! \wc_get_product( $input['productId'] ) ) {
55+
throw new UserError( __( 'No product found matching the ID provided', 'wp-graphql-woocommerce' ) );
56+
}
57+
4758
$cart_item_args = array( $input['productId'] );
4859
$cart_item_args[] = ! empty( $input['quantity'] ) ? $input['quantity'] : 1;
4960
$cart_item_args[] = ! empty( $input['variationId'] ) ? $input['variationId'] : 0;
@@ -145,6 +156,120 @@ public static function prepare_cart_fee( $input, $context, $info ) {
145156
return apply_filters( 'graphql_woocommerce_new_cart_fee_data', $cart_item_args, $input, $context, $info );
146157
}
147158

159+
160+
/**
161+
* Validates coupon and checks if application is possible
162+
*
163+
* @param string $code Coupon code.
164+
* @param string $reason Reason for failure.
165+
*
166+
* @return bool
167+
*/
168+
public static function validate_coupon( $code, &$reason = '' ) {
169+
// Get the coupon.
170+
$the_coupon = new \WC_Coupon( $code );
171+
172+
// Prevent adding coupons by post ID.
173+
if ( $the_coupon->get_code() !== $code ) {
174+
$reason = __( 'No coupon found with the code provided', 'wp-graphql-woocommerce' );
175+
return false;
176+
}
177+
178+
// Check it can be used with cart.
179+
if ( ! $the_coupon->is_valid() ) {
180+
$reason = $the_coupon->get_error_message();
181+
return false;
182+
}
183+
184+
// Check if applied.
185+
if ( \WC()->cart->has_discount( $code ) ) {
186+
$reason = __( 'This coupon has already been applied to the cart', 'wp-graphql-woocommerce' );
187+
return false;
188+
}
189+
190+
return true;
191+
}
192+
193+
/**
194+
* Validates shipping method by checking comparing against shipping package.
195+
*
196+
* @param string $shipping_method Shipping method being validated.
197+
* @param integer $index Index of the shipping package.
198+
* @param string $reason Reason for failure.
199+
*
200+
* @return bool
201+
*/
202+
public static function validate_shipping_method( $shipping_method, $index, &$reason = '' ) {
203+
// Get available shipping packages.
204+
$available_packages = \WC()->cart->needs_shipping()
205+
? \WC()->shipping()->calculate_shipping( \WC()->cart->get_shipping_packages() )
206+
: array();
207+
208+
if ( ! isset( $available_packages[ $index ] ) ) {
209+
$reason = sprintf(
210+
/* translators: %d: Package index */
211+
__( 'No shipping packages available for corresponding index %d', 'wp-graphql-woocommerce' ),
212+
$index
213+
);
214+
215+
return false;
216+
}
217+
218+
$package = $available_packages[ $index ];
219+
$chosen_rate_index = array_search( $shipping_method, wp_list_pluck( $package['rates'], 'id' ), true );
220+
221+
if ( false !== $chosen_rate_index ) {
222+
return true;
223+
}
224+
225+
$product_names = array();
226+
foreach ( $package['contents'] as $item_id => $values ) {
227+
$product_names[ $item_id ] = \html_entity_decode( $values['data']->get_name() . ' ×' . $values['quantity'] );
228+
}
229+
230+
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
231+
$product_names = apply_filters( 'woocommerce_shipping_package_details_array', $product_names, $package );
232+
233+
$reason = sprintf(
234+
/* translators: %1$s: shipping method ID, %2$s: package contents */
235+
__( '"%1$s" is not an available shipping method for shipping package "%2$s"', 'wp-graphql-woocommerce' ),
236+
$shipping_method,
237+
implode( ', ', $product_names )
238+
);
239+
240+
return false;
241+
}
242+
243+
/**
244+
* Validates and prepares posted shipping methods for the user session.
245+
*
246+
* @param array $posted_shipping_methods Chosen shipping methods.
247+
*
248+
* @throws UserError Invalid shipping method.
249+
*
250+
* @return array
251+
*/
252+
public static function prepare_shipping_methods( $posted_shipping_methods ) {
253+
// Get current shipping methods.
254+
$chosen_shipping_methods = \WC()->session->get( 'chosen_shipping_methods' );
255+
256+
// Update current shipping methods.
257+
foreach ( $posted_shipping_methods as $package => $chosen_method ) {
258+
if ( empty( $chosen_method ) ) {
259+
continue;
260+
}
261+
262+
$reason = '';
263+
if ( self::validate_shipping_method( $chosen_method, $package, $reason ) ) {
264+
$chosen_shipping_methods[ $package ] = $chosen_method;
265+
} else {
266+
throw new UserError( $reason );
267+
}
268+
}
269+
270+
return $chosen_shipping_methods;
271+
}
272+
148273
/**
149274
* Validate CartItemQuantityInput item.
150275
*

includes/mutation/class-cart-add-item.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,6 @@ public static function mutate_and_get_payload() {
9292
return function( $input, AppContext $context, ResolveInfo $info ) {
9393
Cart_Mutation::check_session_token();
9494

95-
// Retrieve product database ID if relay ID provided.
96-
if ( empty( $input['productId'] ) ) {
97-
throw new UserError( __( 'No product ID provided', 'wp-graphql-woocommerce' ) );
98-
}
99-
100-
if ( ! \wc_get_product( $input['productId'] ) ) {
101-
throw new UserError( __( 'No product found matching the ID provided', 'wp-graphql-woocommerce' ) );
102-
}
103-
10495
// Prepare args for "add_to_cart" from input data.
10596
$cart_item_args = Cart_Mutation::prepare_cart_item( $input, $context, $info );
10697

0 commit comments

Comments
 (0)