From de3cd819c2bddff8c53db7be086268b0456ef694 Mon Sep 17 00:00:00 2001 From: "Galla, Daniel" Date: Fri, 24 Jan 2020 16:20:27 +0100 Subject: [PATCH 1/3] Add configurable input length validation --- Api/Data/CustomFieldsInterface.php | 8 ++ Helper/Config.php | 77 +++++++++++++++++ Model/Checkout/LayoutProcessor/Plugin.php | 57 ++++++++---- Model/CustomFields/Validator.php | 101 ++++++++++++++++++++++ Model/CustomFieldsRepository.php | 27 ++++-- etc/adminhtml/system.xml | 20 +++++ 6 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 Helper/Config.php create mode 100644 Model/CustomFields/Validator.php diff --git a/Api/Data/CustomFieldsInterface.php b/Api/Data/CustomFieldsInterface.php index 30b8fbd..34ee663 100644 --- a/Api/Data/CustomFieldsInterface.php +++ b/Api/Data/CustomFieldsInterface.php @@ -26,6 +26,14 @@ interface CustomFieldsInterface const CHECKOUT_GOODS_MARK = 'checkout_goods_mark'; const CHECKOUT_COMMENT = 'checkout_comment'; + const ATTRIBUTES = [ + self::CHECKOUT_BUYER_EMAIL, + self::CHECKOUT_BUYER_NAME, + self::CHECKOUT_COMMENT, + self::CHECKOUT_GOODS_MARK, + self::CHECKOUT_PURCHASE_ORDER_NO, + ]; + /** * Get checkout buyer name * diff --git a/Helper/Config.php b/Helper/Config.php new file mode 100644 index 0000000..3d38fce --- /dev/null +++ b/Helper/Config.php @@ -0,0 +1,77 @@ +config = $config; + $this->storeManager = $storeManager; + $this->logger = $logger; + } + + /** + * @return array + */ + public function getEnabledFields() + { + return explode(',', + $this->config->getValue('bodak/checkout/enabled_fields', + ScopeInterface::SCOPE_STORE)); + } + + /** + * @param $attribute + * + * @return int + */ + public function getAllowedLength($attribute) + { + $configPath = 'checkout/general/' . $attribute . '_limit'; + try { + $allowedLength = $this->config->getValue($configPath, ScopeInterface::SCOPE_STORES, + $this->storeManager->getStore()->getId()); + } catch (NoSuchEntityException $e) { + $this->logger->error('Cannot get allowed length for custom field. Falling back to default scope value.'); + $this->logger->error($e->getMessage(), ['exception' => $e]); + $allowedLength = $this->config->getValue($configPath); + } + + return (int)$allowedLength ?: self::LIMIT_NOT_SET; + } +} \ No newline at end of file diff --git a/Model/Checkout/LayoutProcessor/Plugin.php b/Model/Checkout/LayoutProcessor/Plugin.php index d15ff24..19e5dae 100644 --- a/Model/Checkout/LayoutProcessor/Plugin.php +++ b/Model/Checkout/LayoutProcessor/Plugin.php @@ -12,9 +12,18 @@ namespace Bodak\CheckoutCustomForm\Model\Checkout\LayoutProcessor; use Bodak\CheckoutCustomForm\Api\Data\CustomFieldsInterface; +use Bodak\CheckoutCustomForm\Helper\Config; class Plugin { + /** + * @var Config + */ + private $config; + + /** + * @var array + */ private $fields = [ [ 'dataScopeName' => CustomFieldsInterface::CHECKOUT_BUYER_NAME, @@ -28,8 +37,8 @@ class Plugin ], 'config' => [ 'tooltip' => [ - 'description' => 'We will send an order confirmation to this email address' - ] + 'description' => 'We will send an order confirmation to this email address', + ], ], ], [ @@ -46,48 +55,45 @@ class Plugin 'config' => [ 'cols' => 15, 'rows' => 2, - 'maxlength' => 80, + 'maxlength' => null, 'elementTmpl' => 'Bodak_CheckoutCustomForm/form/element/textarea', ], 'showTitle' => false, ], ]; - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - protected $_scopeConfig; - /** * LayoutProcessor constructor. * - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param Config $config */ - public function __construct(\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig) + public function __construct(Config $config) { - $this->_scopeConfig = $scopeConfig; + $this->config = $config; } /** * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject * @param array $jsLayout * - * @see \Magento\Checkout\Block\Checkout\LayoutProcessor::process * @return array + * @see \Magento\Checkout\Block\Checkout\LayoutProcessor::process */ public function afterProcess( \Magento\Checkout\Block\Checkout\LayoutProcessor $subject, array $jsLayout ) { - $config = explode(',', - $this->_scopeConfig->getValue('bodak/checkout/enabled_fields', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE)); + $config = $this->config->getEnabledFields(); + + $this->applyLengthLimitToFields(); foreach ($this->fields as $sortOrder => $field) { - if ( ! in_array($field['dataScopeName'], $config)) { + if (!in_array($field['dataScopeName'], $config)) { continue; } - if(!isset($field['showTitle'])) $field['showTitle'] = true; + if (!isset($field['showTitle'])) { + $field['showTitle'] = true; + } $formField = [ 'component' => 'Magento_Ui/js/form/element/abstract', @@ -112,10 +118,23 @@ public function afterProcess( } $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step'] - ['children']['shippingAddress']['children']['custom-checkout-form-container'] - ['children']['custom-checkout-form-fieldset']['children'][$field['dataScopeName']] = $formField; + ['children']['shippingAddress']['children']['custom-checkout-form-container'] + ['children']['custom-checkout-form-fieldset']['children'][$field['dataScopeName']] = $formField; } return $jsLayout; } + + private function applyLengthLimitToFields() + { + foreach ($this->fields as $key => $field) { + $fieldName = $field['dataScopeName']; + $allowedLength = $this->config->getAllowedLength($fieldName); + if ($allowedLength === Config::LIMIT_NOT_SET) { + continue; + } + $this->fields[$key]['config']['maxlength'] = $allowedLength; + $this->fields[$key]['validation']['max_text_length'] = $allowedLength; + } + } } \ No newline at end of file diff --git a/Model/CustomFields/Validator.php b/Model/CustomFields/Validator.php new file mode 100644 index 0000000..8fe78b9 --- /dev/null +++ b/Model/CustomFields/Validator.php @@ -0,0 +1,101 @@ +config = $config; + } + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * + * @return boolean + * @throws Zend_Validate_Exception If validation of $value is impossible + */ + public function isValid($value) + { + $valid = true; + if (!($value instanceof CustomFieldsInterface)) { + throw new Zend_Validate_Exception('Expected value to be instance of \Bodak\CheckoutCustomForm\Api\Data\CustomFieldsInterface'); + } + + $this->setValue($value); + + foreach (CustomFieldsInterface::ATTRIBUTES as $attribute) { + if (!$this->lengthIsValid($attribute)) { + $valid = false; + $this->_addMessages([sprintf('Field %s is to long.', $attribute)]); + } + } + + return $valid; + } + + /** + * @param $value + */ + private function setValue($value) + { + $this->value = $value; + } + + /** + * @param $attribute + * + * @return bool + */ + private function lengthIsValid($attribute) + { + $allowedLength = $this->config->getAllowedLength($attribute); + + if ($allowedLength === Config::LIMIT_NOT_SET) { + return true; + } + + $function = $this->convertSnakeToCamelCase('get_' . $attribute); + $value = call_user_func([$this->value, $function]); + + return mb_strlen($value) <= $allowedLength; + } + + /** + * @param $string + * + * @return string + */ + private function convertSnakeToCamelCase($string) + { + return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))))); + } +} \ No newline at end of file diff --git a/Model/CustomFieldsRepository.php b/Model/CustomFieldsRepository.php index df3763e..649ba05 100644 --- a/Model/CustomFieldsRepository.php +++ b/Model/CustomFieldsRepository.php @@ -10,6 +10,8 @@ namespace Bodak\CheckoutCustomForm\Model; +use Bodak\CheckoutCustomForm\Model\CustomFields\Validator; +use Magento\Framework\Exception\LocalizedException; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\NoSuchEntityException; @@ -47,31 +49,42 @@ class CustomFieldsRepository implements CustomFieldsRepositoryInterface */ protected $customFields; + /** + * @var Validator + */ + private $validator; + /** * CustomFieldsRepository constructor. * * @param CartRepositoryInterface $cartRepository CartRepositoryInterface - * @param ScopeConfigInterface $scopeConfig ScopeConfigInterface - * @param CustomFieldsInterface $customFields CustomFieldsInterface + * @param ScopeConfigInterface $scopeConfig ScopeConfigInterface + * @param CustomFieldsInterface $customFields CustomFieldsInterface + * @param Validator $validator */ public function __construct( CartRepositoryInterface $cartRepository, ScopeConfigInterface $scopeConfig, - CustomFieldsInterface $customFields + CustomFieldsInterface $customFields, + Validator $validator ) { $this->cartRepository = $cartRepository; $this->scopeConfig = $scopeConfig; $this->customFields = $customFields; + $this->validator = $validator; } + /** * Save checkout custom fields * - * @param int $cartId Cart id + * @param int $cartId Cart id * @param \Bodak\CheckoutCustomForm\Api\Data\CustomFieldsInterface $customFields Custom fields * - * @return \Bodak\CheckoutCustomForm\Api\Data\CustomFieldsInterface + * @return \Bodak\CheckoutCustomForm\Api\Data\CustomFieldsInterface|string * @throws CouldNotSaveException * @throws NoSuchEntityException + * @throws \Zend_Validate_Exception + * @throws LocalizedException */ public function saveCustomFields( int $cartId, @@ -82,6 +95,10 @@ public function saveCustomFields( throw new NoSuchEntityException(__('Cart %1 is empty', $cartId)); } + if (!$this->validator->isValid($customFields)) { + throw new LocalizedException(__('Custom fields contain invalid values.')); + } + try { $cart->setData( CustomFieldsInterface::CHECKOUT_BUYER_NAME, diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index cf571a6..ac6f36b 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -15,6 +15,26 @@ Bodak\CheckoutCustomForm\Model\Config\Source\Option bodak/checkout/enabled_fields + + + Limit the character length. Leave empty for no limit. + + + + Limit the character length. Leave empty for no limit. + + + + Limit the character length. Leave empty for no limit. + + + + Limit the character length. Leave empty for no limit. + + + + Limit the character length. Leave empty for no limit. + From b2f9219c95c4a1dd1904ea2ee00752ad106c2835 Mon Sep 17 00:00:00 2001 From: "Galla, Daniel" Date: Mon, 27 Jan 2020 09:38:52 +0100 Subject: [PATCH 2/3] Use zend class instead of implementing functionality again --- Model/CustomFields/Validator.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Model/CustomFields/Validator.php b/Model/CustomFields/Validator.php index 8fe78b9..094c461 100644 --- a/Model/CustomFields/Validator.php +++ b/Model/CustomFields/Validator.php @@ -6,6 +6,7 @@ use Bodak\CheckoutCustomForm\Helper\Config; use Magento\Framework\Validator\AbstractValidator; use Zend_Validate_Exception; +use Zend\Filter\Word\UnderscoreToCamelCase; class Validator extends AbstractValidator @@ -21,14 +22,21 @@ class Validator extends AbstractValidator */ private $config; + /** + * @var UnderscoreToCamelCase + */ + private $underscoreToCamelCase; + /** * Validator constructor. * * @param Config $config + * @param UnderscoreToCamelCase $underscoreToCamelCase */ - public function __construct(Config $config) + public function __construct(Config $config, UnderscoreToCamelCase $underscoreToCamelCase) { $this->config = $config; + $this->underscoreToCamelCase = $underscoreToCamelCase; } /** @@ -96,6 +104,6 @@ private function lengthIsValid($attribute) */ private function convertSnakeToCamelCase($string) { - return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($string))))); + return $this->underscoreToCamelCase->filter($string); } } \ No newline at end of file From 8651d7af1c537bdfe2ac7caf31d2ca177efb251d Mon Sep 17 00:00:00 2001 From: "Galla, Daniel" Date: Mon, 27 Jan 2020 09:41:13 +0100 Subject: [PATCH 3/3] Remove wrapper function --- Model/CustomFields/Validator.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Model/CustomFields/Validator.php b/Model/CustomFields/Validator.php index 094c461..671eab5 100644 --- a/Model/CustomFields/Validator.php +++ b/Model/CustomFields/Validator.php @@ -91,19 +91,9 @@ private function lengthIsValid($attribute) return true; } - $function = $this->convertSnakeToCamelCase('get_' . $attribute); + $function = $this->underscoreToCamelCase->filter('get_' . $attribute); $value = call_user_func([$this->value, $function]); return mb_strlen($value) <= $allowedLength; } - - /** - * @param $string - * - * @return string - */ - private function convertSnakeToCamelCase($string) - { - return $this->underscoreToCamelCase->filter($string); - } } \ No newline at end of file