Skip to content

Commit

Permalink
Feat/pricing fields (#293)
Browse files Browse the repository at this point in the history
* Feat: Refactor FormField registration (#273)

* feat!: refactor form field settings to use interfaces

* feat!: refactor form field choice and inputs to use interfaces

* chore: cleanup

* chore: rename types

* chore: cleanup filters

* docs: add new actions/filters

* chore: update composer deps

* fix: remove duplicate $ignored_field values.

* fix: use the correct GF_Field for child types with their own classes

* chore: order value methods alphabetically

* feat: graduate product Field (query)

* feat: gradiuate `option`, `price`, `quantity`, `shipping`, and `total` fields (query

* fix: set fallback product quantity to 1

* fix: change quantity GraphQL type to float

* chore: fix composer deps post rebase

* feat: add Product mutation support

* feat: add price and formattedPrice to QuantitySelectFieldChoice

* tests: quantity field tests

* fix: docblock headers

* test: reset $_gf_state by setting to empty array

* fix: option choice type name should resolve to OptionFieldChoice

* test: option field tests

* test: shipping field tests

* fix: use updated entry when preparing values for save

* feat: add support for total field

* test: lint
  • Loading branch information
justlevine authored Aug 30, 2022
1 parent 1888be8 commit fe69948
Show file tree
Hide file tree
Showing 53 changed files with 6,375 additions and 240 deletions.
6 changes: 6 additions & 0 deletions src/Data/EntryObjectMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ public static function get_field_value_input( array $args, array $form, bool $is
case 'post_image':
$field_value_input = FieldValueInput\ImageValuesInput::class;
break;
case 'hiddenproduct':
case 'singleproduct':
case 'calculation':
$field_value_input = FieldValueInput\ProductValueInput::class;
break;
case 'date':
case 'hidden':
case 'number':
case 'phone':
case 'post_content':
case 'post_excerpt':
case 'post_title':
case 'price':
case 'radio':
case 'select':
case 'text':
Expand Down
106 changes: 106 additions & 0 deletions src/Data/FieldValueInput/ProductValueInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/**
* Manipulates input data for Product field values.
*
* @package WPGraphQL\GF\Data\FieldValueInput
* @since @todo
*/

namespace WPGraphQL\GF\Data\FieldValueInput;

use GFCommon;
use GraphQL\Error\UserError;

/**
* Class - ProductValueInput
*/
class ProductValueInput extends AbstractFieldValueInput {
/**
* {@inheritDoc}
*
* @var array
*/
protected $args;

/**
* {@inheritDoc}
*
* @var array
*/
public $value;

/**
* {@inheritDoc}
*/
protected function get_field_name() : string {
return 'productValues';
}

/**
* {@inheritDoc}
*
* @throws UserError .
*/
protected function is_valid_input_type() : bool {
$is_valid = false;

// Calculation fields need a quantity and price.
if ( 'calculation' === $this->field->get_input_type() && ( ! isset( $this->input_args[ $this->field_name ]['quantity'] ) || ! isset( $this->input_args[ $this->field_name ]['price'] ) ) ) {
return $is_valid;
}

// Fields using `productValues` need a quantity.
if ( isset( $this->input_args[ $this->field_name ] ) && isset( $this->input_args[ $this->field_name ]['quantity'] ) ) {
return true;
}

// Fields using `value` must use it as the quantity.
if ( isset( $this->input_args['value'] ) ) {
if ( ! floatval( $this->input_args['value'] ) ) {
throw new UserError(
sprintf(
// translators: field ID, input key.
__( 'Mutation not processed. Field %1$s requires the use of `%2$s` as a valid quantity.', 'wp-graphql-gravity-forms' ),
$this->field->id,
'value',
)
);
}
return true;
}

return $is_valid;
}

/**
* {@inheritDoc}
*
* @return array
*/
public function get_args() {
return $this->input_args[ $this->field_name ] ?? [
'quantity' => $this->input_args['value'],
'price' => null,
];
}

/**
* {@inheritDoc}
*/
protected function prepare_value() {
$field = $this->field;

return [
(string) $field->inputs[0]['id'] => $field->label,
(string) $field->inputs[1]['id'] => ! empty( $this->args['price'] ) ? GFCommon::format_number( $this->args['price'], 'currency' ) : $field->basePrice,
(string) $field->inputs[2]['id'] => floatval( $this->args['quantity'] ),
];
}

/**
* {@inheritDoc}
*/
public function add_value_to_submission( array &$field_values ) : void {
$field_values += $this->value;
}
}
31 changes: 31 additions & 0 deletions src/Data/FieldValueInput/ValueInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,45 @@

namespace WPGraphQL\GF\Data\FieldValueInput;

use GFCommon;
/**
* Class - ValueInput
*/
class ValueInput extends AbstractFieldValueInput {
/**
* {@inheritDoc}
*
* @var string
*/
protected $args;

/**
* {@inheritDoc}
*/
protected function get_field_name() : string {
return 'value';
}

/**
* {@inheritDoc}
*/
protected function prepare_value() : string {
// Handle choices with price.
if ( property_exists( $this->field, 'enablePrice' ) && $this->field->enablePrice && false === strpos( $this->args, '|' ) ) {
$value_key = property_exists( $this->field, 'enableChoiceValue' ) && $this->field->enableChoiceValue ? 'value' : 'text';

$choice_key = array_search( $this->args, array_column( $this->field->choices, $value_key ), true );

$choice = $this->field->choices[ $choice_key ];

$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );

return $this->args . '|' . $price;
} elseif ( 'total' === $this->field->type ) {
// Convert to number so draft updates dont return the currency.
return GFCommon::to_number( $this->args );
}

return $this->args;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class SignatureValuesInput extends ValueInput {
/**
* {@inheritDoc}
*/
protected function prepare_value() {
protected function prepare_value() : string {
$value = $this->args;

$this->ensure_signatures_folder_exists();
Expand Down
3 changes: 3 additions & 0 deletions src/Mutation/UpdateEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ private static function prepare_field_values( array $field_values, array $entry,
* @param array $form the existing form.
*/
public static function prepare_field_values_for_save( array $values, array $entry, array $form ) : array {
// We need the entry fresh to prepare the values.
$entry = array_merge( $entry, $values );

foreach ( $values as $id => &$value ) {
$input_name = 'input_' . str_replace( '.', '_', $id );
$field_id = strtok( $id, '.' );
Expand Down
5 changes: 4 additions & 1 deletion src/Registry/FieldChoiceRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ public static function get_type_name( GF_Field $field ) : string {
$input_type = $field->get_input_type();

switch ( true ) {
// Post Category choices are on the interface.
// Some choices belong on the the interface.
case 'post_category' === $field->type:
$input_name = 'PostCategoryFieldChoice';
break;
case 'option' === $field->type:
$input_name = 'OptionFieldChoice';
break;
default:
$input_name = ( $field->type !== $input_type ? $field->type . '_' . $input_type : $field->type ) . 'FieldChoice';
}
Expand Down
21 changes: 18 additions & 3 deletions src/Registry/FormFieldRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ protected static function register_interface_and_types( GF_Field $field, array $
continue;
}

$possible_settings[ $gf_type ] = $child_field->get_form_editor_field_settings();
$possible_settings[ $gf_type ] = self::get_field_settings( $child_field );
}

// We flip the arrays and compare the keys for performance.
Expand Down Expand Up @@ -174,7 +174,12 @@ function( TypeRegistry $type_registry ) use ( $field, $interface_settings, $poss

// Loop through the child fields and register each one.
foreach ( $possible_types as $gf_type => $graphql_type ) {
$field_to_register = clone( $field );
if ( in_array( $gf_type, [ 'calculation', 'hiddenproduct', 'singleproduct', 'singleshipping' ], true ) ) {
// These possible types are actually their own GF_Field classes that were skipped in the register() loop.
$field_to_register = GF_Fields::get( $gf_type );
} else {
$field_to_register = clone( $field );
}

// Override the field config from the inherited GF field with those from the child type.
$field_to_register->inputType = $gf_type;
Expand All @@ -197,7 +202,13 @@ function( TypeRegistry $type_registry ) use ( $field, $interface_settings, $poss
* @param GF_Field $field .
*/
public static function get_field_settings( GF_Field $field ) : array {
$settings = $field->get_form_editor_field_settings();
$settings = $field->get_form_editor_field_settings();

// Product inputs are not stored as a setting, so we're going to fake it.
if ( 'singleproduct' === $field->type ) {
$settings[] = 'single_product_inputs';
}

$input_type = $field->get_input_type();

// Bail early if the types are the same.
Expand Down Expand Up @@ -390,6 +401,10 @@ public static function get_field_value_fields( GF_Field $field ) : array {
case 'post_image':
$fields += FieldValues::image_values();
break;
case 'singleproduct':
case 'product':
$fields += FieldValues::product_values();
break;
case 'time':
$fields += FieldValues::time_values();
break;
Expand Down
6 changes: 5 additions & 1 deletion src/Registry/TypeRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private static function inputs() : array {
Input\FormsConnectionOrderbyInput::class,
Input\ListFieldInput::class,
Input\NameFieldInput::class,
Input\ProductFieldInput::class,
Input\SubmitFormMetaInput::class,
Input\UpdateDraftEntryMetaInput::class,
Input\UpdateEntryMetaInput::class,
Expand Down Expand Up @@ -276,10 +277,11 @@ public static function objects() : array {
// Form Field Value properties.
WPObject\FormField\FieldValue\ValueProperty\AddressFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\CheckboxFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\ImageFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\FileUploadFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\ListFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\NameFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\ImageFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\ProductFieldValue::class,
WPObject\FormField\FieldValue\ValueProperty\TimeFieldValue::class,
// Form Fields.
WPObject\FormField\FormFields::class,
Expand Down Expand Up @@ -375,6 +377,7 @@ public static function form_field_settings() : array {
'rich_text_editor_setting' => WPInterface\FieldSetting\FieldWithRichTextEditor::class,
'rules_setting' => WPInterface\FieldSetting\FieldWithRules::class,
'select_all_choices_setting' => WPInterface\FieldSetting\FieldWithSelectAllChoices::class,
'single_product_inputs' => WPInterface\FieldSetting\FieldWithSingleProductInputs::class,
'size_setting' => WPInterface\FieldSetting\FieldWithSize::class,
'sub_label_placement_setting' => WPInterface\FieldSetting\FieldWithSubLabelPlacement::class,
'time_format_setting' => WPInterface\FieldSetting\FieldWithTimeFormat::class,
Expand Down Expand Up @@ -405,6 +408,7 @@ public static function form_field_setting_inputs() : array {
'password_setting' => WPInterface\FieldInputSetting\InputWithPassword::class,
'select_all_choices_setting' => WPInterface\FieldInputSetting\InputWithSelectAllChoices::class,
'time_format_setting' => WPInterface\FieldInputSetting\InputWithTimeFormat::class,
'single_product_inputs' => WPInterface\FieldInputSetting\InputWithSingleProduct::class,
];

/**
Expand Down
4 changes: 4 additions & 0 deletions src/Type/Input/FormFieldValuesInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public static function get_fields() : array {
'type' => NameFieldInput::$type,
'description' => __( 'The form field values for Name fields.', 'wp-graphql-gravity-forms' ),
],
'productValues' => [
'type' => ProductFieldInput::$type,
'description' => __( 'The form field values for Name fields.', 'wp-graphql-gravity-forms' ),
],
'values' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'The form field values for fields that accept multiple string values. Used by MultiSelect, Post Category, Post Custom, and Post Tags fields.', 'wp-graphql-gravity-forms' ),
Expand Down
45 changes: 45 additions & 0 deletions src/Type/Input/ProductFieldInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/**
* GraphQL Input Type - ProductFieldInput
* Input fields for a product.
*
* @package WPGraphQL\GF\Type\Input
* @since 0.5.0
*/

namespace WPGraphQL\GF\Type\Input;

/**
* Class - ProductFieldInput
*/
class ProductFieldInput extends AbstractInput {
/**
* Type registered in WPGraphQL.
*
* @var string
*/
public static string $type = 'ProductFieldInput';

/**
* {@inheritDoc}
*/
public static function get_description() : string {
return __( 'Input fields for Product field.', 'wp-graphql-gravity-forms' );
}

/**
* {@inheritDoc}
*/
public static function get_fields() : array {
return [
'quantity' => [
'type' => 'Float',
'description' => __( 'Product quantity.', 'wp-graphql-gravity-forms' ),
],
'price' => [
'type' => 'Float',
'description' => __( 'Product price.', 'wp-graphql-gravity-forms' ),
],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static function get_fields() : array {
public static function add_fields_to_child_type( array $fields, string $choice_name, GF_Field $field, array $settings, array $interfaces ) : array {
if (
! in_array( self::$type, $interfaces, true ) ||
( empty( $field->enablePrice ) && ! in_array( $field->type, [ 'product', 'option', 'shipping' ], true ) )
( empty( $field->enablePrice ) && ! in_array( $field->type, [ 'product', 'quantity', 'option', 'shipping' ], true ) )
) {
return $fields;
}
Expand Down
Loading

0 comments on commit fe69948

Please sign in to comment.