From 671530750cbdc6ebb3b103664314cce1739538e2 Mon Sep 17 00:00:00 2001 From: Reece Alexander Date: Wed, 9 Nov 2016 23:30:32 +1000 Subject: [PATCH] A more versatile solution to field naming (#149) * Added rename.xml for custom field name support * Added getFieldName() * Updated tests so they don't break when renames are in effect * Cleaned up configuration (release warning: prefix enabled by default) * Scoping fix * Fixed rename config issue with commenting * Removed example prefix as default * Batch support for getFieldName(). Added normalizeFormData() to reverse the effects of custom field name support * Added test for normalizeFormData * Removed mock functionality and updated test --- _config/rename.yml | 16 +++ code/GatewayFieldsFactory.php | 153 ++++++++++++++++++++++++----- tests/GatewayFieldsFactoryTest.php | 64 +++++++++++- 3 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 _config/rename.yml diff --git a/_config/rename.yml b/_config/rename.yml new file mode 100644 index 00000000..bedcb896 --- /dev/null +++ b/_config/rename.yml @@ -0,0 +1,16 @@ +--- +Name: omnipay-rename +--- +# You can define custom names globally, or on a per gateway basis, if you define your gateway here, it will take priority +# and then will fall back to any of the global renames defined below (if set). The example below shows how you would rename +# the default fields for the Stripe payment gateway, and how to rename field names globally + +GatewayFieldsFactory: + rename: + prefix: '' # e.g "card_" + #name: 'myName' + #type: 'myType' + #number: 'myNumber' + Stripe: + name: 'name' # e.g change to newName + #number: 'myStripeNumber' diff --git a/code/GatewayFieldsFactory.php b/code/GatewayFieldsFactory.php index aac16e53..80489db9 100644 --- a/code/GatewayFieldsFactory.php +++ b/code/GatewayFieldsFactory.php @@ -96,21 +96,21 @@ public function getCardFields() $expiryrange = range($year, date('Y', strtotime("+$range years"))); $fields = array( - 'type' => \DropdownField::create('type', _t('PaymentForm.Type', 'Type'), $this->getCardTypes()), - 'name' => \TextField::create('name', _t('PaymentForm.Name', 'Name on Card')), - 'number' => \TextField::create('number', _t('PaymentForm.Number', 'Card Number')) + 'type' => \DropdownField::create($this->getFieldName('type'), _t('PaymentForm.Type', 'Type'), $this->getCardTypes()), + 'name' => \TextField::create($this->getFieldName('name'), _t('PaymentForm.Name', 'Name on Card')), + 'number' => \TextField::create($this->getFieldName('number'), _t('PaymentForm.Number', 'Card Number')) ->setDescription(_t('PaymentForm.NumberDescription', 'no dashes or spaces')), - 'startMonth' => \DropdownField::create('startMonth', _t('PaymentForm.StartMonth', 'Month'), $months), - 'startYear' => \DropdownField::create('startYear', _t('PaymentForm.StartYear', 'Year'), - array_combine($startrange, $startrange), $year + 'startMonth' => \DropdownField::create($this->getFieldName('startMonth'), _t('PaymentForm.StartMonth', 'Month'), $months), + 'startYear' => \DropdownField::create($this->getFieldName('startYear'), _t('PaymentForm.StartYear', 'Year'), + array_combine($startrange, $startrange), $year ), - 'expiryMonth' => \DropdownField::create('expiryMonth', _t('PaymentForm.ExpiryMonth', 'Month'), $months), - 'expiryYear' => \DropdownField::create('expiryYear', _t('PaymentForm.ExpiryYear', 'Year'), - array_combine($expiryrange, $expiryrange), $year + 'expiryMonth' => \DropdownField::create($this->getFieldName('expiryMonth'), _t('PaymentForm.ExpiryMonth', 'Month'), $months), + 'expiryYear' => \DropdownField::create($this->getFieldName('expiryYear'), _t('PaymentForm.ExpiryYear', 'Year'), + array_combine($expiryrange, $expiryrange), $year ), - 'cvv' => \TextField::create('cvv', _t('PaymentForm.CVV', 'Security Code')) + 'cvv' => \TextField::create($this->getFieldName('cvv'), _t('PaymentForm.CVV', 'Security Code')) ->setMaxLength(5), - 'issueNumber' => \TextField::create('issueNumber', _t('PaymentForm.IssueNumber', 'Issue Number')) + 'issueNumber' => \TextField::create($this->getFieldName('issueNumber'), _t('PaymentForm.IssueNumber', 'Issue Number')) ); $this->cullForGateway($fields); @@ -158,13 +158,13 @@ public function getCardTypes() public function getBillingFields() { $fields = array( - 'billingAddress1' => \TextField::create('billingAddress1', _t('PaymentForm.BillingAddress1', 'Address')), - 'billingAddress2' => \TextField::create('billingAddress2', _t('PaymentForm.BillingAddress2', 'Address line 2')), - 'city' => \TextField::create('billingCity', _t('PaymentForm.BillingCity', 'City')), - 'postcode' => \TextField::create('billingPostcode', _t('PaymentForm.BillingPostcode', 'Postcode')), - 'state' => \TextField::create('billingState', _t('PaymentForm.BillingState', 'State')), - 'country' => \TextField::create('billingCountry', _t('PaymentForm.BillingCountry', 'Country')), - 'phone' => \PhoneNumberField::create('billingPhone', _t('PaymentForm.BillingPhone', 'Phone')) + 'billingAddress1' => \TextField::create($this->getFieldName('billingAddress1'), _t('PaymentForm.BillingAddress1', 'Address')), + 'billingAddress2' => \TextField::create($this->getFieldName('billingAddress2'), _t('PaymentForm.BillingAddress2', 'Address line 2')), + 'city' => \TextField::create($this->getFieldName('billingCity'), _t('PaymentForm.BillingCity', 'City')), + 'postcode' => \TextField::create($this->getFieldName('billingPostcode'), _t('PaymentForm.BillingPostcode', 'Postcode')), + 'state' => \TextField::create($this->getFieldName('billingState'), _t('PaymentForm.BillingState', 'State')), + 'country' => \TextField::create($this->getFieldName('billingCountry'), _t('PaymentForm.BillingCountry', 'Country')), + 'phone' => \PhoneNumberField::create($this->getFieldName('billingPhone'), _t('PaymentForm.BillingPhone', 'Phone')) ); $this->cullForGateway($fields); @@ -179,16 +179,16 @@ public function getShippingFields() { $fields = array( 'shippingAddress1' => \TextField::create( - 'shippingAddress1', _t('PaymentForm.ShippingAddress1', 'Shipping Address') + $this->getFieldName('shippingAddress1'), _t('PaymentForm.ShippingAddress1', 'Shipping Address') ), 'shippingAddress2' => \TextField::create( - 'shippingAddress2', _t('PaymentForm.ShippingAddress2', 'Shipping Address 2') + $this->getFieldName('shippingAddress2'), _t('PaymentForm.ShippingAddress2', 'Shipping Address 2') ), - 'city' => \TextField::create('shippingCity', _t('PaymentForm.ShippingCity', 'Shipping City')), - 'postcode' => \TextField::create('shippingPostcode', _t('PaymentForm.ShippingPostcode', 'Shipping Postcode')), - 'state' => \TextField::create('shippingState', _t('PaymentForm.ShippingState', 'Shipping State')), - 'country' => \TextField::create('shippingCountry', _t('PaymentForm.ShippingCountry', 'Shipping Country')), - 'phone' => \PhoneNumberField::create('shippingPhone', _t('PaymentForm.ShippingPhone', 'Shipping Phone')) + 'city' => \TextField::create($this->getFieldName('shippingCity'), _t('PaymentForm.ShippingCity', 'Shipping City')), + 'postcode' => \TextField::create($this->getFieldName('shippingPostcode'), _t('PaymentForm.ShippingPostcode', 'Shipping Postcode')), + 'state' => \TextField::create($this->getFieldName('shippingState'), _t('PaymentForm.ShippingState', 'Shipping State')), + 'country' => \TextField::create($this->getFieldName('shippingCountry'), _t('PaymentForm.ShippingCountry', 'Shipping Country')), + 'phone' => \PhoneNumberField::create($this->getFieldName('shippingPhone'), _t('PaymentForm.ShippingPhone', 'Shipping Phone')) ); $this->cullForGateway($fields); @@ -202,7 +202,7 @@ public function getShippingFields() public function getEmailFields() { $fields = array( - 'email' => \EmailField::create('email', _t('PaymentForm.Email', 'Email')) + 'email' => \EmailField::create($this->getFieldName('email'), _t('PaymentForm.Email', 'Email')) ); $this->cullForGateway($fields); @@ -216,13 +216,60 @@ public function getEmailFields() public function getCompanyFields() { $fields = array( - 'company' => \TextField::create('company', _t('PaymentForm.Company', 'Company')) + 'company' => \TextField::create($this->getFieldName('company'), _t('PaymentForm.Company', 'Company')) ); $this->cullForGateway($fields); return \FieldList::create($fields); } + /** + * Attempts to find a custom field name and/or prefix defined in rename.yml, otherwise returns the same input + * that it was given + * + * @param string|array $defaultName The default name of the field + * @param null|bool $skipGateway Used in recursion + * + * @return string|array + */ + public function getFieldName($defaultName, $skipGateway = null) { + // batch support + if (is_array($defaultName)) { + $stack = array(); + foreach ($defaultName as $name) { + $stack[] = $this->getFieldName($name, $skipGateway); + } + + return $stack; + } + + $renameMap = \Config::inst()->get('GatewayFieldsFactory', 'rename'); + + if (!$renameMap || empty($renameMap)) { + return $defaultName; + } + + // allows support for custom field names that are dependent on the gateway provider + // see rename.yml configuration file for an example + if (array_key_exists($this->gateway, $renameMap) && !empty($renameMap[$this->gateway]) && !$skipGateway) { + $gatewayMap = $renameMap[$this->gateway]; + + // if not in gatewayMap, run this function again but skip this gateway check + if (!array_key_exists($defaultName, $gatewayMap)) { + return $this->getFieldName($defaultName, true); + } + + // a name has been found to replace $defaultName with, switch map over to the gateway map + $renameMap = $gatewayMap; + } + + if (!array_key_exists($defaultName, $renameMap)) { + return (array_key_exists('prefix', $renameMap)) ? $renameMap['prefix'] . $defaultName : $defaultName; + } + + return (array_key_exists('prefix', $renameMap)) ? $renameMap['prefix'] . $renameMap[$defaultName] : $renameMap[$defaultName]; + } + /** * Clear all fields that are not required by the gateway. Does nothing if gateway is null * @param $fields @@ -241,4 +288,56 @@ protected function cullForGateway(&$fields, $defaults = array()) } } } + + /** + * Normalizes form data keys to map to their respective Omnipay parameters (in other words: reverses the effects + * from the custom field name support) + * + * @param array $data The form data consisting of key value pairs + * + * @return array + */ + public function normalizeFormData(array $data) { + + $renameMap = \Config::inst()->get('GatewayFieldsFactory', 'rename'); + + if (!$renameMap || empty($renameMap)) { + return $data; + } + + $hasPrefix = false; + + if (array_key_exists('prefix', $renameMap)) { + $hasPrefix = true; + } + + // Fix for: array_flip(): Can only flip STRING and INTEGER values! + // This would occur if you have defined Gateways in rename.yml + foreach($globalMap = $renameMap as $key => $value) { + if (is_array($value)) { + unset($globalMap[$key]); + } + } + + $stack = array(array_flip($globalMap)); + + if (array_key_exists($this->gateway, $renameMap) && !empty($renameMap[$this->gateway])) { + $stack[] = $gatewayMap = array_flip($renameMap[$this->gateway]); + } + + foreach($stack as $map) { + foreach ($map as $otherName => $defaultName) { + if ($hasPrefix) { + $otherName = $renameMap['prefix'] . $otherName; + } + if (array_key_exists($otherName, $data)) { + $value = $data[$otherName]; + unset($data[$otherName]); + $data[$defaultName] = $value; + } + } + } + + return $data; + } } diff --git a/tests/GatewayFieldsFactoryTest.php b/tests/GatewayFieldsFactoryTest.php index f1208857..c27d05b3 100644 --- a/tests/GatewayFieldsFactoryTest.php +++ b/tests/GatewayFieldsFactoryTest.php @@ -59,6 +59,20 @@ public function setUp() 'Company', 'Email' )); + + // caters for custom field names so that tests pass even if user has defined custom names + $fieldSets = array( + &$this->ccFields, + &$this->billingFields, + &$this->shippingFields, + &$this->companyFields, + &$this->emailFields, + ); + foreach ($fieldSets as &$fieldSet) { + foreach ($fieldSet as &$field) { + $field = $this->factory->getFieldName($field); + } + } } public function testAllFieldGroups() @@ -188,7 +202,7 @@ public function testRequiredFields() $fields = $factory->getFields(); - $this->assertEquals(array( + $defaults = array( // default required CC fields for gateways that aren't manual and aren't offsite 'name', 'number', @@ -203,7 +217,9 @@ public function testRequiredFields() 'shippingCountry', 'company', 'email' - ), array_keys($fields->dataFields())); + ); + + $this->assertEquals($this->factory->getFieldName($defaults), array_keys($fields->dataFields())); // Same procedure with offsite gateway should not return the CC fields @@ -217,7 +233,7 @@ public function testRequiredFields() $fields = $factory->getFields(); - $this->assertEquals(array( + $pxDefaults = array( 'billingAddress1', 'billingCity', 'billingCountry', @@ -225,6 +241,46 @@ public function testRequiredFields() 'shippingCountry', 'company', 'email' - ), array_keys($fields->dataFields())); + ); + + $this->assertEquals($this->factory->getFieldName($pxDefaults), array_keys($fields->dataFields())); + } + + public function testNormalizeFormData() { + $data = array( + 'prefix_testName' => 'Reece Alexander', + 'prefix_testNumber' => '4242424242424242', + 'prefix_testExpiryMonth' => '11', + 'prefix_testExpiryYear' => '2016' + ); + + Config::inst()->update('GatewayFieldsFactory', 'rename', array( + 'prefix' => 'prefix_', + 'name' => 'testName', + 'number' => 'testNumber', + 'expiryMonth' => 'testExpiryMonth', + 'expiryYear' => 'testExpiryYear' + )); + + $factory = new GatewayFieldsFactory(); + + $this->assertEquals( + array_keys($factory->normalizeFormData($data, true)), + array( + 'name', + 'number', + 'expiryMonth', + 'expiryYear' + ) + ); + } + + public function renameWalk(&$array) { + return $array = array_map( + function ($name) { + return $this->factory->getFieldName($name); + }, + $array + ); } }