Skip to content

Commit

Permalink
Placemarker before Order_Mutation refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
kidunot89 committed Jul 3, 2019
1 parent 7086a45 commit fccf7b9
Show file tree
Hide file tree
Showing 14 changed files with 784 additions and 173 deletions.
41 changes: 41 additions & 0 deletions class-inflect.php
Expand Up @@ -187,4 +187,45 @@ public static function pluralize_if( $count, $string ) {

return $count . ' ' . self::pluralize( $string );
}

/**
* Converts a camel case formatted string to a underscore formatted string.
*
* @param string $string String to be formatted.
* @param boolean $capitalize Capitalize first letter of string.
*
* @return string
*/
public static function underscore_to_camel_case( $string, $capitalize = false ) {
$str = str_replace( ' ', '', ucwords( str_replace( '-', ' ', $string ) ) );

if ( ! $capitalize ) {
$str[0] = strtolower( $str[0] );
}

return $str;
}

/**
* Converts a camel case formatted string to a underscore formatted string.
*
* @param string $string String to be formatted.
*
* @return string
*/
public static function camel_case_to_underscore( $string ) {
preg_match_all(
'!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
$string,
$matches
);

$ret = $matches[0];

foreach ( $ret as &$match ) {
$match = strtoupper( $match ) === $match ? strtolower( $match ) : lcfirst( $match );
}

return implode( '_', $ret );
}
}
2 changes: 1 addition & 1 deletion includes/data/mutation/class-customer-mutation.php
Expand Up @@ -59,7 +59,7 @@ public static function prepare_customer_props( $input, $mutation ) {
*
* @return array;
*/
private function address_input_mapping( $type = 'billing', $input ) {
public static function address_input_mapping( $type = 'billing', $input ) {
// Map GQL input to address props array.
$key_mapping = array(
'firstName' => 'first_name',
Expand Down
266 changes: 254 additions & 12 deletions includes/data/mutation/class-order-mutation.php
Expand Up @@ -42,7 +42,7 @@ public static function prepare_props( $input, $context, $info ) {

// Input keys to be skipped.
$skipped_keys = apply_filters(
'woocommerce_new_order_s',
'woocommerce_order_mutation_skipped_props',
array(
'status',
'coupon',
Expand Down Expand Up @@ -81,18 +81,18 @@ public static function prepare_order_instance( $props, $context, $info ) {
break;
case 'billing':
case 'shipping':
self::update_address( $order, $value, $props );
self::update_address( $value, $order, $key );
break;
case 'line_items':
case 'shipping_lines':
case 'fee_lines':
if ( is_array( $value ) ) {
foreach ( $value as $item ) {
if ( is_array( $item ) ) {
if ( OrderMutation::item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
if ( self::item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
$order->remove_item( $item['id'] );
} else {
OrderMutation::set_item( $order, $props, $item );
self::set_item( $item, $order, $key );
}
}
}
Expand All @@ -116,9 +116,6 @@ public static function prepare_order_instance( $props, $context, $info ) {
/**
* Filters an object before it is inserted via the GraphQL API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Order $order WC_Order instance.
* @param array $props Order props array.
* @param AppContext $context Request AppContext instance.
Expand All @@ -130,13 +127,11 @@ public static function prepare_order_instance( $props, $context, $info ) {
/**
* Validates order customer
*
* @param array $input Input data describing order.
* @param AppContext $context AppContext instance.
* @param ResolveInfo $info ResolveInfo instance.
* @param array $input Input data describing order.
*
* @return bool
*/
public static function validate_customer( $input, $context, $info ) {
public static function validate_customer( $input ) {
if ( ! empty( $input['customerId'] ) ) {
// Make sure customer exists.
if ( false === get_user_by( 'id', $input['customerId'] ) ) {
Expand All @@ -153,13 +148,245 @@ public static function validate_customer( $input, $context, $info ) {
return false;
}

/**
* Update address.
*
* @param array $address Address data.
* @param WC_Order $order WC_Order instance.
* @param string $type Address type.
*/
protected static function update_address( $address, $order, $type = 'billing' ) {
$formatted_address = Customer_Mutation::address_input_mapping( $type, $address );
foreach ( $formatted_address as $key => $value ) {
if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
$order->{"set_{$type}_{$key}"}( $value );
}
}
}

/**
* Gets the product ID from the SKU or posted ID.
*
* @param array $data Line item data.
*
* @return int
*
* @throws UserError When SKU or ID is not valid.
*/
protected static function get_product_id( $data ) {
if ( ! empty( $data['sku'] ) ) {
$product_id = (int) \wc_get_product_id_by_sku( $data['sku'] );
} elseif ( ! empty( $data['productId'] ) && empty( $data['variationId'] ) ) {
$product_id = (int) $data['productId'];
} elseif ( ! empty( $data['variationId'] ) ) {
$product_id = (int) $data['variationId'];
} else {
throw new UserError( __( 'Product ID or SKU is required.', 'wp-graphql-woocommerce' ) );
}

return $product_id;
}

/**
* Wrapper method to create/update order items.
* When updating, the item ID provided is checked to ensure it is associated
* with the order.
*
* @param array $data Item data provided in the request body.
* @param WC_Order $order WC_Order instance.
* @param string $type The item type.
*
* @throws UserError If item ID is not associated with order.
*/
protected static function set_item( $data, $order, $type ) {
if ( ! empty( $data['id'] ) ) {
$action = 'update';
} else {
$action = 'create';
}

$method = 'prepare_' . $type;
$item = null;

// Verify provided line item ID is associated with order.
if ( 'update' === $action ) {
$item = $order->get_item( absint( $data['id'] ) );
if ( ! $item ) {
throw new UserError( __( 'Order item ID provided is not associated with order.', 'wp-graphql-woocommerce' ) );
}
}

// Prepare item data.
$item = self::{$method}( $data, $action, $item );

do_action( 'woocommerce_graphql_set_order_item', $item, $data );

// If creating the order, add the item to it.
if ( 'create' === $action ) {
$item->apply_changes();
$order->add_item( $item );
} else {
$item->save();
}
}

/**
* Create or update a line item.
*
* @param array $data Line item data.
* @param string $action 'create' to add line item or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
*
* @return WC_Order_Item_Product
*/
protected static function prepare_line_items( $data, $action = 'create', $item = null ) {
$item = is_null( $item )
? new \WC_Order_Item_Product( ! empty( $data['id'] ) ? $data['id'] : 0 )
: $item;

$product = \wc_get_product( self::get_product_id( $data ) );

if ( $product !== $item->get_product() ) {
$item->set_product( $product );
if ( 'create' === $action ) {
$quantity = isset( $data['quantity'] ) ? $data['quantity'] : 1;
$total = \wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$item->set_total( $total );
$item->set_subtotal( $total );
}
}

self::maybe_set_item_props( $item, array( 'name', 'quantity', 'total', 'subtotal', 'taxClass' ), $data );
self::maybe_set_item_meta_data( $item, $data );

return $item;
}

/**
* Create or update an order shipping method.
*
* @param array $data Shipping Item data.
* @param string $action 'create' to add shipping or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
*
* @return WC_Order_Item_Shipping
*
* @throws UserError Invalid data, server error.
*/
protected static function prepare_shipping_lines( $data, $action = 'create', $item = null ) {
$item = is_null( $item )
? new \WC_Order_Item_Shipping( ! empty( $data['id'] ) ? $data['id'] : '' )
: $item;

if ( 'create' === $action ) {
if ( empty( $data['methodId'] ) ) {
throw new UserError( __( 'Shipping method ID is required.', 'wp-graphql-woocommerce' ) );
}
}

self::maybe_set_item_props( $item, array( 'methodId', 'methodTitle', 'total' ), $data );
self::maybe_set_item_meta_data( $item, $data );

return $item;
}

/**
* Create or update an order fee.
*
* @param array $data Item data.
* @param string $action 'create' to add fee or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
*
* @return WC_Order_Item_Fee
*
* @throws UserError Invalid data, server error.
*/
protected static function prepare_fee_lines( $data, $action = 'create', $item = null ) {
$item = is_null( $item )
? new \WC_Order_Item_Fee( ! empty( $data['id'] ) ? $data['id'] : '' )
: $item;

if ( 'create' === $action ) {
if ( empty( $data['name'] ) ) {
throw new UserError( __( 'Fee name is required.', 'woocommerce' ) );
}
}

self::maybe_set_item_props( $item, array( 'name', 'taxClass', 'taxStatus', 'total' ), $data );
self::maybe_set_item_meta_data( $item, $data );

return $item;
}

/**
* Maybe set an item prop if the value was posted.
*
* @param WC_Order_Item $item Order item.
* @param string $prop Order property.
* @param array $data Request data.
*/
protected static function maybe_set_item_prop( $item, $prop, $data ) {
if ( isset( $data[ $prop ] ) ) {
$key = \Inflect::camel_case_to_underscore( $prop );
$item->{"set_{$key}"}( $data[ $prop ] );
}
}

/**
* Maybe set item props if the values were posted.
*
* @param WC_Order_Item $item Order item data.
* @param string[] $props Target properties.
* @param array $data Prop data.
*/
protected static function maybe_set_item_props( $item, $props, $data ) {
foreach ( $props as $prop ) {
self::maybe_set_item_prop( $item, $prop, $data );
}
}

/**
* Maybe set item meta if posted.
*
* @param WC_Order_Item $item Order item data.
*
* @param array $data Request data.
*/
protected static function maybe_set_item_meta_data( $item, $data ) {
if ( ! empty( $data['metaData'] ) && is_array( $data['metaData'] ) ) {
foreach ( $data['metaData'] as $meta ) {
if ( isset( $meta['key'] ) ) {
$value = isset( $meta['value'] ) ? $meta['value'] : null;
$item->update_meta_data( $meta['key'], $value, isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
}
}

/**
* Helper method to check if the resource ID associated with the provided item is null.
* Items can be deleted by setting the resource ID to null.
*
* @param array $item Item provided in the request body.
* @return bool True if the item resource ID is null, false otherwise.
*/
protected static function item_is_null( $item ) {
$keys = array( 'productId', 'methodId', 'methodTitle', 'name', 'code' );
foreach ( $keys as $key ) {
if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
return true;
}
}
return false;
}

/**
* Applies coupons to WC_Order instance
*
* @param array $coupons Coupon codes to be applied to order.
* @param WC_Order $order WC_Order instance.
*/
public static function apply_coupons( $coupons, &$order ) {
public static function apply_coupons( $coupons, $order ) {
// Remove all coupons first to ensure calculation is correct.
foreach ( $order->get_items( 'coupon' ) as $coupon ) {
$order->remove_coupon( $coupon->get_code() );
Expand All @@ -172,4 +399,19 @@ public static function apply_coupons( $coupons, &$order ) {
}
}
}

/**
* Purge object when creating.
*
* @param WC_Order $order Object data.
*
* @return bool
*/
protected function purge( $order ) {
if ( $order instanceof WC_Order ) {
return $order->delete( true );
}

return false;
}
}
4 changes: 2 additions & 2 deletions includes/model/class-order-item.php
Expand Up @@ -124,7 +124,7 @@ protected function init() {
if ( $this->data->get_tax_status() === 'taxable' ) {
return ! empty( $this->data->get_tax_class() )
? $this->data->get_tax_class()
: 'standard';
: '';
}
return null;
},
Expand Down Expand Up @@ -239,7 +239,7 @@ protected function init() {
if ( $this->data->get_tax_status() === 'taxable' ) {
return ! empty( $this->data->get_tax_class() )
? $this->data->get_tax_class()
: 'standard';
: '';
}
return null;
},
Expand Down

0 comments on commit fccf7b9

Please sign in to comment.