Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v0.3.3 #216

Merged
merged 12 commits into from
Jan 14, 2020
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ branches:
- master
- release-v0.1.2
- release-v0.2.2
- release-v0.3.1
- release-v0.3.3

cache:
apt: true
Expand Down
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
## Quick Install
1. Install & activate [WooCommerce](https://woocommerce.com/)
2. Install & activate [WPGraphQL](https://www.wpgraphql.com/)
3. (Optional) Install & activate [WPGraphQL-JWT-Authentication](https://github.com/wp-graphql/wp-graphql-jwt-authentication) to add a `login` mutation that returns a JSON Web Token.
4. Clone or download the zip of this repository into your WordPress plugin directory & activate the **WP GraphQL WooCommerce** plugin
3. Clone or download the zip of this repository into your WordPress plugin directory & activate the **WP GraphQL WooCommerce** plugin.
4. (Optional) Install & activate [WPGraphQL-JWT-Authentication](https://github.com/wp-graphql/wp-graphql-jwt-authentication) to add a `login` mutation that returns a JSON Web Token.
5. (Optional) Install & activate [WPGraphQL-CORS](https://github.com/funkhaus/wp-graphql-cors) to add an extra layer of security using HTTP CORS and some of WPGraphQL advanced functionality.

## What does this plugin do?
It adds WooCommerce functionality to the WPGraphQL schema using WooCommerce's [CRUD](https://github.com/woocommerce/woocommerce/wiki/CRUD-Objects-in-3.0) objects.

## Features
- Query product, customers, coupons, order, refund, product variations with complex filtering options.
- Add items to cart and process user store session using HTTP header defined by WooGraphQL's built-in session handler
- Create/process user checkout with the `checkout` mutation.
- Query **product**, **product variations**, **customers**, **coupons**, **orders**, **refunds** and **more** with complex filtering options.
- Manipulate customer session data using customer and cart mutations while managing customer session token using HTTP headers or cookies *(not recommended)*. *[HTTP header example w/ React/Apollo](https://github.com/wp-graphql/wp-graphql-woocommerce/pull/88)*
- Create orders using the `order` mutations with the `checkout` mutation.

## Future Features
- Payment Processing
- Adminstrator mutations. Eg. Creating and deleting products, coupons, and refunds.

## Playground
Expand Down Expand Up @@ -84,14 +86,16 @@ If you use the command with at least a `suite` specified, **Codeception** will r

To learn more about the usage of Codeception with WordPress view the [Documentation](https://codeception.com/for/wordpress)

## Functional and Acceptance Tests (Docker/Docker-Compose required)
## Functional and Acceptance Tests (Docker && Docker-Compose required)
It's possible to run functional and acceptance tests, but is very limited at the moment. The script docker entrypoint script runs all three suites (acceptance, functional, and wpunit) at once. This will change eventually, however as of right now, this is the limitation.

### Running tests
Even though the two suite use a Docker environment to run, the docker environment relies on a few environmental variables defined in `.env.dist` and a volume source provided by the test install script and the configuration `codeception.dist.yml`. If you have created a `codeception.yml` file ensure it is identical to `codeception.dist.yml` or delete it.
Even though the two suites use a Docker environment to run, the `testing` service in the `docker.compose.yml` file requires the `.env.dist` and `codeception.dist.yml` untouched.
Run the following in the terminal to run all three suites. Isolating specific suites should be simple to figure out.
```
docker-compose run --rm -e SUITE=acceptance;wpunit;functional -e DEBUG=1 -e COVERAGE=1 testing --scale app=0
docker-compose run --rm -e \
SUITE=acceptance;wpunit;functional \
-e DEBUG=1 -e COVERAGE=1 testing --scale app=0
```
- The `COVERAGE`, and `DEBUG` vars are optional flags for toggle codecoverage and debug output.
- `--scale app=0` ensures that the service running a local app doesn't create any instances. It must be added or a collision with `mysql` will occur. More on this service in the next section
Expand All @@ -118,6 +122,4 @@ If you get HTTP 500 error upon activation or accessing the `endpoint` and have *

**GraphQL-PHP** :point_right: **[OpenCollective](https://opencollective.com/webonyx-graphql-php)**

## Follow
[![alt text](http://i.imgur.com/tXSoThF.png)](https://twitter.com/woographql)
[![alt text](http://i.imgur.com/P3YfQoD.png)](https://www.facebook.com/woographql)
## Follow [![alt text](http://i.imgur.com/tXSoThF.png)](https://twitter.com/woographql)[![alt text](http://i.imgur.com/P3YfQoD.png)](https://www.facebook.com/woographql)
2 changes: 1 addition & 1 deletion README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: GraphQL, WooCommerce, WPGraphQL
Requires at least: 4.9
Tested up to: 5.2
Requires PHP: 5.6
Stable tag: 0.3.2
Stable tag: 0.3.3
License: GPL-3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Maintained at: https://github.com/wp-graphql/wp-graphql-woocommerce
Expand Down
2 changes: 1 addition & 1 deletion includes/connection/class-cart-items.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static function get_connection_config( $args = array() ) {
*/
public static function get_connection_args() {
return array(
'needShipping' => array(
'needsShipping' => array(
'type' => 'Boolean',
'description' => __( 'Limit results to cart items that require shipping', 'wp-graphql-woocommerce' ),
),
Expand Down
53 changes: 38 additions & 15 deletions includes/data/connection/class-cart-item-connection-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
* Class Cart_Item_Connection_Resolver
*/
class Cart_Item_Connection_Resolver extends AbstractConnectionResolver {
/**
* Include shared connection functions.
*/
use WC_Connection_Functions;

/**
* Confirms if cart items should be retrieved.
*
Expand All @@ -30,23 +35,34 @@ public function should_execute() {
}

/**
* Creates query arguments array
* Creates cart item filters.
*
* @return array
*/
public function get_query_args() {
$query_args = array();
$query_args = array( 'filters' => array() );
if ( ! empty( $this->args['where'] ) ) {
$where_args = $this->args['where'];
if ( ! empty( $where_args['needShipping'] ) ) {
$query_args['filters'] = array();
$query_args['filters'][] = function( $cart_item ) {
if ( isset( $where_args['needsShipping'] ) ) {
$needs_shipping = $where_args['needsShipping'];
$query_args['filters'][] = function( $cart_item ) use ( $needs_shipping ) {
$product = \WC()->product_factory->get_product( $cart_item['product_id'] );
if ( $product ) {
return $product->needs_shipping();
}
return $needs_shipping === (bool) $product->needs_shipping();
};
}
}

/**
* Filter the $query_args to allow folks to customize queries programmatically.
*
* @param array $query_args The args that will be passed to the WP_Query.
* @param mixed $source The source that's passed down the GraphQL queries.
* @param array $args The inputArgs on the field.
* @param AppContext $context The AppContext passed down the GraphQL tree.
* @param ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*/
$query_args = apply_filters( 'graphql_cart_item_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );

return $query_args;
}

Expand All @@ -58,13 +74,9 @@ public function get_query_args() {
public function get_query() {
$cart_items = array_values( $this->source->get_cart() );

if ( ! empty( $this->query_args['filters'] ) ) {
if ( is_array( $this->query_args['filters'] ) ) {
foreach ( $this->query_args['filters'] as $filter ) {
$cart_items = array_filter( $cart_items, $filter );
}
} else {
$cart_items = array_filter( $cart_items, $this->query_args['filters'] );
if ( ! empty( $this->query_args['filters'] ) && is_array( $this->query_args['filters'] ) ) {
foreach ( $this->query_args['filters'] as $filter ) {
$cart_items = array_filter( $cart_items, $filter );
}
}

Expand Down Expand Up @@ -122,4 +134,15 @@ protected function get_cursor_for_node( $node, $key = null ) {
public function get_items() {
return ! empty( $this->query ) ? $this->query : array();
}

/**
* Wrapper for "WC_Connection_Functions::is_valid_cart_item_offset()"
*
* @param integer $offset Post ID.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return $this->is_valid_cart_item_offset( $offset );
}
}
20 changes: 17 additions & 3 deletions includes/data/connection/class-coupon-connection-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
* Class Coupon_Connection_Resolver
*/
class Coupon_Connection_Resolver extends AbstractConnectionResolver {
use Common_CPT_Input_Sanitize_Functions;
/**
* Include shared connection functions.
*/
use WC_Connection_Functions;

/**
* The name of the post type, or array of post types the connection resolver is resolving for
Expand Down Expand Up @@ -105,7 +108,7 @@ public function get_query_args() {
/**
* Collect the input_fields and sanitize them to prepare them for sending to the WP_Query
*/
$input_fields = [];
$input_fields = array();
if ( ! empty( $this->args['where'] ) ) {
$input_fields = $this->sanitize_input_fields( $this->args['where'] );
}
Expand Down Expand Up @@ -163,7 +166,7 @@ public function get_query() {
* @return array
*/
public function get_items() {
return ! empty( $this->query->posts ) ? $this->query->posts : [];
return ! empty( $this->query->posts ) ? $this->query->posts : array();
}

/**
Expand Down Expand Up @@ -222,4 +225,15 @@ public function sanitize_input_fields( array $where_args ) {

return $args;
}

/**
* Wrapper for "WC_Connection_Functions::is_valid_post_offset()"
*
* @param integer $offset Post ID.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return $this->is_valid_post_offset( $offset );
}
}
77 changes: 75 additions & 2 deletions includes/data/connection/class-customer-connection-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
* Class Customer_Connection_Resolver
*/
class Customer_Connection_Resolver extends AbstractConnectionResolver {
/**
* Include shared connection functions.
*/
use WC_Connection_Functions;

/**
* Confirms the uses has the privileges to query Customers
*
Expand All @@ -36,14 +41,26 @@ public function should_execute() {
* Creates query arguments array
*/
public function get_query_args() {
/**
* Prepare for later use
*/
$last = ! empty( $this->args['last'] ) ? $this->args['last'] : null;

/**
* Set the $query_args based on various defaults and primary input $args
*/
$query_args['count_total'] = false;
$query_args['offset'] = $this->get_offset();
$query_args['orderby'] = 'ID';
$query_args['order'] = ! empty( $this->args['last'] ) ? 'ASC' : 'DESC';
$query_args['number'] = $this->get_query_amount();
$query_args['number'] = $this->get_query_amount() + 1;

/**
* Set the graphql_cursor_offset which is used by Config::graphql_wp_user_query_cursor_pagination_support
* to filter the WP_User_Query to support cursor pagination
*/
$cursor_offset = $this->get_offset();
$query_args['graphql_cursor_offset'] = $cursor_offset;
$query_args['graphql_cursor_compare'] = ( ! empty( $last ) ) ? '>' : '<';

$input_fields = array();
if ( ! empty( $this->args['where'] ) ) {
Expand All @@ -69,6 +86,51 @@ public function get_query_args() {

$query_args['fields'] = 'ID';

/**
* Map the orderby inputArgs to the WP_User_Query
*/
if ( ! empty( $this->args['where']['orderby'] ) && is_array( $this->args['where']['orderby'] ) ) {
$query_args['orderby'] = array();
foreach ( $this->args['where']['orderby'] as $orderby_input ) {
/**
* These orderby options should not include the order parameter.
*/
if ( in_array( $orderby_input['field'], array( 'login__in', 'nicename__in' ), true ) ) {
$query_args['orderby'] = esc_sql( $orderby_input['field'] );
} elseif ( ! empty( $orderby_input['field'] ) ) {
$query_args['orderby'] = array(
esc_sql( $orderby_input['field'] ) => esc_sql( $orderby_input['order'] ),
);
}
}
}

/**
* Convert meta_value_num to seperate meta_value value field which our
* graphql_wp_term_query_cursor_pagination_support knowns how to handle
*/
if ( isset( $query_args['orderby'] ) && 'meta_value_num' === $query_args['orderby'] ) {
$query_args['orderby'] = array(
'meta_value' => empty( $query_args['order'] ) ? 'DESC' : $query_args['order'], // WPCS: slow query OK.
);
unset( $query_args['order'] );
$query_args['meta_type'] = 'NUMERIC';
}
/**
* If there's no orderby params in the inputArgs, set order based on the first/last argument
*/
if ( empty( $query_args['orderby'] ) ) {
$query_args['order'] = ! empty( $last ) ? 'ASC' : 'DESC';
}

if (
empty( $query_args['role'] ) &&
empty( $query_args['role__in'] ) &&
empty( $query_args['role__not_in'] )
) {
$query_args['role'] = 'customer';
}

$query_args = apply_filters(
'graphql_customer_connection_query_args',
$query_args,
Expand Down Expand Up @@ -150,4 +212,15 @@ public function sanitize_input_fields( array $where_args ) {

return $args;
}

/**
* Wrapper for "WC_Connection_Functions::is_valid_user_offset()"
*
* @param integer $offset User ID.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return $this->is_valid_user_offset( $offset );
}
}
36 changes: 25 additions & 11 deletions includes/data/connection/class-order-connection-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
* Class Order_Connection_Resolver
*/
class Order_Connection_Resolver extends AbstractConnectionResolver {
use Common_CPT_Input_Sanitize_Functions;
/**
* Include shared connection functions.
*/
use WC_Connection_Functions;

/**
* The name of the post type, or array of post types the connection resolver is resolving for
Expand Down Expand Up @@ -48,21 +51,21 @@ public function __construct( $source, $args, $context, $info ) {
}

/**
* Confirms the uses has the privileges to query Orders
* Checks if user is authorized to query orders
*
* @return bool
*/
public function should_execute() {
$post_type_obj = get_post_type_object( 'shop_order' );
switch ( true ) {
case current_user_can( $post_type_obj->cap->edit_posts ):
case is_a( $this->source, Customer::class )
&& 'orders' === $this->info->fieldName
&& get_current_user_id() === $this->source->ID:
return true;
default:
return false;
$post_type_obj = get_post_type_object( $this->post_type );
if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
return true;
}

if ( is_a( $this->source, Customer::class ) ) {
return 'orders' === $this->info->fieldName && get_current_user_id() === $this->source->ID;
}

return false;
}

/**
Expand Down Expand Up @@ -274,4 +277,15 @@ public function sanitize_input_fields( array $where_args ) {

return $args;
}

/**
* Wrapper for "WC_Connection_Functions::is_valid_post_offset()"
*
* @param integer $offset Post ID.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return $this->is_valid_post_offset( $offset );
}
}
Loading