feat: Queries and mutations for shipping zones, tax classes, and tax rates. (#856)

* fix: General bugfixes and improvements

* devops: New mutations and types tested and compliance with Linter and PHPStan

* chore: hooks added to the mutation resolvers

* feat: permission checks added

* chore: Linter and compliance met

* chore: Linter and compliance met
This commit is contained in:
Geoff Taylor
2024-05-17 00:03:10 -04:00
committed by GitHub
parent 6028061a50
commit f686e743b0
49 changed files with 4604 additions and 117 deletions
+4
View File
@@ -271,12 +271,16 @@ class Core_Schema_Filters {
$loaders['cart_item'] = &$cart_item_loader;
$downloadable_item_loader = new WC_Db_Loader( $context, 'DOWNLOADABLE_ITEM' );
$loaders['downloadable_item'] = &$downloadable_item_loader;
$tax_class_loader = new WC_Db_Loader( $context, 'TAX_CLASS' );
$loaders['tax_class'] = &$tax_class_loader;
$tax_rate_loader = new WC_Db_Loader( $context, 'TAX_RATE' );
$loaders['tax_rate'] = &$tax_rate_loader;
$order_item_loader = new WC_Db_Loader( $context, 'ORDER_ITEM' );
$loaders['order_item'] = &$order_item_loader;
$shipping_item_loader = new WC_Db_Loader( $context, 'SHIPPING_METHOD' );
$loaders['shipping_method'] = &$shipping_item_loader;
$shipping_zone_loader = new WC_Db_Loader( $context, 'SHIPPING_ZONE' );
$loaders['shipping_zone'] = &$shipping_zone_loader;
return $loaders;
}
+23
View File
@@ -46,6 +46,8 @@ class Type_Registry {
Type\WPEnum\Product_Attribute_Enum::register();
Type\WPEnum\Attribute_Operator_Enum::register();
Type\WPEnum\Currency_Enum::register();
Type\WPEnum\Shipping_Location_Type_Enum::register();
Type\WPEnum\WC_Setting_Type_Enum::register();
/**
* InputObjects.
@@ -67,6 +69,8 @@ class Type_Registry {
Type\WPInputObject\Collection_Stats_Where_Args::register();
Type\WPInputObject\Product_Attribute_Filter_Input::register();
Type\WPInputObject\Product_Attribute_Query_Input::register();
Type\WPInputObject\Shipping_Location_Input::register();
Type\WPInputObject\WC_Setting_Input::register();
/**
* Interfaces.
@@ -112,6 +116,10 @@ class Type_Registry {
Type\WPObject\Payment_Token_Types::register();
Type\WPObject\Country_State_Type::register();
Type\WPObject\Collection_Stats_Type::register();
Type\WPObject\Shipping_Zone_Type::register();
Type\WPObject\Shipping_Location_Type::register();
Type\WPObject\Tax_Class_Type::register();
Type\WPObject\WC_Setting_Type::register();
/**
* Object fields.
@@ -145,6 +153,8 @@ class Type_Registry {
Connection\Tax_Rates::register_connections();
Connection\Shipping_Methods::register_connections();
Connection\Payment_Gateways::register_connections();
Connection\Shipping_Zones::register_connections();
Connection\Tax_Classes::register_connections();
/**
* Mutations.
@@ -175,6 +185,19 @@ class Type_Registry {
Mutation\Coupon_Delete::register_mutation();
Mutation\Payment_Method_Delete::register_mutation();
Mutation\Payment_Method_Set_Default::register_mutation();
Mutation\Shipping_Zone_Create::register_mutation();
Mutation\Shipping_Zone_Delete::register_mutation();
Mutation\Shipping_Zone_Locations_Clear::register_mutation();
Mutation\Shipping_Zone_Locations_Update::register_mutation();
Mutation\Shipping_Zone_Method_Add::register_mutation();
Mutation\Shipping_Zone_Method_Remove::register_mutation();
Mutation\Shipping_Zone_Method_Update::register_mutation();
Mutation\Shipping_Zone_Update::register_mutation();
Mutation\Tax_Class_Create::register_mutation();
Mutation\Tax_Class_Delete::register_mutation();
Mutation\Tax_Rate_Create::register_mutation();
Mutation\Tax_Rate_Delete::register_mutation();
Mutation\Tax_Rate_Update::register_mutation();
Mutation\Update_Session::register_mutation();
}
}
+29 -1
View File
@@ -174,6 +174,7 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'model/class-order.php';
require $include_directory_path . 'model/class-order-item.php';
require $include_directory_path . 'model/class-shipping-method.php';
require $include_directory_path . 'model/class-shipping-zone.php';
require $include_directory_path . 'model/class-tax-rate.php';
// Include data loaders class files.
@@ -190,13 +191,15 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'data/connection/class-order-item-connection-resolver.php';
require $include_directory_path . 'data/connection/class-payment-gateway-connection-resolver.php';
require $include_directory_path . 'data/connection/class-product-attribute-connection-resolver.php';
require $include_directory_path . 'data/connection/class-product-connection-resolver.php';
require $include_directory_path . 'data/connection/class-shipping-method-connection-resolver.php';
require $include_directory_path . 'data/connection/class-shipping-zone-connection-resolver.php';
require $include_directory_path . 'data/connection/class-tax-class-connection-resolver.php';
require $include_directory_path . 'data/connection/class-tax-rate-connection-resolver.php';
require $include_directory_path . 'data/connection/class-variation-attribute-connection-resolver.php';
// Include deprecated resolver trait/class files.
require $include_directory_path . 'data/connection/class-coupon-connection-resolver.php';
require $include_directory_path . 'data/connection/class-product-connection-resolver.php';
require $include_directory_path . 'data/connection/class-customer-connection-resolver.php';
// Include mutation processor class files.
@@ -205,6 +208,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'data/mutation/class-coupon-mutation.php';
require $include_directory_path . 'data/mutation/class-customer-mutation.php';
require $include_directory_path . 'data/mutation/class-order-mutation.php';
require $include_directory_path . 'data/mutation/class-shipping-mutation.php';
require $include_directory_path . 'data/mutation/class-settings-mutation.php';
// Include factory class file.
require $include_directory_path . 'data/class-factory.php';
@@ -239,6 +244,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/enum/class-attribute-operator-enum.php';
require $include_directory_path . 'type/enum/class-product-attribute-enum.php';
require $include_directory_path . 'type/enum/class-currency-enum.php';
require $include_directory_path . 'type/enum/class-shipping-location-type-enum.php';
require $include_directory_path . 'type/enum/class-wc-setting-type-enum.php';
// Include interface type class files.
require $include_directory_path . 'type/interface/class-attribute.php';
@@ -282,6 +289,10 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/object/class-payment-token-types.php';
require $include_directory_path . 'type/object/class-country-state-type.php';
require $include_directory_path . 'type/object/class-collection-stats-type.php';
require $include_directory_path . 'type/object/class-shipping-location-type.php';
require $include_directory_path . 'type/object/class-shipping-zone-type.php';
require $include_directory_path . 'type/object/class-tax-class-type.php';
require $include_directory_path . 'type/object/class-wc-setting-type.php';
// Include input type class files.
require $include_directory_path . 'type/input/class-cart-item-input.php';
@@ -301,6 +312,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/input/class-collection-stats-where-args.php';
require $include_directory_path . 'type/input/class-product-attribute-filter-input.php';
require $include_directory_path . 'type/input/class-product-attribute-query-input.php';
require $include_directory_path . 'type/input/class-shipping-location-input.php';
require $include_directory_path . 'type/input/class-wc-setting-input.php';
// Include mutation type class files.
require $include_directory_path . 'mutation/class-cart-add-fee.php';
@@ -329,6 +342,19 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'mutation/class-review-update.php';
require $include_directory_path . 'mutation/class-payment-method-delete.php';
require $include_directory_path . 'mutation/class-payment-method-set-default.php';
require $include_directory_path . 'mutation/class-shipping-zone-create.php';
require $include_directory_path . 'mutation/class-shipping-zone-delete.php';
require $include_directory_path . 'mutation/class-shipping-zone-locations-clear.php';
require $include_directory_path . 'mutation/class-shipping-zone-locations-update.php';
require $include_directory_path . 'mutation/class-shipping-zone-method-add.php';
require $include_directory_path . 'mutation/class-shipping-zone-method-remove.php';
require $include_directory_path . 'mutation/class-shipping-zone-method-update.php';
require $include_directory_path . 'mutation/class-shipping-zone-update.php';
require $include_directory_path . 'mutation/class-tax-class-create.php';
require $include_directory_path . 'mutation/class-tax-class-delete.php';
require $include_directory_path . 'mutation/class-tax-rate-create.php';
require $include_directory_path . 'mutation/class-tax-rate-delete.php';
require $include_directory_path . 'mutation/class-tax-rate-update.php';
require $include_directory_path . 'mutation/class-update-session.php';
// Include connection class/function files.
@@ -342,6 +368,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'connection/class-product-attributes.php';
require $include_directory_path . 'connection/class-products.php';
require $include_directory_path . 'connection/class-shipping-methods.php';
require $include_directory_path . 'connection/class-shipping-zones.php';
require $include_directory_path . 'connection/class-tax-classes.php';
require $include_directory_path . 'connection/class-tax-rates.php';
require $include_directory_path . 'connection/class-wc-terms.php';
@@ -0,0 +1,63 @@
<?php
/**
* Connection - Shipping_Zones
*
* Registers connections to ShippingZone
*
* @package WPGraphQL\WooCommerce\Connection
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Connection;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Connection\Shipping_Zone_Connection_Resolver;
/**
* Class - Shipping_Zones
*/
class Shipping_Zones {
/**
* Registers the various connections from other Types to ShippingZone
*
* @return void
*/
public static function register_connections() {
// From RootQuery.
register_graphql_connection( self::get_connection_config() );
}
/**
* Given an array of $args, this returns the connection config, merging the provided args
* with the defaults.
*
* @param array $args - Connection configuration.
* @return array
*/
public static function get_connection_config( $args = [] ): array {
return array_merge(
[
'fromType' => 'RootQuery',
'toType' => 'ShippingZone',
'fromFieldName' => 'shippingZones',
'connectionArgs' => [],
'resolve' => static function ( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new Shipping_Zone_Connection_Resolver( $source, $args, $context, $info );
return $resolver->get_connection();
},
],
$args
);
}
/**
* Returns array of where args.
*
* @return array
*/
public static function get_connection_args(): array {
return [];
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/**
* Connection - Tax_Classes
*
* Registers connections to TaxClass
*
* @package WPGraphQL\WooCommerce\Connection
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Connection;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Connection\Tax_Class_Connection_Resolver;
/**
* Class - TaxClass
*/
class Tax_Classes {
/**
* Registers the various connections from other Types to TaxClass
*
* @return void
*/
public static function register_connections() {
// From RootQuery.
register_graphql_connection( self::get_connection_config() );
}
/**
* Given an array of $args, this returns the connection config, merging the provided args
* with the defaults.
*
* @param array $args - Connection configuration.
* @return array
*/
public static function get_connection_config( $args = [] ): array {
return array_merge(
[
'fromType' => 'RootQuery',
'toType' => 'TaxClass',
'fromFieldName' => 'taxClasses',
'connectionArgs' => [],
'resolve' => static function ( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new Tax_Class_Connection_Resolver( $source, $args, $context, $info );
return $resolver->get_connection();
},
],
$args
);
}
/**
* Returns array of where args.
*
* @return array
*/
public static function get_connection_args(): array {
return [];
}
}
@@ -11,6 +11,8 @@
namespace WPGraphQL\WooCommerce\Data\Connection;
use WPGraphQL\Data\Connection\AbstractConnectionResolver;
use WPGraphQL\WooCommerce\Model\Shipping_Method;
use WPGraphQL\WooCommerce\Model\Shipping_Zone;
/**
* Class Shipping_Method_Connection_Resolver
@@ -31,6 +33,10 @@ class Shipping_Method_Connection_Resolver extends AbstractConnectionResolver {
* @return bool
*/
public function should_execute() {
if ( ! wc_rest_check_manager_permissions( 'shipping_methods', 'read' ) ) {
graphql_debug( __( 'Permission denied.', 'wp-graphql-woocommerce' ) );
return false;
}
return true;
}
@@ -47,20 +53,22 @@ class Shipping_Method_Connection_Resolver extends AbstractConnectionResolver {
/**
* Executes query
*
* @return array|mixed|string[]
* @return int[]
*/
public function get_query() {
// TODO: Implement get_query() method.
$wc_shipping = \WC_Shipping::instance();
$methods = $wc_shipping->get_shipping_methods();
if ( $this->source instanceof Shipping_Zone ) {
$methods = $this->source->methods;
} else {
$wc_shipping = \WC_Shipping::instance();
$methods = $wc_shipping->get_shipping_methods();
}
foreach ( $methods as $method ) {
$this->loader->prime( $method->id, new Shipping_Method( $method ) );
}
// Get shipping method IDs.
$methods = array_map(
static function ( $item ) {
return $item->id;
},
array_values( $methods )
);
$methods = wp_list_pluck( array_values( $methods ), 'id' );
return $methods;
}
@@ -68,9 +76,9 @@ class Shipping_Method_Connection_Resolver extends AbstractConnectionResolver {
/**
* Return an array of items from the query
*
* @return array|mixed
* @return array
*/
public function get_ids() {
public function get_ids_from_query() {
return ! empty( $this->query ) ? $this->query : [];
}
@@ -0,0 +1,107 @@
<?php
/**
* ConnectionResolver - Shipping_Zone_Connection_Resolver
*
* Resolves connections to ShippingZone
*
* @package WPGraphQL\WooCommerce\Data\Connection
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Data\Connection;
use WPGraphQL\Data\Connection\AbstractConnectionResolver;
use WPGraphQL\WooCommerce\Model\Shipping_Zone;
/**
* Class Shipping_Zone_Connection_Resolver
*/
class Shipping_Zone_Connection_Resolver extends AbstractConnectionResolver {
/**
* Return the name of the loader to be used with the connection resolver
*
* @return string
*/
public function get_loader_name() {
return 'shipping_zone';
}
/**
* Confirms if shipping methods should be retrieved.
*
* @return bool
*/
public function should_execute() {
if ( ! \wc_shipping_enabled() ) {
graphql_debug( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ) );
return false;
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
graphql_debug( __( 'Permission denied.', 'wp-graphql-woocommerce' ) );
return false;
}
return true;
}
/**
* Creates filters for shipping zones.
*
* @return array|void
*/
public function get_query_args() {
return [];
}
/**
* Executes query
*
* @return array|mixed|string[]
*/
public function get_query() {
/** @var \WC_Shipping_Zone $rest_of_the_world */
$rest_of_the_world = \WC_Shipping_Zones::get_zone_by( 'zone_id', 0 );
$zones = \WC_Shipping_Zones::get_zones();
array_unshift( $zones, $rest_of_the_world->get_data() );
if ( ! empty( $this->query_args['filters'] ) && is_array( $this->query_args['filters'] ) ) {
foreach ( $this->query_args['filters'] as $filter ) {
$zones = array_filter( $zones, $filter );
}
}
return wp_list_pluck( $zones, 'id' );
}
/**
* Return an array of items from the query
*
* @return array
*/
public function get_ids_from_query() {
return ! empty( $this->query ) ? $this->query : [];
}
/**
* Validates offset.
*
* @param mixed $offset Decoded query cursor.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return is_string( $offset );
}
/**
* Validates shipping zone model.
*
* @param array $model Shipping zone model.
*
* @return bool
*/
protected function is_valid_model( $model ) {
return ! empty( $model ) && $model instanceof Shipping_Zone && 0 !== $model->ID;
}
}
@@ -0,0 +1,113 @@
<?php
/**
* ConnectionResolver - Tax_Class_Connection_Resolver
*
* Resolves connections to TaxClass
*
* @package WPGraphQL\WooCommerce\Data\Connection
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Data\Connection;
use WPGraphQL\Data\Connection\AbstractConnectionResolver;
/**
* Class Tax_Class_Connection_Resolver
*/
class Tax_Class_Connection_Resolver extends AbstractConnectionResolver {
/**
* Return the name of the loader to be used with the connection resolver
*
* @return string
*/
public function get_loader_name() {
return 'tax_class';
}
/**
* Confirms if tax classes should be retrieved.
*
* @return bool
*/
public function should_execute() {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
graphql_debug(
__( 'User does not have permission to view tax classes.', 'wp-graphql-woocommerce' )
);
return false;
}
return true;
}
/**
* Creates filters for tax classes.
*
* @return array|void
*/
public function get_query_args() {
return [];
}
/**
* Executes query
*
* @return array|mixed|string[]
*/
public function get_query() {
$tax_classes = [];
// Add standard class.
$tax_classes[] = [
'slug' => 'standard',
'name' => __( 'Standard rate', 'wp-graphql-woocommerce' ),
];
$classes = \WC_Tax::get_tax_classes();
foreach ( $classes as $class ) {
$tax_classes[] = [
'slug' => sanitize_title( $class ),
'name' => $class,
];
}
// Cache cart items for later.
foreach ( $tax_classes as $tax_class ) {
$this->loader->prime( $tax_class['slug'], $tax_class );
}
return wp_list_pluck( $tax_classes, 'slug' );
}
/**
* Return an array of items from the query
*
* @return array
*/
public function get_ids_from_query() {
return ! empty( $this->query ) ? $this->query : [];
}
/**
* Validates offset.
*
* @param mixed $offset Decoded query cursor.
*
* @return bool
*/
public function is_valid_offset( $offset ) {
return is_string( $offset );
}
/**
* Validates tax class model.
*
* @param array $model Tax class model.
*
* @return bool
*/
protected function is_valid_model( $model ) {
return is_array( $model ) && ! empty( $model['name'] ) && ! empty( $model['slug'] );
}
}
@@ -35,6 +35,12 @@ class Tax_Rate_Connection_Resolver extends AbstractConnectionResolver {
* @return bool
*/
public function should_execute() {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
graphql_debug(
__( 'User does not have permission to view tax rates.', 'wp-graphql-woocommerce' )
);
return false;
}
return true;
}
+78 -8
View File
@@ -14,6 +14,7 @@ use GraphQL\Error\UserError;
use WPGraphQL\Data\Loader\AbstractDataLoader;
use WPGraphQL\WooCommerce\Data\Factory;
use WPGraphQL\WooCommerce\Model\Shipping_Method;
use WPGraphQL\WooCommerce\Model\Shipping_Zone;
use WPGraphQL\WooCommerce\Model\Tax_Rate;
/**
@@ -64,6 +65,9 @@ class WC_Db_Loader extends AbstractDataLoader {
case 'DOWNLOADABLE_ITEM':
$loader = [ $this, 'load_downloadable_item_from_id' ];
break;
case 'TAX_CLASS':
$loader = [ $this, 'load_tax_class_from_slug' ];
break;
case 'TAX_RATE':
$loader = [ $this, 'load_tax_rate_from_id' ];
break;
@@ -73,6 +77,9 @@ class WC_Db_Loader extends AbstractDataLoader {
case 'SHIPPING_METHOD':
$loader = [ $this, 'load_shipping_method_from_id' ];
break;
case 'SHIPPING_ZONE':
$loader = [ $this, 'load_shipping_zone_from_id' ];
break;
default:
/**
* For adding custom key types to this loader
@@ -126,6 +133,25 @@ class WC_Db_Loader extends AbstractDataLoader {
return 0 === $node->get_id() ? $node : null;
}
/**
* Returns the tax class connected the provided IDs.
*
* @param int $slug - Tax class slug.
*
* @return array|null
*/
public function load_tax_class_from_slug( $slug ) {
if ( 'standard' === $slug ) {
return [
'slug' => 'standard',
'name' => __( 'Standard rate', 'wp-graphql-woocommerce' ),
];
} else {
$tax_class = \WC_Tax::get_tax_class_by( 'slug', $slug );
return is_array( $tax_class ) && ! empty( $tax_class ) ? $tax_class : null;
}
}
/**
* Returns the tax rate connected the provided IDs.
*
@@ -139,7 +165,7 @@ class WC_Db_Loader extends AbstractDataLoader {
/**
* Get tax rate from WooCommerce.
*
* @var object{
* @var \stdClass&object{
* tax_rate_id: int,
* tax_rate_class: string,
* tax_rate_country: string,
@@ -151,27 +177,51 @@ class WC_Db_Loader extends AbstractDataLoader {
* tax_rate_shipping: bool,
* tax_rate_order: int,
* tax_rate_city: string,
* tax_rate_postcode: string
* tax_rate_postcode: string,
* tax_rate_postcodes: string,
* tax_rate_cities: string
* } $rate
*/
$rate = \WC_Tax::_get_tax_rate( $id, OBJECT );
if ( ! empty( $rate ) && is_object( $rate ) ) {
$rate->tax_rate_city = '';
$rate->tax_rate_postcode = '';
$rate->tax_rate_postcodes = '';
$rate->tax_rate_cities = '';
// Get locales from a tax rate.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$locales = $wpdb->get_results(
$wpdb->prepare(
"SELECT location_code, location_type
"
SELECT location_code, location_type
FROM {$wpdb->prefix}woocommerce_tax_rate_locations
WHERE tax_rate_id = %d",
WHERE tax_rate_id = %d
",
$rate->tax_rate_id
)
);
$cities = [];
$postcodes = [];
foreach ( $locales as $locale ) {
if ( empty( $rate->{'tax_rate_' . $locale->location_type} ) ) {
$rate->{'tax_rate_' . $locale->location_type} = [];
if ( 'city' === $locale->location_type ) {
$cities[] = $locale->location_code;
} elseif ( 'postcode' === $locale->location_type ) {
$postcodes[] = $locale->location_code;
} else {
$rate->{'tax_rate_' . $locale->location_type} = $locale->location_code;
}
$rate->{'tax_rate_' . $locale->location_type}[] = $locale->location_code;
}
if ( ! empty( $cities ) ) {
$rate->tax_rate_cities = implode( ';', $cities );
$rate->tax_rate_city = end( $cities );
}
if ( ! empty( $postcodes ) ) {
$rate->tax_rate_postcodes = implode( ';', $postcodes );
$rate->tax_rate_postcode = end( $postcodes );
}
return new Tax_Rate( $rate );
} else {
@@ -185,7 +235,7 @@ class WC_Db_Loader extends AbstractDataLoader {
* @param int $id - Shipping method ID.
*
* @return \WPGraphQL\WooCommerce\Model\Shipping_Method
* @access public
*
* @throws \GraphQL\Error\UserError Invalid object.
*/
public function load_shipping_method_from_id( $id ) {
@@ -203,6 +253,26 @@ class WC_Db_Loader extends AbstractDataLoader {
return new Shipping_Method( $method );
}
/**
* Returns the shipping zone connected the provided IDs.
*
* @param int $id - Shipping zone IDs.
*
* @return \WPGraphQL\WooCommerce\Model\Shipping_Zone|null
*/
public function load_shipping_zone_from_id( $id ) {
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone( $id );
if ( false === $zone ) {
return null;
}
$zone = new Shipping_Zone( $zone );
return $zone;
}
/**
* Returns the order item connected the provided IDs.
*
@@ -0,0 +1,164 @@
<?php
/**
* Defines helper functions for executing mutations related to the WC Settings API.
*
* @package WPGraphQL\WooCommerce\Data\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Data\Mutation;
use GraphQL\Error\UserError;
/**
* Class - Settings_Mutation
*/
class Settings_Mutation {
/**
* Validate a text value for a text based setting.
*
* @param string $value Value.
* @param array $setting Setting.
* @return string
*/
public static function validate_setting_text_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses_post( trim( stripslashes( $value ) ) );
}
/**
* Validate select based settings.
*
* @param string $value Value.
* @param array $setting Setting.
*
* @throws \GraphQL\Error\UserError If an invalid setting value was passed.
*
* @return string
*/
public static function validate_setting_select_field( $value, $setting ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
return $value;
} else {
throw new UserError( __( 'An invalid setting value was passed.', 'wp-graphql-woocommerce' ), 400 );
}
}
/**
* Validate multiselect based settings.
*
* @param array $values Values.
* @param array $setting Setting.
*
* @throws \GraphQL\Error\UserError If an invalid setting value was passed.
*
* @return array
*/
public static function validate_setting_multiselect_field( $values, $setting ) {
if ( empty( $values ) ) {
return [];
}
if ( ! is_array( $values ) ) {
throw new UserError( __( 'An invalid setting value was passed.', 'wp-graphql-woocommerce' ), 400 );
}
$final_values = [];
foreach ( $values as $value ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
$final_values[] = $value;
}
}
return $final_values;
}
/**
* Validate image_width based settings.
*
* @param array $values Values.
* @param array $setting Setting.
*
* @throws \GraphQL\Error\UserError If an invalid setting value was passed.
*
* @return string
*/
public static function validate_setting_image_width_field( $values, $setting ) {
if ( ! is_array( $values ) ) {
throw new UserError( __( 'An invalid setting value was passed.', 'wp-graphql-woocommerce' ), 400 );
}
$current = $setting['value'];
if ( isset( $values['width'] ) ) {
$current['width'] = intval( $values['width'] );
}
if ( isset( $values['height'] ) ) {
$current['height'] = intval( $values['height'] );
}
if ( isset( $values['crop'] ) ) {
$current['crop'] = (bool) $values['crop'];
}
return $current;
}
/**
* Validate radio based settings.
*
* @param string $value Value.
* @param array $setting Setting.
*
* @throws \GraphQL\Error\UserError If an invalid setting value was passed.
*
* @return string
*/
public static function validate_setting_radio_field( $value, $setting ) {
return self::validate_setting_select_field( $value, $setting );
}
/**
* Validate checkbox based settings.
*
* @param string $value Value.
* @param array $setting Setting.
*
* @throws \GraphQL\Error\UserError If an invalid setting value was passed.
*
* @return string
*/
public function validate_setting_checkbox_field( $value, $setting ) {
if ( in_array( $value, [ 'yes', 'no' ], true ) ) {
return $value;
} elseif ( empty( $value ) ) {
$value = isset( $setting['default'] ) ? $setting['default'] : 'no';
return $value;
} else {
throw new UserError( __( 'An invalid setting value was passed.', 'wp-graphql-woocommerce' ), 400 );
}
}
/**
* Validate textarea based settings.
*
* @param string $value Value.
* @param array $setting Setting.
*
* @return string
*/
public static function validate_setting_textarea_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses(
trim( stripslashes( $value ) ),
array_merge(
[
'iframe' => [
'src' => true,
'style' => true,
'id' => true,
'class' => true,
],
],
wp_kses_allowed_html( 'post' )
)
);
}
}
@@ -0,0 +1,110 @@
<?php
/**
* Defines helper functions for executing mutations related to shipping.
*
* @package WPGraphQL\WooCommerce\Data\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Data\Mutation;
/**
* Class - Shipping_Mutation
*/
class Shipping_Mutation {
/**
* Maps the settings input for insertion.
*
* @param array<array<string, string>> $settings_input Settings input.
*
* @return array<string, string>
*/
private static function flatten_settings_input( $settings_input ) {
$settings = [];
foreach ( $settings_input as $setting ) {
$settings[ $setting['id'] ] = $setting['value'];
}
return $settings;
}
/**
* Updates settings on a shipping zone method.
*
* @param int $instance_id Instance ID.
* @param \WC_Shipping_Method $method Shipping method data.
* @param array $settings_input Settings input.
*
* @return \WC_Shipping_Method
*/
public static function set_shipping_zone_method_settings( $instance_id, $method, $settings_input ) {
$settings = self::flatten_settings_input( $settings_input );
$method->init_instance_settings();
$instance_settings = $method->instance_settings;
$errors_found = false;
foreach ( $method->get_instance_form_fields() as $key => $field ) {
if ( isset( $settings[ $key ] ) ) {
if ( is_callable( [ Settings_Mutation::class, 'validate_setting_' . $field['type'] . '_field' ] ) ) {
$value = Settings_Mutation::{'validate_setting_' . $field['type'] . '_field'}( $settings[ $key ], $field );
} else {
$value = Settings_Mutation::validate_setting_text_field( $settings[ $key ], $field );
}
$instance_settings[ $key ] = $value;
}
}
update_option(
$method->get_instance_option_key(),
apply_filters(
'woocommerce_shipping_' . $method->id . '_instance_settings_values', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$instance_settings,
$method
)
);
$method->instance_settings = $instance_settings;
return $method;
}
/**
* Updates the order of a shipping zone method.
*
* @param int $instance_id Instance ID.
* @param \WC_Shipping_Method $method Shipping method data.
* @param int $order Order.
*
* @return \WC_Shipping_Method
*/
public static function set_shipping_zone_method_order( $instance_id, $method, $order ) {
global $wpdb;
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
"{$wpdb->prefix}woocommerce_shipping_zone_methods",
[ 'method_order' => $order ],
[ 'instance_id' => $instance_id ]
);
$method->method_order = $order;
return $method;
}
/**
* Updates the enabled status of a shipping zone method.
*
* @param int $zone_id Zone ID.
* @param int $instance_id Instance ID.
* @param \WC_Shipping_Method $method Shipping method data.
* @param bool $enabled Enabled status.
*
* @return \WC_Shipping_Method
*/
public static function set_shipping_zone_method_enabled( $zone_id, $instance_id, $method, $enabled ) {
global $wpdb;
if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", [ 'is_enabled' => $enabled ], [ 'instance_id' => $instance_id ] ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery
do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method->id, $zone_id, $enabled ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$method->enabled = ( true === $enabled ? 'yes' : 'no' );
}
return $method;
}
}
+28
View File
@@ -81,4 +81,32 @@ class Shipping_Method extends Model {
];
}
}
/**
* Forwards function calls to WC_Data sub-class instance.
*
* @param string $method - function name.
* @param array $args - function call arguments.
*
* @return mixed
*
* @throws \BadMethodCallException Method not found on WC data object.
*/
public function __call( $method, $args ) {
if ( \is_callable( [ $this->data, $method ] ) ) {
return $this->data->$method( ...$args );
}
$class = self::class;
throw new \BadMethodCallException( "Call to undefined method {$method} on the {$class}" );
}
/**
* Returns the source WC_Data instance
*
* @return \WC_Shipping_Method
*/
public function as_WC_Data() {
return $this->data;
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
/**
* Model - Shipping_Zone
*
* This model represents a Shipping Zone.
*
* @package WPGraphQL\WooCommerce\Model
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Model;
use GraphQLRelay\Relay;
use WPGraphQL\Model\Model;
/**
* Class Shipping_Zone
*
* @property \WC_Shipping_Zone $data
*
* @property int $ID
* @property string $id
* @property int $databaseId
* @property string $name
* @property string $order
* @property object{code: string, type: string}[] $locations
* @property \WC_Shipping_Method[] $methods
*
* @package WPGraphQL\WooCommerce\Model
*/
class Shipping_Zone extends Model {
/**
* Shipping_Zone constructor
*
* @param \WC_Shipping_Zone $zone Shipping zone object.
*/
public function __construct( $zone ) {
$this->data = $zone;
$allowed_restricted_fields = [
'isRestricted',
'isPrivate',
'isPublic',
'id',
'databaseId',
];
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$restricted_cap = apply_filters( 'shipping_zone_restricted_cap', '' );
parent::__construct( $restricted_cap, $allowed_restricted_fields, null );
}
/**
* Determines if the order item should be considered private.
*
* @return bool
*/
protected function is_private() {
return false;
}
/**
* Initializes the Order field resolvers.
*/
protected function init() {
if ( empty( $this->fields ) ) {
$this->fields = [
'ID' => function () {
return $this->data->get_id();
},
'id' => function () {
return ! empty( $this->data->get_id() ) ? Relay::toGlobalId( 'shipping_zone', (string) $this->data->get_id() ) : null;
},
'databaseId' => function () {
return ! empty( $this->ID ) ? $this->ID : null;
},
'name' => function () {
return ! empty( $this->data->get_zone_name() ) ? $this->data->get_zone_name() : null;
},
'order' => function () {
return $this->data->get_zone_order();
},
'locations' => function () {
return $this->data->get_zone_locations();
},
'methods' => function () {
return $this->data->get_shipping_methods();
},
];
}
}
/**
* Forwards function calls to WC_Data sub-class instance.
*
* @param string $method - function name.
* @param array $args - function call arguments.
*
* @return mixed
*
* @throws \BadMethodCallException Method not found on WC data object.
*/
public function __call( $method, $args ) {
if ( \is_callable( [ $this->data, $method ] ) ) {
return $this->data->$method( ...$args );
}
$class = self::class;
throw new \BadMethodCallException( "Call to undefined method {$method} on the {$class}" );
}
/**
* Returns the source WC_Data instance
*
* @return \WC_Shipping_Zone
*/
public function as_WC_Data() {
return $this->data;
}
}
+30 -20
View File
@@ -28,23 +28,27 @@ use WPGraphQL\Model\Model;
* tax_rate_shipping: bool,
* tax_rate_order: int,
* tax_rate_city: string,
* tax_rate_postcode: string
* tax_rate_postcode: string,
* tax_rate_postcodes: string,
* tax_rate_cities: string
* } $data
*
* @property int $ID
* @property string $id
* @property string $databaseId
* @property string $country
* @property string $state
* @property string $city
* @property string $postcode
* @property string $rate
* @property string $name
* @property string $priority
* @property bool $compound
* @property bool $shipping
* @property int $order
* @property string $class
* @property int $ID
* @property string $id
* @property string $databaseId
* @property string $country
* @property string $state
* @property string $city
* @property string $postcode
* @property string[] $postcodes
* @property string[] $cities
* @property string $rate
* @property string $name
* @property string $priority
* @property bool $compound
* @property bool $shipping
* @property int $order
* @property string $class
*
* @package WPGraphQL\WooCommerce\Model
*/
@@ -52,7 +56,7 @@ class Tax_Rate extends Model {
/**
* Tax_Rate constructor
*
* @param object{ tax_rate_id: int, tax_rate_class: string, tax_rate_country: string, tax_rate_state: string, tax_rate: string, tax_rate_name: string, tax_rate_priority: int, tax_rate_compound: bool, tax_rate_shipping: bool, tax_rate_order: int, tax_rate_city: string, tax_rate_postcode: string } $rate Tax rate object.
* @param object{ tax_rate_id: int, tax_rate_class: string, tax_rate_country: string, tax_rate_state: string, tax_rate: string, tax_rate_name: string, tax_rate_priority: int, tax_rate_compound: bool, tax_rate_shipping: bool, tax_rate_order: int, tax_rate_city: string, tax_rate_postcode: string, tax_rate_postcodes: string, tax_rate_cities: string } $rate Tax rate object.
*/
public function __construct( $rate ) {
$this->data = $rate;
@@ -101,10 +105,16 @@ class Tax_Rate extends Model {
return ! empty( $this->data->tax_rate_state ) ? $this->data->tax_rate_state : null;
},
'city' => function () {
return ! empty( $this->data->tax_rate_city ) ? $this->data->tax_rate_city : [ '*' ];
return ! empty( $this->data->tax_rate_city ) ? $this->data->tax_rate_city : '*';
},
'postcode' => function () {
return ! empty( $this->data->tax_rate_postcode ) ? $this->data->tax_rate_postcode : [ '*' ];
return ! empty( $this->data->tax_rate_postcode ) ? $this->data->tax_rate_postcode : '*';
},
'postcodes' => function () {
return ! empty( $this->data->tax_rate_postcodes ) ? explode( ';', $this->data->tax_rate_postcodes ) : [ '*' ];
},
'cities' => function () {
return ! empty( $this->data->tax_rate_cities ) ? explode( ';', $this->data->tax_rate_cities ) : [ '*' ];
},
'rate' => function () {
return ! empty( $this->data->tax_rate ) ? $this->data->tax_rate : null;
@@ -116,10 +126,10 @@ class Tax_Rate extends Model {
return ! empty( $this->data->tax_rate_priority ) ? $this->data->tax_rate_priority : null;
},
'compound' => function () {
return ! empty( $this->data->tax_rate_compound ) ? $this->data->tax_rate_compound : null;
return isset( $this->data->tax_rate_compound ) ? $this->data->tax_rate_compound : null;
},
'shipping' => function () {
return ! empty( $this->data->tax_rate_shipping ) ? $this->data->tax_rate_shipping : null;
return isset( $this->data->tax_rate_shipping ) ? $this->data->tax_rate_shipping : null;
},
'order' => function () {
return ! is_null( $this->data->tax_rate_order ) ? absint( $this->data->tax_rate_order ) : null;
@@ -0,0 +1,110 @@
<?php
/**
* Mutation - createShippingZone
*
* Registers mutation for creating a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Shipping_Zone_Create
*/
class Shipping_Zone_Create {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'createShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'name' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Name of the shipping zone.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'Order of the shipping zone.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to create shipping zones.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$zone = new \WC_Shipping_Zone( null );
$zone->set_zone_name( $input['name'] );
if ( ! empty( $input['order'] ) ) {
$zone->set_zone_order( $input['order'] );
}
/**
* Filter zone object before saving.
*
* @param \WC_Shipping_Zone $zone The response object.
* @param array $input Request input.
*/
$zone = apply_filters( 'graphql_woocommerce_shipping_zone_create', $zone, $input );
$zone_id = $zone->save();
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Failed to create shipping zone.', 'wp-graphql-woocommerce' ) );
}
return [ 'zone_id' => $zone_id ];
};
}
}
@@ -0,0 +1,114 @@
<?php
/**
* Mutation - deleteShippingZone
*
* Registers mutation for deleting a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Shipping_Zone_Delete
*/
class Shipping_Zone_Delete {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'deleteShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'id' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload ) {
return $payload['shippingZone'];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to delete shipping zones', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$zone_id = $input['id'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
/**
* Filter zone object returned from the GraphQL API.
*
* @param \WC_Shipping_Zone $zone The response object.
* @param array $input Request input.
*/
$zone = apply_filters( 'graphql_woocommerce_delete_shipping_zone', $zone, $input );
$object = $context->get_loader( 'shipping_zone' )->load( $zone_id );
/**
* Filter zone model returned from the GraphQL API before deletion.
*
* @param \Shipping_Zone $object The response object.
* @param \WC_Shipping_Zone $zone The zone object.
* @param array $input Request input.
*/
$object = apply_filters( 'graphql_woocommerce_delete_shipping_zone_object', $object, $zone, $input );
$zone->delete();
return [ 'shippingZone' => $object ];
};
}
}
@@ -0,0 +1,142 @@
<?php
/**
* Mutation - clearShippingZoneLocations
*
* Registers mutation for removing all registered shipping locations from a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Shipping_Zone_Locations_Clear
*/
class Shipping_Zone_Locations_Clear {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'clearShippingZoneLocations',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'zoneId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
'type' => [
'type' => 'ShippingLocationTypeEnum',
'description' => __( 'The type of location to remove.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
'removedLocations' => [
'type' => [ 'list_of' => 'ShippingLocation' ],
'resolve' => static function ( $payload ) {
return ! empty( $payload['removedLocations'] )
? array_map(
static function ( $location ) {
return (object) $location;
},
$payload['removedLocations'],
)
: [];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to remove shipping locations', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$zone_id = $input['zoneId'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
$types = [ 'postcode', 'state', 'country', 'continent' ];
if ( ! empty( $input['type'] ) ) {
$types = [ $input['type'] ];
}
$all_locations = $zone->get_zone_locations();
$locations = array_filter(
$all_locations,
static function ( $location ) use ( $types ) {
return in_array( $location->type, $types, true );
}
);
/**
* Filter zone object before removing the locations.
*
* @param \WC_Shipping_Zone $zone The response object.
* @param array $locations Locations to be removed.
* @param array $input Request input.
*/
$zone = apply_filters( 'graphql_woocommerce_shipping_zone_locations_clear', $zone, $locations, $input );
$zone->clear_locations( $types );
$zone->save();
return [
'zone_id' => $zone_id,
'removedLocations' => $locations,
];
};
}
}
@@ -0,0 +1,140 @@
<?php
/**
* Mutation - clearShippingZoneLocations
*
* Registers mutation for update the registered shipping locations on a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Shipping_Zone_Locations_Update
*/
class Shipping_Zone_Locations_Update {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'updateShippingZoneLocations',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'zoneId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
'locations' => [
'type' => [ 'list_of' => 'ShippingLocationInput' ],
'description' => __( 'The locations to add to the shipping zone.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
'locations' => [
'type' => [ 'list_of' => 'ShippingLocation' ],
'resolve' => static function ( $payload ) {
return ! empty( $payload['locations'] )
? array_map(
static function ( $location ) {
return (object) $location;
},
$payload['locations'],
)
: [];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to update shipping location', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$zone_id = $input['zoneId'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
$raw_locations = ! empty( $input['locations'] ) ? $input['locations'] : [];
$locations = [];
foreach ( $raw_locations as $location ) {
$type = ! empty( $location['type'] ) ? $location['type'] : 'country';
$locations[] = [
'type' => $type,
'code' => $location['code'],
];
}
/**
* Filter zone object before add the locations.
*
* @param \WC_Shipping_Zone $zone The response object.
* @param array $locations Locations to be saved.
* @param array $input Request input.
*/
$zone = apply_filters( 'graphql_woocommerce_shipping_zone_locations_update', $zone, $locations, $input );
$zone->set_locations( $locations );
$zone->save();
return [
'zone_id' => $zone_id,
'locations' => $locations,
];
};
}
}
@@ -0,0 +1,168 @@
<?php
/**
* Mutation - addMethodToShippingZone
*
* Registers mutation for adding a shipping method to a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Mutation\Shipping_Mutation;
use WPGraphQL\WooCommerce\Model\Shipping_Method;
/**
* Class - Shipping_Zone_Method_Add
*/
class Shipping_Zone_Method_Add {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'addMethodToShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'zoneId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
'methodId' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'The ID of the shipping method to add.', 'wp-graphql-woocommerce' ),
],
'enabled' => [
'type' => 'Boolean',
'description' => __( 'Whether the shipping method is enabled or not.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'The order of the shipping method.', 'wp-graphql-woocommerce' ),
],
'settings' => [
'type' => [ 'list_of' => 'WCSettingInput' ],
'description' => __( 'The settings for the shipping method.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
'method' => [
'type' => 'ShippingZoneToShippingMethodConnectionEdge',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return [
// Call the Shipping_Method constructor directly because "$payload['method']" is a non-scalar value.
'node' => new Shipping_Method( $payload['method'] ),
'source' => $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] ),
];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to add shipping methods', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$method_id = $input['methodId'];
$zone_id = $input['zoneId'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
$instance_id = $zone->add_shipping_method( $method_id );
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $method_obj ) {
if ( $method_obj->instance_id === $instance_id ) {
$method = $method_obj;
break;
}
}
if ( false === $method ) {
throw new UserError( __( 'Failed to add shipping method to shipping zone.', 'wp-graphql-woocommerce' ) );
}
// Update settings.
if ( ! empty( $input['settings'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_settings( $instance_id, $method, $input['settings'] );
}
// Update order.
if ( isset( $input['order'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_order( $instance_id, $method, $input['order'] );
}
// Update if this method is enabled or not.
if ( isset( $input['enabled'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_enabled( $zone_id, $instance_id, $method, $input['enabled'] );
}
/**
* Filter shipping method object before responding.
*
* @param \WC_Shipping_Method $method The shipping method object.
* @param \WC_Shipping_Zone $zone The response object.
* @param array $input Request input.
*/
$method = apply_filters( 'graphql_woocommerce_shipping_zone_method_add', $method, $zone, $input );
return [
'zone_id' => $zone_id,
'zone' => $zone,
'method' => $method,
];
};
}
}
@@ -0,0 +1,142 @@
<?php
/**
* Mutation - removeMethodFromShippingZone
*
* Registers mutation for removing a shipping method from a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Model\Shipping_Method;
/**
* Class - Shipping_Zone_Method_Remove
*/
class Shipping_Zone_Method_Remove {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'removeMethodFromShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'zoneId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
'instanceId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'Shipping method instance ID', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
'removedMethod' => [
'type' => 'ShippingZoneToShippingMethodConnectionEdge',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return [
// Call the Shipping_Method constructor directly because "$payload['method']" is a non-scalar value.
'node' => new Shipping_Method( $payload['method'] ),
'source' => $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] ),
];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to remove shipping methods', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$instance_id = $input['instanceId'];
$zone_id = $input['zoneId'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $shipping_method ) {
if ( $shipping_method->instance_id === $instance_id ) {
$method = $shipping_method;
break;
}
}
if ( ! $method ) {
throw new UserError( __( 'Invalid shipping method instance ID.', 'wp-graphql-woocommerce' ) );
}
/**
* Filter shipping method object before it's removed from the shipping zone.
*
* @param \WC_Shipping_Method $method The shipping method to be deleted.
* @param \WC_Shipping_Zone $zone The shipping zone object.
* @param array $input Request input.
*/
$method = apply_filters( 'graphql_woocommerce_shipping_zone_method_add', $method, $zone, $input );
$zone->delete_shipping_method( $instance_id );
return [
'zone_id' => $zone_id,
'zone' => $zone,
'method' => $method,
];
};
}
}
@@ -0,0 +1,167 @@
<?php
/**
* Mutation - updateMethodOnShippingZone
*
* Registers mutation for update a shipping method on a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Mutation\Shipping_Mutation;
use WPGraphQL\WooCommerce\Model\Shipping_Method;
/**
* Class - Shipping_Zone_Method_Update
*/
class Shipping_Zone_Method_Update {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'updateMethodOnShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'zoneId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to delete.', 'wp-graphql-woocommerce' ),
],
'instanceId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'Shipping method instance ID', 'wp-graphql-woocommerce' ),
],
'enabled' => [
'type' => 'Boolean',
'description' => __( 'Whether the shipping method is enabled or not.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'The order of the shipping method.', 'wp-graphql-woocommerce' ),
],
'settings' => [
'type' => [ 'list_of' => 'WCSettingInput' ],
'description' => __( 'The settings for the shipping method.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
'method' => [
'type' => 'ShippingZoneToShippingMethodConnectionEdge',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return [
// Call the Shipping_Method constructor directly because "$payload['method']" is a non-scalar value.
'node' => new Shipping_Method( $payload['method'] ),
'source' => $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] ),
];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to edit shipping methods.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$instance_id = $input['instanceId'];
$zone_id = $input['zoneId'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $shipping_method ) {
if ( $shipping_method->instance_id === $instance_id ) {
$method = $shipping_method;
break;
}
}
if ( ! $method ) {
throw new UserError( __( 'Invalid shipping method instance ID.', 'wp-graphql-woocommerce' ) );
}
// Update settings.
if ( ! empty( $input['settings'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_settings( $instance_id, $method, $input['settings'] );
}
// Update order.
if ( isset( $input['order'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_order( $instance_id, $method, $input['order'] );
}
// Update if this method is enabled or not.
if ( isset( $input['enabled'] ) ) {
$method = Shipping_Mutation::set_shipping_zone_method_enabled( $zone_id, $instance_id, $method, $input['enabled'] );
}
/**
* Filter shipping method object before responding.
*
* @param \WC_Shipping_Method $method The shipping method object.
* @param \WC_Shipping_Zone $zone The shipping zone object.
* @param array $input Request input.
*/
$method = apply_filters( 'graphql_woocommerce_shipping_zone_method_update', $method, $zone, $input );
return [
'zone_id' => $zone_id,
'zone' => $zone,
'method' => $method,
];
};
}
}
@@ -0,0 +1,128 @@
<?php
/**
* Mutation - updateShippingZone
*
* Registers mutation for updating a shipping zone.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Shipping_Zone_Update
*/
class Shipping_Zone_Update {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'updateShippingZone',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'id' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the shipping zone to update.', 'wp-graphql-woocommerce' ),
],
'name' => [
'type' => 'String',
'description' => __( 'Name of the shipping zone.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'Order of the shipping zone.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'shippingZone' => [
'type' => 'ShippingZone',
'resolve' => static function ( $payload, array $args, AppContext $context ) {
return $context->get_loader( 'shipping_zone' )->load( $payload['zone_id'] );
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to edit shipping zones', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$zone_id = $input['id'];
/** @var \WC_Shipping_Zone|false $zone */
$zone = \WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
throw new UserError( __( 'Invalid shipping zone ID.', 'wp-graphql-woocommerce' ) );
}
if ( 0 === $zone->get_id() ) {
throw new UserError( __( 'The "locations not covered by your other zones" zone cannot be updated.', 'wp-graphql-woocommerce' ) );
}
$zone_changed = false;
if ( ! empty( $input['name'] ) ) {
$zone->set_zone_name( $input['name'] );
$zone_changed = true;
}
if ( ! empty( $input['order'] ) ) {
$zone->set_zone_order( $input['order'] );
$zone_changed = true;
}
if ( $zone_changed ) {
/**
* Filter zone object before saving changes.
*
* @param \WC_Shipping_Zone $zone The response object.
* @param array $input Request input.
*/
$zone = apply_filters( 'graphql_woocommerce_shipping_zone_update', $zone, $input );
$zone->save();
}
return [ 'zone_id' => $zone_id ];
};
}
}
@@ -0,0 +1,101 @@
<?php
/**
* Mutation - createTaxClass
*
* Registers mutation for creating a tax class.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Tax_Class_Create
*/
class Tax_Class_Create {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'createTaxClass',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'name' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Name of the tax class.', 'wp-graphql-woocommerce' ),
],
'slug' => [
'type' => 'String',
'description' => __( 'Slug of the tax class.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'taxClass' => [
'type' => 'TaxClass',
'resolve' => static function ( $payload ) {
return ! empty( $payload['taxClass'] ) ? $payload['taxClass'] : null;
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_rest_check_manager_permissions( 'settings', 'create' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to create tax classes.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$name = $input['name'];
$slug = ! empty( $input['slug'] ) ? $input['slug'] : '';
$tax_class = \WC_Tax::create_tax_class( $name, $slug );
if ( is_wp_error( $tax_class ) ) {
throw new UserError( $tax_class->get_error_message() );
}
/**
* Filter tax class object before responding.
*
* @param array $tax_class The shipping method object.
* @param array $input Request input.
*/
$tax_class = apply_filters( 'graphql_woocommerce_tax_class_create', $tax_class, $input );
return [ 'taxClass' => $tax_class ];
};
}
}
@@ -0,0 +1,109 @@
<?php
/**
* Mutation - deleteTaxClass
*
* Registers mutation for deleting a tax class.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Tax_Class_Delete
*/
class Tax_Class_Delete {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'deleteTaxClass',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'slug' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Slug of the tax class.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'taxClass' => [
'type' => 'TaxClass',
'resolve' => static function ( $payload ) {
return ! empty( $payload['taxClass'] ) ? $payload['taxClass'] : null;
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to delete tax classes.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$slug = $input['slug'];
/** @var array|false $tax_class */
$tax_class = \WC_Tax::get_tax_class_by( 'slug', $slug );
if ( ! $tax_class ) {
throw new UserError( __( 'Invalid tax class slug.', 'wp-graphql-woocommerce' ) );
}
/**
* Action hook before deleting tax class.
*
* @param array $tax_class The tax class object.
* @param array $input Request input.
*/
do_action( 'graphql_woocommerce_before_tax_class_delete', $tax_class, $input );
$deleted = \WC_Tax::delete_tax_class_by( 'slug', $slug );
if ( ! $deleted ) {
throw new UserError( __( 'Failed to delete tax class.', 'wp-graphql-woocommerce' ) );
}
/**
* Filter tax class object before responding.
*
* @param array $tax_class The shipping method object.
* @param array $input Request input.
*/
$tax_class = apply_filters( 'graphql_woocommerce_tax_class_delete', $tax_class, $input );
return [ 'taxClass' => $tax_class ];
};
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
/**
* Mutation - createTaxRate
*
* Registers mutation for creating a tax rate.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Tax_Rate_Create
*/
class Tax_Rate_Create {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'createTaxRate',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => [ self::class, 'mutate_and_get_payload' ],
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'country' => [
'type' => 'String',
'description' => __( 'Country code for the tax rate.', 'wp-graphql-woocommerce' ),
],
'state' => [
'type' => 'String',
'description' => __( 'State code for the tax rate.', 'wp-graphql-woocommerce' ),
],
'postcodes' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Postcodes for the tax rate.', 'wp-graphql-woocommerce' ),
],
'cities' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Cities for the tax rate.', 'wp-graphql-woocommerce' ),
],
'rate' => [
'type' => 'String',
'description' => __( 'Tax rate.', 'wp-graphql-woocommerce' ),
],
'name' => [
'type' => 'String',
'description' => __( 'Tax rate name.', 'wp-graphql-woocommerce' ),
],
'priority' => [
'type' => 'Int',
'description' => __( 'Tax rate priority.', 'wp-graphql-woocommerce' ),
],
'compound' => [
'type' => 'Boolean',
'description' => __( 'Whether the tax rate is compound.', 'wp-graphql-woocommerce' ),
],
'shipping' => [
'type' => 'Boolean',
'description' => __( 'Whether the tax rate is applied to shipping.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'Tax rate order.', 'wp-graphql-woocommerce' ),
],
'class' => [
'type' => 'TaxClassEnum',
'description' => __( 'Tax rate class.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'taxRate' => [
'type' => 'TaxRate',
'resolve' => static function ( array $payload, array $args, AppContext $context ) {
return $context->get_loader( 'tax_rate' )->load( $payload['tax_rate_id'] );
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @param array $input Mutation input.
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance. Can be
* use to get info about the current node in the GraphQL tree.
*
* @throws \GraphQL\Error\UserError Invalid ID provided | Lack of capabilities.
*
* @return array
*/
public static function mutate_and_get_payload( $input, AppContext $context, ResolveInfo $info ) {
$id = ! empty( $input['id'] ) ? $input['id'] : null;
$action = ! $id ? 'create' : 'update';
$permission = ! $id ? 'create' : 'edit';
if ( ! \wc_rest_check_manager_permissions( 'settings', $permission ) ) {
throw new UserError(
sprintf(
/* translators: %s: permission */
__( 'Sorry, you are not allowed to %s tax rates.', 'wp-graphql-woocommerce' ),
$permission
),
\rest_authorization_required_code()
);
}
$current = null;
if ( ! empty( $id ) ) {
/**
* @var object{
* tax_rate_id: int,
* tax_rate_class: string,
* tax_rate_country: string,
* tax_rate_state: string,
* tax_rate: string,
* tax_rate_name: string,
* tax_rate_priority: int,
* tax_rate_compound: bool,
* tax_rate_shipping: bool,
* tax_rate_order: int,
* tax_rate_city: string,
* tax_rate_postcode: string,
* tax_rate_postcodes: string,
* tax_rate_cities: string
* } $current
*/
$current = \WC_Tax::_get_tax_rate( $id, OBJECT );
}
$data = [];
$fields = [
'country' => 'tax_rate_country',
'state' => 'tax_rate_state',
'rate' => 'tax_rate',
'name' => 'tax_rate_name',
'priority' => 'tax_rate_priority',
'compound' => 'tax_rate_compound',
'shipping' => 'tax_rate_shipping',
'order' => 'tax_rate_order',
'class' => 'tax_rate_class',
];
foreach ( $fields as $key => $field ) {
if ( ! isset( $input[ $key ] ) ) {
continue;
}
if ( $current && $current->$field === $input[ $key ] ) {
continue;
}
switch ( $field ) {
case 'tax_rate_priority':
case 'tax_rate_compound':
case 'tax_rate_shipping':
case 'tax_rate_order':
$data[ $field ] = $input[ $key ];
break;
case 'tax_rate_class':
$data[ $field ] = 'standard' !== $input[ $key ] ? $input[ $key ] : '';
break;
default:
$data[ $field ] = $input[ $key ];
break;
}
}
/**
* Filter tax rate data before creating/updating.
*
* @param array $data The tax rate data.
* @param array $input Request input.
*/
$data = apply_filters( "graphql_woocommerce_before_tax_rate_{$action}_data", $data, $input );
if ( ! $id ) {
$id = \WC_Tax::_insert_tax_rate( $data );
} else {
\WC_Tax::_update_tax_rate( $id, $data );
}
if ( isset( $input['cities'] ) ) {
\WC_Tax::_update_tax_rate_cities( $id, join( ';', $input['cities'] ) );
}
if ( isset( $input['postcodes'] ) ) {
\WC_Tax::_update_tax_rate_postcodes( $id, join( ';', $input['postcodes'] ) );
}
/**
* Filter tax rate object before responding.
*
* @param object $tax_rate_id The shipping method object.
* @param array $input Request input.
*/
do_action( "graphql_woocommerce_tax_rate_{$action}", $id, $input );
return [ 'tax_rate_id' => $id ];
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
/**
* Mutation - deleteTaxRate
*
* Registers mutation for deleting a tax rate.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
/**
* Class - Tax_Rate_Delete
*/
class Tax_Rate_Delete {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'deleteTaxRate',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => self::mutate_and_get_payload(),
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return [
'id' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the tax rate to update.', 'wp-graphql-woocommerce' ),
],
];
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'taxRate' => [
'type' => 'TaxRate',
'resolve' => static function ( $payload ) {
return $payload['taxRate'];
},
],
];
}
/**
* Defines the mutation data modification closure.
*
* @return callable
*/
public static function mutate_and_get_payload() {
return static function ( $input, AppContext $context, ResolveInfo $info ) {
if ( ! \wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
throw new UserError( __( 'Sorry, you are not allowed to delete tax rates.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
global $wpdb;
$id = $input['id'];
$tax = $context->get_loader( 'tax_rate' )->load( $id );
if ( ! $tax ) {
throw new UserError( __( 'Invalid tax rate ID.', 'wp-graphql-woocommerce' ) );
}
/**
* Action before deleting tax rate.
*
* @param object $tax_rate The tax rate object.
* @param array $input Request input.
*/
do_action( 'graphql_woocommerce_before_tax_rate_delete', $tax, $input );
\WC_Tax::_delete_tax_rate( $id );
if ( 0 === $wpdb->rows_affected ) {
throw new UserError( __( 'Failed to delete tax rate.', 'wp-graphql-woocommerce' ) );
}
/**
* Filter tax rate object before responding.
*
* @param object $tax_rate The shipping method object.
* @param array $input Request input.
*/
$tax = apply_filters( 'graphql_woocommerce_tax_rate_delete', $tax, $input );
return [
'taxRate' => $tax,
];
};
}
}
@@ -0,0 +1,67 @@
<?php
/**
* Mutation - updateTaxRate
*
* Registers mutation for updating a tax rate.
*
* @package WPGraphQL\WooCommerce\Mutation
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Mutation;
use WPGraphQL\AppContext;
/**
* Class - Tax_Rate_Update
*/
class Tax_Rate_Update {
/**
* Registers mutation
*
* @return void
*/
public static function register_mutation() {
register_graphql_mutation(
'updateTaxRate',
[
'inputFields' => self::get_input_fields(),
'outputFields' => self::get_output_fields(),
'mutateAndGetPayload' => [ Tax_Rate_Create::class, 'mutate_and_get_payload' ],
]
);
}
/**
* Defines the mutation input field configuration
*
* @return array
*/
public static function get_input_fields() {
return array_merge(
Tax_Rate_Create::get_input_fields(),
[
'id' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'The ID of the tax rate to update.', 'wp-graphql-woocommerce' ),
],
]
);
}
/**
* Defines the mutation output field configuration
*
* @return array
*/
public static function get_output_fields() {
return [
'taxRate' => [
'type' => 'TaxRate',
'resolve' => static function ( array $payload, array $args, AppContext $context ) {
return $context->get_loader( 'tax_rate' )->load( $payload['tax_rate_id'] );
},
],
];
}
}
@@ -101,6 +101,17 @@ class Id_Type_Enums {
]
);
register_graphql_enum_type(
'ShippingZoneIdTypeEnum',
[
'description' => __( 'The Type of Identifier used to fetch a single Shipping Zone. Default is ID.', 'wp-graphql-woocommerce' ),
'values' => [
'id' => self::get_value( 'id' ),
'database_id' => self::get_value( 'database_id' ),
],
]
);
register_graphql_enum_type(
'TaxRateIdTypeEnum',
[
@@ -0,0 +1,34 @@
<?php
/**
* WPEnum Type - ShippingLocationType
*
* @package WPGraphQL\WooCommerce\Type\WPEnum
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPEnum;
/**
* Class Shipping_Location_Type_Enum
*/
class Shipping_Location_Type_Enum {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_enum_type(
'ShippingLocationTypeEnum',
[
'description' => __( 'A Shipping zone location type.', 'wp-graphql-woocommerce' ),
'values' => [
'COUNTRY' => [ 'value' => 'country' ],
'CONTINENT' => [ 'value' => 'continent' ],
'STATE' => [ 'value' => 'state' ],
'POSTCODE' => [ 'value' => 'postcode' ],
],
]
);
}
}
@@ -0,0 +1,41 @@
<?php
/**
* WPEnum Type - WCSettingTypeEnum
*
* @package WPGraphQL\WooCommerce\Type\WPEnum
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPEnum;
/**
* Class WC_Setting_Type_Enum
*/
class WC_Setting_Type_Enum {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_enum_type(
'WCSettingTypeEnum',
[
'description' => __( 'Type of WC setting.', 'wp-graphql-woocommerce' ),
'values' => [
'TEXT' => [ 'value' => 'text' ],
'EMAIL' => [ 'value' => 'email' ],
'NUMBER' => [ 'value' => 'number' ],
'COLOR' => [ 'value' => 'color' ],
'PASSWORD' => [ 'value' => 'password' ],
'TEXTAREA' => [ 'value' => 'textarea' ],
'SELECT' => [ 'value' => 'select' ],
'MULTI_SELECT' => [ 'value' => 'multi_select' ],
'RADIO' => [ 'value' => 'radio' ],
'IMAGE_WIDTH' => [ 'value' => 'image_width' ],
'CHECKBOX' => [ 'value' => 'checkbox' ],
],
]
);
}
}
@@ -0,0 +1,38 @@
<?php
/**
* WPInputObjectType - ShippingLocationInput
*
* @package WPGraphQL\WooCommerce\Type\WPInputObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
/**
* Class Shipping_Location_Input
*/
class Shipping_Location_Input {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_input_type(
'ShippingLocationInput',
[
'description' => __( 'Shipping lines data.', 'wp-graphql-woocommerce' ),
'fields' => [
'code' => [
'type' => 'String',
'description' => __( 'Shipping location code.', 'wp-graphql-woocommerce' ),
],
'type' => [
'type' => 'ShippingLocationTypeEnum',
'description' => __( 'Shipping location type.', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
@@ -0,0 +1,38 @@
<?php
/**
* WPInputObjectType - WCSettingInput
*
* @package WPGraphQL\WooCommerce\Type\WPInputObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
/**
* Class WC_Setting_Input
*/
class WC_Setting_Input {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_input_type(
'WCSettingInput',
[
'description' => __( 'WooCommerce setting input.', 'wp-graphql-woocommerce' ),
'fields' => [
'id' => [
'type' => 'String',
'description' => __( 'A unique identifier for the setting.', 'wp-graphql-woocommerce' ),
],
'value' => [
'type' => 'String',
'description' => __( 'Setting value.', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
+50
View File
@@ -413,6 +413,10 @@ class Root_Query {
],
],
'resolve' => static function ( $source, array $args ) {
if ( ! \wc_rest_check_manager_permissions( 'shipping_methods', 'read' ) ) {
throw new UserError( __( 'Sorry, you cannot view shipping methods.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$id = isset( $args['id'] ) ? $args['id'] : null;
$id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
@@ -434,6 +438,49 @@ class Root_Query {
return Factory::resolve_shipping_method( $method_id );
},
],
'shippingZone' => [
'type' => 'ShippingZone',
'description' => __( 'A shipping zone object', 'wp-graphql-woocommerce' ),
'args' => [
'id' => [
'type' => 'ID',
'description' => __( 'The ID for identifying the shipping zone', 'wp-graphql-woocommerce' ),
],
'idType' => [
'type' => 'ShippingZoneIdTypeEnum',
'description' => __( 'Type of ID being used identify shipping zone', 'wp-graphql-woocommerce' ),
],
],
'resolve' => static function ( $source, array $args, AppContext $context ) {
if ( ! \wc_shipping_enabled() ) {
throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
}
if ( ! \wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
throw new UserError( __( 'Permission denied.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$id = isset( $args['id'] ) ? $args['id'] : null;
$id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
$zone_id = null;
switch ( $id_type ) {
case 'database_id':
$zone_id = $id;
break;
case 'global_id':
default:
$id_components = Relay::fromGlobalId( $id );
if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
}
$zone_id = $id_components['id'];
break;
}
return $context->get_loader( 'shipping_zone' )->load( $zone_id );
},
],
'taxRate' => [
'type' => 'TaxRate',
'description' => __( 'A tax rate object', 'wp-graphql-woocommerce' ),
@@ -448,6 +495,9 @@ class Root_Query {
],
],
'resolve' => static function ( $source, array $args, AppContext $context ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
throw new UserError( __( 'Sorry, you cannot view tax rates.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
}
$id = isset( $args['id'] ) ? $args['id'] : null;
$id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
@@ -0,0 +1,41 @@
<?php
/**
* WPObject Type - Shipping_Location_Type
*
* Registers ShippingLocation WPObject type and queries
*
* @package WPGraphQL\WooCommerce\Type\WPObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPObject;
/**
* Class Shipping_Location_Type
*/
class Shipping_Location_Type {
/**
* Registers shipping location type
*
* @return void
*/
public static function register() {
register_graphql_object_type(
'ShippingLocation',
[
'eagerlyLoadType' => true,
'description' => __( 'A Shipping zone object', 'wp-graphql-woocommerce' ),
'fields' => [
'code' => [
'type' => 'String',
'description' => __( 'The globally unique identifier for the tax rate.', 'wp-graphql-woocommerce' ),
],
'type' => [
'type' => 'ShippingLocationTypeEnum',
'description' => __( 'Shipping zone location name.', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
@@ -0,0 +1,140 @@
<?php
/**
* WPObject Type - Shipping_Zone_Type
*
* Registers ShippingZone WPObject type and queries
*
* @package WPGraphQL\WooCommerce\Type\WPObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPObject;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Connection\Shipping_Method_Connection_Resolver;
/**
* Class Shipping_Zone_Type
*/
class Shipping_Zone_Type {
/**
* Registers shipping zone type
*
* @return void
*/
public static function register() {
register_graphql_object_type(
'ShippingZone',
[
'description' => __( 'A Shipping zone object', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'Node' ],
'fields' => [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'The globally unique identifier for the tax rate.', 'wp-graphql-woocommerce' ),
],
'databaseId' => [
'type' => 'Int',
'description' => __( 'The ID of the customer in the database', 'wp-graphql-woocommerce' ),
],
'name' => [
'type' => 'String',
'description' => __( 'Shipping zone name.', 'wp-graphql-woocommerce' ),
],
'order' => [
'type' => 'Int',
'description' => __( 'Shipping zone order.', 'wp-graphql-woocommerce' ),
],
'locations' => [
'type' => [ 'list_of' => 'ShippingLocation' ],
'description' => __( 'Shipping zone locations.', 'wp-graphql-woocommerce' ),
],
],
'connections' => [
'methods' => [
'toType' => 'ShippingMethod',
'edgeFields' => [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'The globally unique identifier for the shipping method.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $edge ) {
if ( isset( $edge['node'] ) ) {
$shipping_method = $edge['node']->as_WC_Data();
$instance_id = $shipping_method->instance_id;
return ! empty( $instance_id ) ? \GraphQLRelay\Relay::toGlobalId( 'shipping_zone_method', $instance_id ) : null;
}
return null;
},
],
'instanceId' => [
'type' => 'Int',
'description' => __( 'Shipping method instance ID.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $edge ) {
if ( isset( $edge['node'] ) ) {
$shipping_method = $edge['node']->as_WC_Data();
return $shipping_method->instance_id ?? null;
}
return null;
},
],
'order' => [
'type' => 'Int',
'description' => __( 'The order of the shipping method.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $edge ) {
if ( isset( $edge['node'] ) ) {
$shipping_method = $edge['node']->as_WC_Data();
return $shipping_method->method_order ?? null;
}
return null;
},
],
'enabled' => [
'type' => 'Boolean',
'description' => __( 'Whether the shipping method is enabled.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $edge ) {
if ( isset( $edge['node'] ) ) {
/** @var \WC_Shipping_Method $shipping_method */
$shipping_method = $edge['node']->as_WC_Data();
return $shipping_method->is_enabled();
}
return false;
},
],
'settings' => [
'type' => [ 'list_of' => 'WCSetting' ],
'description' => __( 'Shipping method settings.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $edge ) {
$settings = [];
if ( isset( $edge['node'] ) ) {
$shipping_method = $edge['node']->as_WC_Data();
$instance_settings = $shipping_method->instance_settings;
$fields = $shipping_method->instance_form_fields;
foreach ( $fields as $key => $field ) {
$default_value = ! empty( $field['default'] ) ? $field['default'] : null;
$value = ! empty( $instance_settings[ $key ] ) ? $instance_settings[ $key ] : $default_value;
$settings[] = array_merge(
$field,
[
'id' => $key,
'value' => $value,
]
);
}
}
return $settings;
},
],
],
'resolve' => static function ( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new Shipping_Method_Connection_Resolver( $source, $args, $context, $info );
return $resolver->get_connection();
},
],
],
]
);
}
}
@@ -0,0 +1,55 @@
<?php
/**
* WPObject Type - Tax_Class_Type
*
* Registers TaxClass WPObject type
*
* @package WPGraphQL\WooCommerce\Type\WPObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPObject;
/**
* Class Tax_Class_Type
*/
class Tax_Class_Type {
/**
* Registers tax class type
*
* @return void
*/
public static function register() {
register_graphql_object_type(
'TaxClass',
[
'eagerlyLoadType' => true,
'description' => __( 'A Tax class object', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'Node' ],
'fields' => [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'The globally unique identifier for the tax class.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['slug'] ) ? \GraphQLRelay\Relay::toGlobalId( 'tax_class', $source['slug'] ) : null;
},
],
'slug' => [
'type' => 'String',
'description' => __( 'The globally unique identifier for the tax class.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['slug'] ) ? $source['slug'] : null;
},
],
'name' => [
'type' => 'String',
'description' => __( 'Tax class name.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['name'] ) ? $source['name'] : null;
},
],
],
]
);
}
}
+13 -3
View File
@@ -43,12 +43,22 @@ class Tax_Rate_Type {
'description' => __( 'State code.', 'wp-graphql-woocommerce' ),
],
'postcode' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Postcode/ZIP.', 'wp-graphql-woocommerce' ),
'type' => 'String',
'description' => __( 'Postcode/ZIP.', 'wp-graphql-woocommerce' ),
'deprecationReason' => 'Use "postcodes" instead.',
],
'city' => [
'type' => 'String',
'description' => __( 'City name.', 'wp-graphql-woocommerce' ),
'deprecationReason' => 'Use "cities" instead.',
],
'postcodes' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'City name.', 'wp-graphql-woocommerce' ),
'description' => __( 'Postcodes/ZIPs.', 'wp-graphql-woocommerce' ),
],
'cities' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'City names.', 'wp-graphql-woocommerce' ),
],
'rate' => [
'type' => 'String',
@@ -0,0 +1,89 @@
<?php
/**
* WPObject Type - WC_Setting_Type
*
* Registers WCSetting WPObject type
*
* @package WPGraphQL\WooCommerce\Type\WPObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPObject;
/**
* Class WC_Setting_Type
*/
class WC_Setting_Type {
/**
* Registers WC setting type
*
* @return void
*/
public static function register() {
register_graphql_object_type(
'WCSetting',
[
'eagerlyLoadType' => true,
'description' => __( 'A WC setting object', 'wp-graphql-woocommerce' ),
'fields' => [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'The globally unique identifier for the WC setting.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['id'] ) ? $source['id'] : null;
},
],
'label' => [
'type' => 'String',
'description' => __( 'A human readable label for the setting used in user interfaces.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['title'] ) ? $source['title'] : null;
},
],
'description' => [
'type' => 'String',
'description' => __( 'A human readable description for the setting used in user interfaces.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['description'] ) ? $source['description'] : null;
},
],
'type' => [
'type' => 'WCSettingTypeEnum',
'description' => __( 'Type of setting.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['type'] ) ? $source['type'] : null;
},
],
'value' => [
'type' => 'String',
'description' => __( 'Setting value.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['value'] ) ? $source['value'] : null;
},
],
'default' => [
'type' => 'String',
'description' => __( 'Default value for the setting.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['default'] ) ? $source['default'] : null;
},
],
'tip' => [
'type' => 'String',
'description' => __( 'Additional help text shown to the user about the setting', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['desc_tip'] ) ? $source['desc_tip'] : null;
},
],
'placeholder' => [
'type' => 'String',
'description' => __( 'Placeholder text to be displayed in text inputs.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, $context, $info ) {
return ! empty( $source['placeholder'] ) ? $source['placeholder'] : null;
},
],
],
]
);
}
}
+41 -8
View File
@@ -17,10 +17,12 @@ class ShippingZoneFactory extends \WP_UnitTest_Factory_For_Thing {
public function __construct( $factory = null ) {
parent::__construct( $factory );
$this->default_generation_definitions = [
'zone_name' => '',
];
$this->dummy = Dummy::instance();
$this->default_generation_definitions = [
'zone_name' => 'Test Shipping Zone ' . $this->dummy->number(),
'zone_order' => 0,
];
}
public function create_object( $args ) {
@@ -60,9 +62,37 @@ class ShippingZoneFactory extends \WP_UnitTest_Factory_For_Thing {
}
public function update_object( $object, $fields ) {
if ( ! $object instanceof \WC_Customer && 0 !== absint( $object ) ) {
if ( ! $object instanceof \WC_Shipping_Zone && 0 !== absint( $object ) ) {
$object = $this->get_object_by_id( $object );
}
if ( ! empty( $fields['countries'] ) ) {
foreach ( $fields['countries'] as $country ) {
$object->add_location( $country, 'country' );
}
}
if ( ! empty( $fields['states'] ) ) {
foreach ( $fields['states'] as $state ) {
$object->add_location( $state, 'state' );
}
}
if ( ! empty( $fields['postcode'] ) ) {
foreach ( $fields['postcode'] as $postcode ) {
$object->add_location( $postcode, 'postcode' );
}
}
if ( ! empty( $fields['shipping_method'] ) ) {
$object->add_shipping_method( $fields['shipping_method'] );
}
foreach ( $fields as $key => $value ) {
if ( is_callable( [ $object, "set_{$key}" ] ) ) {
$object->{"set_{$key}"}( $value );
}
}
return $object->save();
}
public function get_object_by_id( $id ) {
@@ -81,6 +111,11 @@ class ShippingZoneFactory extends \WP_UnitTest_Factory_For_Thing {
return false;
}
public function reloadShippingMethods() {
\WC_Cache_Helper::get_transient_version( 'shipping', true );
WC()->shipping()->load_shipping_methods();
}
public function createLegacyFlatRate( $args = [] ) {
$flat_rate_settings = array_merge(
[
@@ -95,8 +130,7 @@ class ShippingZoneFactory extends \WP_UnitTest_Factory_For_Thing {
);
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
update_option( 'woocommerce_flat_rate', [] );
\WC_Cache_Helper::get_transient_version( 'shipping', true );
\WC()->shipping()->load_shipping_methods();
$this->reloadShippingMethods();
return 'legacy_flat_rate';
}
@@ -113,8 +147,7 @@ class ShippingZoneFactory extends \WP_UnitTest_Factory_For_Thing {
);
update_option( 'woocommerce_free_shipping_settings', $free_shipping_settings );
update_option( 'woocommerce_free_shipping', [] );
\WC_Cache_Helper::get_transient_version( 'shipping', true );
WC()->shipping()->load_shipping_methods();
$this->reloadShippingMethods();
return 'legacy_free_shipping';
}
@@ -0,0 +1,58 @@
<?php
/**
* Factory class for the WooCommerce's tax class data objects.
*
* @since TBD
* @package Tests\WPGraphQL\WooCommerce\Factory
*/
namespace Tests\WPGraphQL\WooCommerce\Factory;
use Tests\WPGraphQL\WooCommerce\Utils\Dummy;
/**
* Tax Class factory class for testing.
*/
class TaxClassFactory extends \WP_UnitTest_Factory_For_Thing {
public function __construct( $factory = null ) {
parent::__construct( $factory );
$this->default_generation_definitions = [
'name' => '',
'slug' => '',
];
$this->dummy = Dummy::instance();
}
public function create_object( $args = [] ) {
$name = ! empty( $args['name'] ) ? $args['name'] : 'TaxClassNo' . Dummy::instance()->number();
$slug = ! empty( $args['slug'] ) ? $args['slug'] : '';
$tax_class = \WC_Tax::create_tax_class( $name, $slug );
if ( is_wp_error( $tax_class ) ) {
\codecept_debug( $tax_class->get_error_message() );
throw new \Exception( $tax_class->get_error_message() );
}
return $tax_class;
}
public function update_object( $object, $fields ) {
throw new \Exception( 'You doing it wrong. You can only create or delete tax classes.' );
}
public function get_object_by_id( $slug ) {
$tax_class = \WC_Tax::get_tax_class_by( 'slug', $slug );
if ( ! $tax_class ) {
return null;
}
if ( is_wp_error( $tax_class ) ) {
\codecept_debug( $tax_class->get_error_message() );
return null;
}
return $tax_class;
}
}
@@ -42,6 +42,7 @@ class WooGraphQLTestCase extends \Tests\WPGraphQL\TestCase\WPGraphQLTestCase {
'Coupon',
'Customer',
'ShippingZone',
'TaxClass',
'TaxRate',
'Order',
'Refund',
+48 -63
View File
@@ -2,24 +2,10 @@
use GraphQLRelay\Relay;
class ShippingMethodQueriesTest extends \Codeception\TestCase\WPTestCase {
private $shop_manager;
private $customer;
private $method;
private $helper;
class ShippingMethodQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function setUp(): void {
parent::setUp();
$this->shop_manager = $this->factory->user->create( [ 'role' => 'shop_manager' ] );
$this->customer = $this->factory->user->create( [ 'role' => 'customer' ] );
$this->helper = $this->getModule( '\Helper\Wpunit' )->shipping_method();
$this->method = 'flat_rate';
}
// tests
public function testShippingMethodQueryAndArgs() {
$id = Relay::toGlobalId( 'shipping_method', $this->method );
$id = Relay::toGlobalId( 'shipping_method', 'flat_rate' );
$query = '
query( $id: ID!, $idType: ShippingMethodIdTypeEnum ) {
@@ -35,61 +21,46 @@ class ShippingMethodQueriesTest extends \Codeception\TestCase\WPTestCase {
/**
* Assertion One
*
* Test "ID" ID type.
* Confirm permission check is working
*/
$variables = [
'id' => $id,
'idType' => 'ID',
];
$actual = graphql(
[
'query' => $query,
'variables' => $variables,
]
);
$expected = [ 'data' => [ 'shippingMethod' => $this->helper->print_query( $this->method ) ] ];
$variables = [ 'id' => $id ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// use --debug flag to view.
codecept_debug( $actual );
$this->assertQueryError( $response );
$this->assertEquals( $expected, $actual );
// Login as shop manager.
$this->loginAsShopManager();
/**
* Assertion Two
*
* Test "ID" ID type.
*/
$variables = [ 'id' => $id ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField( 'shippingMethod.id', $id ),
$this->expectedField( 'shippingMethod.databaseId', 'flat_rate' ),
$this->expectedField( 'shippingMethod.title', self::NOT_NULL ),
$this->expectedField( 'shippingMethod.description', self::NOT_NULL ),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Three
*
* Test "DATABASE_ID" ID type.
*/
$variables = [
'id' => $this->method,
'idType' => 'DATABASE_ID',
];
$actual = graphql(
[
'query' => $query,
'variables' => $variables,
]
);
$expected = [ 'data' => [ 'shippingMethod' => $this->helper->print_query( $this->method ) ] ];
$variables = [ 'id' => 'flat_rate', 'idType' => 'DATABASE_ID' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// use --debug flag to view.
codecept_debug( $actual );
$this->assertEquals( $expected, $actual );
$this->assertQuerySuccessful( $response, $expected );
}
public function testShippingMethodsQuery() {
$wc_shipping = WC_Shipping::instance();
$methods = array_values(
array_map(
static function ( $method ) {
return [ 'id' => Relay::toGlobalId( 'shipping_method', $method->id ) ];
},
$wc_shipping->get_shipping_methods()
)
);
$query = '
query shippingMethodsQuery {
query {
shippingMethods {
nodes {
id
@@ -98,17 +69,31 @@ class ShippingMethodQueriesTest extends \Codeception\TestCase\WPTestCase {
}
';
/**
* Assertion One
*
* Confirm permission check is working
*/
$response = $this->graphql( compact( 'query' ) );
$this->assertQuerySuccessful( $response, [ $this->expectedField( 'shippingMethods.nodes', self::IS_FALSY ) ] );
// Login as shop manager.
$this->loginAsShopManager();
/**
* Assertion One
*
* Tests query
*/
$actual = do_graphql_request( $query, 'shippingMethodQuery' );
$expected = [ 'data' => [ 'shippingMethods' => [ 'nodes' => $methods ] ] ];
$response = $this->graphql( compact( 'query' ) );
$expected = array_map(
function( $method ) {
return $this->expectedField( 'shippingMethods.nodes.#.id', $this->toRelayId( 'shipping_method', $method->id ) );
},
array_values( WC_Shipping::instance()->get_shipping_methods() )
);
// use --debug flag to view.
codecept_debug( $actual );
$this->assertEquals( $expected, $actual );
$this->assertQuerySuccessful( $response, $expected );
}
}
+676
View File
@@ -0,0 +1,676 @@
<?php
class ShippingZoneMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function testCreateShippingZoneMutation() {
// Prepare the request.
$query = 'mutation ($input: CreateShippingZoneInput!) {
createShippingZone(input: $input) {
shippingZone {
id
name
order
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'name' => 'Test Shipping Zone',
'order' => 0
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'createShippingZone.shippingZone',
[
$this->expectedField( 'id', self::NOT_NULL ),
$this->expectedField( 'name', 'Test Shipping Zone' ),
$this->expectedField( 'order', 0 ),
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testUpdateShippingZoneMutation() {
// Create a shipping zone.
$shipping_zone_id = $this->factory->shipping_zone->create(
[
'zone_name' => 'Test Shipping Zone',
'zone_order' => 0,
]
);
// Prepare the request.
$query = 'mutation ($input: UpdateShippingZoneInput!) {
updateShippingZone(input: $input) {
shippingZone {
id
databaseId
name
order
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'id' => $shipping_zone_id,
'name' => 'Updated Shipping Zone',
'order' => 1
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'updateShippingZone.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone_id ) ),
$this->expectedField( 'databaseId', $shipping_zone_id ),
$this->expectedField( 'name', 'Updated Shipping Zone' ),
$this->expectedField( 'order', 1 ),
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testDeleteShippingZoneMutation() {
// Create a shipping zone.
$shipping_zone = $this->factory->shipping_zone->create_and_get();
// Prepare the request.
$query = 'mutation ($input: DeleteShippingZoneInput!) {
deleteShippingZone(input: $input) {
shippingZone {
id
databaseId
name
order
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'id' => $shipping_zone->get_id()
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'deleteShippingZone.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ),
$this->expectedField( 'databaseId', $shipping_zone->get_id() ),
$this->expectedField( 'name', $shipping_zone->get_zone_name() ),
$this->expectedField( 'order', $shipping_zone->get_zone_order() ),
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testUpdateShippingZoneLocationsMutation() {
// Create a shipping zone.
$shipping_zone_id = $this->factory->shipping_zone->create();
// Prepare the request.
$query = 'mutation ($input: UpdateShippingZoneLocationsInput!) {
updateShippingZoneLocations(input: $input) {
shippingZone {
id
locations {
code
type
}
}
locations {
code
type
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'zoneId' => $shipping_zone_id,
'locations' => [
[
'code' => 'US',
],
[
'code' => 'CALIFORNIA',
'type' => 'STATE',
],
[
'code' => '12345',
'type' => 'POSTCODE',
],
[
'code' => 'NA',
'type' => 'CONTINENT',
],
]
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'updateShippingZoneLocations.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone_id ) ),
$this->expectedObject(
'locations.0',
[
$this->expectedField( 'code', 'US' ),
$this->expectedField( 'type', 'COUNTRY' )
]
),
$this->expectedObject(
'locations.1',
[
$this->expectedField( 'code', 'CALIFORNIA' ),
$this->expectedField( 'type', 'STATE' )
]
),
$this->expectedObject(
'locations.2',
[
$this->expectedField( 'code', '12345' ),
$this->expectedField( 'type', 'POSTCODE' )
]
),
$this->expectedObject(
'locations.3',
[
$this->expectedField( 'code', 'NA' ),
$this->expectedField( 'type', 'CONTINENT' )
]
)
]
),
$this->expectedObject(
'updateShippingZoneLocations.locations.0',
[
$this->expectedField( 'code', 'US' ),
$this->expectedField( 'type', 'COUNTRY' )
]
),
$this->expectedObject(
'updateShippingZoneLocations.locations.1',
[
$this->expectedField( 'code', 'CALIFORNIA' ),
$this->expectedField( 'type', 'STATE' )
]
),
$this->expectedObject(
'updateShippingZoneLocations.locations.2',
[
$this->expectedField( 'code', '12345' ),
$this->expectedField( 'type', 'POSTCODE' )
]
),
$this->expectedObject(
'updateShippingZoneLocations.locations.3',
[
$this->expectedField( 'code', 'NA' ),
$this->expectedField( 'type', 'CONTINENT' )
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testClearShippingZoneLocationsMutation() {
// Create a shipping zone.
$shipping_zone = $this->factory->shipping_zone->create_and_get();
// Add a location to the shipping zone.
$shipping_zone->add_location( 'US', 'country' );
$shipping_zone->save();
// Prepare the request.
$query = 'mutation ($input: ClearShippingZoneLocationsInput!) {
clearShippingZoneLocations(input: $input) {
shippingZone {
id
locations {
code
}
}
removedLocations {
code
type
}
}
}';
// Prepare the variables.
$variables = [
'input' => [ 'zoneId' => $shipping_zone->get_id() ]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'clearShippingZoneLocations.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ),
$this->expectedField( 'locations', self::IS_FALSY )
]
),
$this->expectedObject(
'clearShippingZoneLocations.removedLocations.0',
[
$this->expectedField( 'code', 'US' ),
$this->expectedField( 'type', 'COUNTRY' )
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testAddMethodToShippingZoneMutation() {
// Create a shipping zone.
$shipping_zone_id = $this->factory->shipping_zone->create();
// Prepare the request.
$query = 'mutation ($input: AddMethodToShippingZoneInput!) {
addMethodToShippingZone(input: $input) {
shippingZone {
id
methods {
edges {
id
instanceId
order
enabled
settings {
id
label
description
type
value
default
tip
placeholder
}
node {
id
databaseId
title
description
}
}
}
}
method {
id
instanceId
order
enabled
settings {
id
label
description
type
value
default
tip
placeholder
}
node {
id
databaseId
title
description
}
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'zoneId' => $shipping_zone_id,
'methodId' => 'flat_rate',
'order' => 0,
'enabled' => true,
'settings' => [
[
'id' => 'cost',
'value' => '10'
]
]
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'addMethodToShippingZone.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone_id ) ),
$this->expectedNode(
'methods.edges',
[
$this->expectedField( 'id', self::NOT_NULL ),
$this->expectedField( 'instanceId', self::NOT_NULL ),
$this->expectedField( 'order', 0 ),
$this->expectedField( 'enabled', true ),
$this->expectedNode(
'settings',
[
$this->expectedField( 'id', 'cost' ),
$this->expectedField( 'value', '10' )
]
),
$this->expectedObject(
'node',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_method', 'flat_rate' ) ),
$this->expectedField( 'databaseId', self::NOT_NULL ),
$this->expectedField( 'title', self::NOT_NULL ),
]
)
],
0
),
]
),
$this->expectedObject(
'addMethodToShippingZone.method',
[
$this->expectedField( 'id', self::NOT_NULL ),
$this->expectedField( 'instanceId', self::NOT_NULL ),
$this->expectedField( 'order', 0 ),
$this->expectedField( 'enabled', true ),
$this->expectedNode(
'settings',
[
$this->expectedField( 'id', 'cost' ),
$this->expectedField( 'value', '10' )
]
),
$this->expectedObject(
'node',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_method', 'flat_rate' ) ),
$this->expectedField( 'databaseId', self::NOT_NULL ),
$this->expectedField( 'title', self::NOT_NULL ),
]
)
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testUpdateMethodOnShippingZoneMutation() {
// Create a shipping zone.
$shipping_zone = $this->factory->shipping_zone->create_and_get();
$instance_id = $shipping_zone->add_shipping_method( 'flat_rate' );
// Prepare the request.
$query = 'mutation ($input: UpdateMethodOnShippingZoneInput!) {
updateMethodOnShippingZone(input: $input) {
shippingZone {
id
methods {
edges {
id
instanceId
order
enabled
settings {
id
label
description
type
value
default
tip
placeholder
}
node {
id
title
description
}
}
}
}
method {
id
instanceId
order
enabled
settings {
id
label
description
type
value
default
tip
placeholder
}
node {
id
title
description
}
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'zoneId' => $shipping_zone->get_id(),
'instanceId' => $instance_id,
'settings' => [
[
'id' => 'cost',
'value' => '10'
]
],
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'updateMethodOnShippingZone.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ),
$this->expectedNode(
'methods.edges',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone_method', $instance_id ) ),
$this->expectedField( 'instanceId', $instance_id ),
$this->expectedField( 'order', self::NOT_NULL ),
$this->expectedField( 'enabled', true ),
$this->expectedNode(
'settings',
[
$this->expectedField( 'id', 'cost' ),
$this->expectedField( 'value', '10' )
]
),
$this->expectedField( 'node.id', $this->toRelayId( 'shipping_method', 'flat_rate' ) ),
],
0
),
]
),
$this->expectedObject(
'updateMethodOnShippingZone.method',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone_method', $instance_id ) ),
$this->expectedField( 'instanceId', $instance_id ),
$this->expectedField( 'order', self::NOT_NULL ),
$this->expectedField( 'enabled', true ),
$this->expectedNode(
'settings',
[
$this->expectedField( 'id', 'cost' ),
$this->expectedField( 'value', '10' )
]
),
$this->expectedField( 'node.id', $this->toRelayId( 'shipping_method', 'flat_rate' ) ),
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testRemoveMethodFromShippingZoneMutation() {
// Create a shipping zone and add the shipping method.
$shipping_zone = $this->factory->shipping_zone->create_and_get();
$instance_id = $shipping_zone->add_shipping_method( 'flat_rate' );
$shipping_method = new \WC_Shipping_Flat_Rate( $instance_id );
// Prepare the request.
$query = 'mutation ($input: RemoveMethodFromShippingZoneInput!) {
removeMethodFromShippingZone(input: $input) {
shippingZone {
id
methods {
edges {
order
enabled
node { id }
}
}
}
removedMethod {
order
enabled
node { id }
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'zoneId' => $shipping_zone->get_id(),
'instanceId' => $instance_id
]
];
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
// Confirm permissions error.
$this->assertQueryError( $response );
// Login as admin and re-execute expecting success.
$this->loginAsShopManager();
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'removeMethodFromShippingZone.shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ),
$this->expectedField( 'methods.edges', self::IS_FALSY ),
]
),
$this->expectedObject(
'removeMethodFromShippingZone.removedMethod',
[
$this->expectedField( 'enabled', $shipping_method->is_enabled() ),
$this->expectedNode(
'node',
[ $this->expectedField( 'id', $this->toRelayId( 'shipping_method', $shipping_method->id ) ) ]
)
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
}
+168
View File
@@ -0,0 +1,168 @@
<?php
class ShippingZoneQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function testShippingZoneQuery() {
// Create a shipping zone.
$shipping_zone = $this->factory->shipping_zone->create_and_get();
// Add location.
$shipping_zone->add_location( 'US', 'country' );
$shipping_zone->save();
// Add shipping method.
$instance_id = $shipping_zone->add_shipping_method( 'flat_rate' );
$shipping_method = null;
foreach ( $shipping_zone->get_shipping_methods() as $method ) {
if ( $method->instance_id === $instance_id ) {
$shipping_method = $method;
break;
}
}
$instance_settings = $shipping_method->instance_settings;
$instance_settings['cost'] = 10.00;
update_option( $shipping_method->get_instance_option_key(), $instance_settings );
// Prepare the request.
$query = 'query ($id: ID!) {
shippingZone(id: $id) {
id
name
order
locations {
code
type
}
methods {
edges {
id
instanceId
order
enabled
settings {
id
label
description
type
value
default
tip
placeholder
}
node {
id
title
description
}
}
}
}
}';
// Prepare the variables.
$variables = [ 'id' => $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ];
// Execute the request expecting failure.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request expecting success.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'shippingZone',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zone->get_id() ) ),
$this->expectedField( 'name', $shipping_zone->get_zone_name() ),
$this->expectedField( 'order', $shipping_zone->get_zone_order() ),
$this->expectedNode(
'locations',
[
$this->expectedField( 'code', 'US' ),
$this->expectedField( 'type', 'COUNTRY' )
]
),
$this->expectedNode(
'methods.edges',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone_method', $instance_id ) ),
$this->expectedField( 'instanceId', $instance_id ),
$this->expectedField( 'order', $shipping_method->method_order ),
$this->expectedField( 'enabled', $shipping_method->is_enabled() ),
$this->expectedNode(
'settings',
[
$this->expectedField( 'id', 'cost' ),
$this->expectedField( 'label', 'Cost' ),
$this->expectedField( 'description', self::NOT_FALSY ),
$this->expectedField( 'type', 'TEXT' ),
$this->expectedField( 'value', '10' ),
$this->expectedField( 'default', self::IS_NULL ),
$this->expectedField( 'placeholder', self::IS_NULL ),
],
)
],
0
)
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testShippingZonesQuery() {
// Create a shipping zones.
$shipping_zones = $this->factory->shipping_zone->create_many( 3 );
// Prepare the request.
$query = 'query {
shippingZones {
nodes {
id
}
}
}';
/**
* Assertion One
*
* Confirm permission check is working
*/
$response = $this->graphql( compact( 'query' ) );
$this->assertQuerySuccessful( $response, [ $this->expectedField( 'shippingZones.nodes', self::IS_FALSY ) ] );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query' ) );
$expected = [
$this->expectedNode(
'shippingZones.nodes',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zones[0] ) )
]
),
$this->expectedNode(
'shippingZones.nodes',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zones[1] ) )
]
),
$this->expectedNode(
'shippingZones.nodes',
[
$this->expectedField( 'id', $this->toRelayId( 'shipping_zone', $shipping_zones[2] ) )
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
class TaxClassMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function testCreateTaxClassMutation() {
// Prepare the request.
$query = 'mutation ($input: CreateTaxClassInput!) {
createTaxClass(input: $input) {
taxClass {
name
slug
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'name' => 'Test Tax Class',
'slug' => 'test-tax-class'
]
];
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'createTaxClass.taxClass',
[
$this->expectedField( 'name', 'Test Tax Class' ),
$this->expectedField( 'slug', 'test-tax-class' )
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testDeleteTaxClassMutation() {
// Create a tax class.
$tax_class = $this->factory->tax_class->create();
// Prepare the request.
$query = 'mutation ($input: DeleteTaxClassInput!) {
deleteTaxClass(input: $input) {
taxClass {
name
slug
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'slug' => $tax_class['slug']
]
];
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'deleteTaxClass.taxClass',
[
$this->expectedField( 'name', $tax_class['name'] ),
$this->expectedField( 'slug', $tax_class['slug'] )
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
// Ensure the tax class was deleted.
$tax_class = $this->factory->tax_class->get_object_by_id( $tax_class['slug'] );
$this->assertNull( $tax_class );
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
class TaxClassQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function testTaxClassesQuery() {
// Create tax classes.
$tax_classes = $this->factory->tax_class->create_many( 2 );
// Prepare the request.
$query = '{
taxClasses {
nodes {
name
slug
}
}
}';
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query' ) );
$this->assertQuerySuccessful( $response, [ $this->expectedField( 'taxClasses.nodes', self::IS_FALSY ) ] );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query' ) );
$expected = [
$this->expectedNode(
'taxClasses.nodes',
[
$this->expectedField( 'name', $tax_classes[0]['name'] ),
$this->expectedField( 'slug', $tax_classes[0]['slug'] ),
]
),
$this->expectedNode(
'taxClasses.nodes',
[
$this->expectedField( 'name', $tax_classes[1]['name'] ),
$this->expectedField( 'slug', $tax_classes[1]['slug'] ),
]
),
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
use WPGraphQL\Type\WPEnumType;
class TaxRateMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function testCreateTaxRateMutation() {
// Create tax class.
$tax_class = $this->factory->tax_class->create();
// Prepare the request.
$query = 'mutation ($input: CreateTaxRateInput!) {
createTaxRate(input: $input) {
taxRate {
id
rate
country
state
postcode
city
postcodes
cities
priority
compound
shipping
order
class
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'rate' => '10',
'country' => 'US',
'state' => 'CA',
'postcodes' => [ '12345', '67890' ],
'cities' => [ 'Los Angeles', 'San Francisco' ],
'priority' => 1,
'compound' => false,
'shipping' => false,
'order' => 0,
'class' => WPEnumType::get_safe_name( $tax_class['slug'] )
]
];
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'createTaxRate.taxRate',
[
$this->expectedField( 'id', self::NOT_NULL ),
$this->expectedField( 'rate', self::NOT_FALSY ),
$this->expectedField( 'country', 'US' ),
$this->expectedField( 'state', 'CA' ),
$this->expectedField( 'postcode', '67890' ),
$this->expectedField( 'postcodes.0', '12345' ),
$this->expectedField( 'postcodes.1', '67890' ),
$this->expectedField( 'city', 'SAN FRANCISCO' ),
$this->expectedField( 'cities.0', 'LOS ANGELES' ),
$this->expectedField( 'cities.1', 'SAN FRANCISCO' ),
$this->expectedField( 'priority', 1 ),
$this->expectedField( 'compound', false ),
$this->expectedField( 'shipping', false ),
$this->expectedField( 'order', 0 ),
$this->expectedField( 'class', WPEnumType::get_safe_name( $tax_class['slug'] ) )
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testUpdateTaxRateMutation() {
// Create a tax class.
$old_tax_class = $this->factory->tax_class->create();
$new_tax_class = $this->factory->tax_class->create();
// Create a tax rate.
$tax_rate_id = $this->factory->tax_rate->create( [ 'class' => $old_tax_class['slug'] ] );
// Prepare the request.
$query = 'mutation ($input: UpdateTaxRateInput!) {
updateTaxRate(input: $input) {
taxRate {
id
rate
country
state
postcodes
cities
priority
compound
shipping
order
class
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'id' => $tax_rate_id,
'rate' => '20',
'country' => 'US',
'state' => 'NY',
'postcodes' => [ '54321', '09876' ],
'cities' => [ 'New York', 'Buffalo' ],
'priority' => 2,
'compound' => true,
'shipping' => true,
'order' => 1,
'class' => WPEnumType::get_safe_name( $new_tax_class['slug'] )
]
];
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'updateTaxRate.taxRate',
[
$this->expectedField( 'id', $this->toRelayId( 'tax_rate', $tax_rate_id ) ),
$this->expectedField( 'rate', self::NOT_FALSY ),
$this->expectedField( 'country', 'US' ),
$this->expectedField( 'state', 'NY' ),
$this->expectedField( 'postcodes.0', '54321' ),
$this->expectedField( 'postcodes.1', '09876' ),
$this->expectedField( 'cities.0', 'NEW YORK' ),
$this->expectedField( 'cities.1', 'BUFFALO' ),
$this->expectedField( 'priority', 2 ),
$this->expectedField( 'compound', true ),
$this->expectedField( 'shipping', true ),
$this->expectedField( 'order', 1 ),
$this->expectedField( 'class', WPEnumType::get_safe_name( $new_tax_class['slug'] ) ),
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
}
public function testDeleteTaxRateMutation() {
// Create a tax rate.
$tax_rate = $this->factory->tax_rate->create_and_get(
[
'rate' => '30',
'country' => 'US',
'state' => 'TX',
'class' => 'zero-rate',
]
);
// Prepare the request.
$query = 'mutation ($input: DeleteTaxRateInput!) {
deleteTaxRate(input: $input) {
taxRate {
id
rate
country
state
class
}
}
}';
// Prepare the variables.
$variables = [
'input' => [
'id' => absint( $tax_rate->tax_rate_id ),
]
];
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
// Execute the request.
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedObject(
'deleteTaxRate.taxRate',
[
$this->expectedField( 'id', $this->toRelayId( 'tax_rate', $tax_rate->tax_rate_id ) ),
$this->expectedField( 'rate', self::NOT_FALSY ),
$this->expectedField( 'country', 'US' ),
$this->expectedField( 'state', 'TX' ),
$this->expectedField( 'class', 'ZERO_RATE' ),
]
)
];
// Validate the response.
$this->assertQuerySuccessful( $response, $expected );
// Ensure the tax rate has been deleted.
$tax_rate = $this->factory->tax_rate->get_object_by_id( $tax_rate->tax_rate_id );
$this->assertNull( $tax_rate );
}
}
+17 -2
View File
@@ -9,8 +9,8 @@ class TaxRateQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
$this->expectedField( 'taxRate.databaseId', absint( $rate->tax_rate_id ) ),
$this->expectedField( 'taxRate.country', ! empty( $rate->tax_rate_country ) ? $rate->tax_rate_country : self::IS_NULL ),
$this->expectedField( 'taxRate.state', ! empty( $rate->tax_rate_state ) ? $rate->tax_rate_state : self::IS_NULL ),
$this->expectedField( 'taxRate.postcode', ! empty( $rate->tax_rate_postcode ) ? $rate->tax_rate_postcode : [ '*' ] ),
$this->expectedField( 'taxRate.city', ! empty( $rate->tax_rate_city ) ? $rate->tax_rate_city : [ '*' ] ),
$this->expectedField( 'taxRate.postcode', ! empty( $rate->tax_rate_postcode ) ? $rate->tax_rate_postcode : '*' ),
$this->expectedField( 'taxRate.city', ! empty( $rate->tax_rate_city ) ? $rate->tax_rate_city : '*' ),
$this->expectedField( 'taxRate.rate', ! empty( $rate->tax_rate ) ? $rate->tax_rate : self::IS_NULL ),
$this->expectedField( 'taxRate.name', ! empty( $rate->tax_rate_name ) ? $rate->tax_rate_name : self::IS_NULL ),
$this->expectedField( 'taxRate.priority', absint( $rate->tax_rate_priority ) ),
@@ -50,6 +50,14 @@ class TaxRateQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
}
';
// Execute the request expecting failure due to missing permissions.
$variables = [ 'id' => $this->toRelayId( 'tax_rate', $rate ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$this->assertQueryError( $response );
// Login as shop manager.
$this->loginAsShopManager();
/**
* Assertion One
*
@@ -119,6 +127,13 @@ class TaxRateQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
}
';
// Execute the request expecting failure due to missing permissions.
$response = $this->graphql( compact( 'query' ) );
$this->assertQuerySuccessful( $response, [ $this->expectedField( 'taxRates.nodes', self::IS_FALSY ) ] );
// Login as shop manager.
$this->loginAsShopManager();
/**
* Assertion One
*