From af931aa353433f8898eb74d35e1d2868d8ee6563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= <lucas@codeccoop.org> Date: Tue, 23 Apr 2024 20:25:12 +0200 Subject: [PATCH] feat: gf vat-id field --- abstract/class-integration.php | 9 +- includes/fields/gf/iban/class-addon.php | 6 + includes/fields/gf/iban/class-field.php | 55 ++-- includes/fields/gf/vat-id/class-addon.php | 58 +++++ .../fields/gf/vat-id/class-field-adapter.php | 28 +++ includes/fields/gf/vat-id/class-field.php | 235 ++++++++++++++++++ .../integrations/gf/class-integration.php | 41 +-- wpct-erp-forms.php | 9 +- 8 files changed, 397 insertions(+), 44 deletions(-) create mode 100644 includes/fields/gf/vat-id/class-addon.php create mode 100644 includes/fields/gf/vat-id/class-field-adapter.php create mode 100644 includes/fields/gf/vat-id/class-field.php diff --git a/abstract/class-integration.php b/abstract/class-integration.php index 69f0caa..1ef2b14 100644 --- a/abstract/class-integration.php +++ b/abstract/class-integration.php @@ -16,15 +16,16 @@ abstract class Integration extends Singleton { foreach (static::$fields as $Field) { $field = $Field::get_instance(); + add_action('init', function () use ($field) { + $field->init(); + }); } + + add_action('init', [$this, 'init']); } public function init() { - foreach (static::$fields as $Field) { - $field = $Field::get_instance(); - $field->init(); - } } public function submit($payload, $endpoints, $files, $form_data) diff --git a/includes/fields/gf/iban/class-addon.php b/includes/fields/gf/iban/class-addon.php index 1d8fbdc..1123270 100644 --- a/includes/fields/gf/iban/class-addon.php +++ b/includes/fields/gf/iban/class-addon.php @@ -34,6 +34,12 @@ class Addon extends GFAddOn return self::$_instance; } + function __construct() + { + $this->_full_path = __FILE__; + parent::__construct(); + } + /** * Include the field early so it is available when entry exports are being performed. */ diff --git a/includes/fields/gf/iban/class-field.php b/includes/fields/gf/iban/class-field.php index dfa96d5..ef8eef5 100644 --- a/includes/fields/gf/iban/class-field.php +++ b/includes/fields/gf/iban/class-field.php @@ -3,6 +3,7 @@ namespace WPCT_ERP_FORMS\GF\Fields\Iban; use GF_Field; +use Exception; class GFField extends GF_Field { @@ -142,7 +143,7 @@ class GFField extends GF_Field * * @return array */ - function get_form_editor_field_settings() + public function get_form_editor_field_settings() { return [ 'conditional_logic_field_setting', @@ -193,8 +194,8 @@ class GFField extends GF_Field $html_input_type = 'password'; } - $logic_event = !$is_form_editor && !$is_entry_detail ? $this->get_conditional_logic_event('keyup') : ''; - $id = (int)$this->id; + $logic_event = ''; // !$is_form_editor && !$is_entry_detail ? $this->get_conditional_logic_event('keyup') : ''; + $id = (int) $this->id; $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; $value = esc_attr($value); @@ -218,15 +219,18 @@ class GFField extends GF_Field */ public function validate($value, $form) { - if (strlen($value) < 5) return false; - $value = strtolower(str_replace(' ', '', $value)); - + try { + if (strlen($value) < 5) { + throw new Exception(); + } + $value = strtolower(str_replace(' ', '', $value)); - $country_exists = array_key_exists(substr($value, 0, 2), $this->_countries); - $country_conform = strlen($value) == $this->_countries[substr($value, 0, 2)]; + $country_exists = array_key_exists(substr($value, 0, 2), $this->_countries); + $country_conform = strlen($value) == $this->_countries[substr($value, 0, 2)]; - try { - if (!($country_exists && $country_conform)) throw new Exception(); + if (!($country_exists && $country_conform)) { + throw new Exception(); + } $moved_char = substr($value, 4) . substr($value, 0, 4); $move_char_array = str_split($moved_char); @@ -234,7 +238,9 @@ class GFField extends GF_Field foreach ($move_char_array as $key => $val) { if (!is_numeric($move_char_array[$key])) { - if (!isset($this->_chars[$val])) throw new Exception(); + if (!isset($this->_chars[$val])) { + throw new Exception(); + } $move_char_array[$key] = $this->_chars[$val]; } @@ -244,13 +250,28 @@ class GFField extends GF_Field if (bcmod($new_string, '97') != 1) { throw new Exception(); } - } catch (Exception) { + } catch (Exception $e) { $this->failed_validation = true; - if (!empty($this->errorMessage)) { - $this->validation_message = $this->errorMessage; - } else { - $this->validation_message = __('The IABN you inserted is not valid.'); - } + $this->validation_message = empty($this->errorMessage) ? __('The IBAN you\'ve inserted is not valid.', 'wpct-erp-forms') : $this->errorMessage; } } + + public function get_form_inline_script_on_page_render($form) + { + ob_start(); + ?> + const input = document.querySelector('#input_<?= $form['id'] ?>_<?= $this->id ?>'); + input.addEventListener("input", ({ target }) => { + const value = String(target.value); + const chars = value.split("").filter((c) => c !== " "); + target.value = chars.reduce((repr, char, i) => { + if (i % 4 === 0) { + char = " " + char; + } + return repr + char; + }); + }); + <?php + return ob_get_clean(); + } } diff --git a/includes/fields/gf/vat-id/class-addon.php b/includes/fields/gf/vat-id/class-addon.php new file mode 100644 index 0000000..fb297e7 --- /dev/null +++ b/includes/fields/gf/vat-id/class-addon.php @@ -0,0 +1,58 @@ +<?php + +namespace WPCT_ERP_FORMS\GF\Fields\VatID; + +use GFForms; +use GFAddOn; +use GF_Fields; + +GFForms::include_addon_framework(); + +class Addon extends GFAddOn +{ + protected $_version = '1.0'; + protected $_slug = 'wpct-erp-forms-vat-id-field'; + protected $_title = 'Gravity Forms VatID validated text field'; + protected $_short_title = 'VatID field'; + protected $_full_path; + + /** + * @var object $_instance If available, contains an instance of this class. + */ + private static $_instance = null; + + /** + * Returns an instance of this class, and stores it in the $_instance property. + * + * @return object $_instance An instance of this class. + */ + public static function get_instance() + { + if (self::$_instance == null) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + public function __construct() + { + $this->_full_path = __FILE__; + parent::__construct(); + } + + /** + * Include the field early so it is available when entry exports are being performed. + */ + public function pre_init() + { + parent::pre_init(); + if ( + $this->is_gravityforms_supported() && + class_exists('GF_Field') && + class_exists('GF_Fields') + ) { + GF_Fields::register(new GFField()); + } + } +} diff --git a/includes/fields/gf/vat-id/class-field-adapter.php b/includes/fields/gf/vat-id/class-field-adapter.php new file mode 100644 index 0000000..2a23769 --- /dev/null +++ b/includes/fields/gf/vat-id/class-field-adapter.php @@ -0,0 +1,28 @@ +<?php + +namespace WPCT_ERP_FORMS\GF\Fields\VatID; + +use WPCT_ERP_FORMS\Abstract\Field as BaseField; + +use GFAddOn; + +require_once 'class-addon.php'; +require_once 'class-field.php'; + +class FieldAdapter extends BaseField +{ + public function __construct() + { + add_action('gform_loaded', [$this, 'register']); + } + + public function register() + { + if (!method_exists('GFForms', 'include_addon_framework')) return; + GFAddOn::register(Addon::class); + } + + public function init() + { + } +} diff --git a/includes/fields/gf/vat-id/class-field.php b/includes/fields/gf/vat-id/class-field.php new file mode 100644 index 0000000..913e1bf --- /dev/null +++ b/includes/fields/gf/vat-id/class-field.php @@ -0,0 +1,235 @@ +<?php + +namespace WPCT_ERP_FORMS\GF\Fields\VatID; + +use Exception; +use GF_Field; + +class GFField extends GF_Field +{ + private static $_dni_regex = '/^(\d{8})([A-Z])$/'; + private static $_cif_regex = '/^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/'; + private static $_nie_regex = '/^[XYZ]\d{7,8}[A-Z]$/'; + + /** + * @var string $type The field type. + */ + public $type = 'vat-id-field'; + + /** + * Return the field title, for use in the form editor. + * + * @return string + */ + public function get_form_editor_field_title() + { + return esc_attr__('VAT ID'); + } + + /** + * Assign the field button to the Advanced Fields group. + * + * @return array + */ + public function get_form_editor_button() + { + return [ + 'group' => 'advanced_fields', + 'text' => $this->get_form_editor_field_title(), + ]; + } + + /** + * The settings which should be available on the field in the form editor. + * + * @return array + */ + public function get_form_editor_field_settings() + { + return [ + 'conditional_logic_field_setting', + 'error_message_setting', + 'label_setting', + 'label_placement_setting', + 'admin_label_setting', + 'size_setting', + 'password_field_setting', + 'rules_setting', + 'visibility_setting', + 'duplicate_setting', + 'default_value_setting', + 'placeholder_setting', + 'description_setting', + 'css_class_setting', + ]; + } + + /** + * Enable this field for use with conditional logic. + * + * @return bool + */ + public function is_conditional_logic_supported() + { + return true; + } + + /** + * Define the fields inner markup. + * + * @param array $form The Form Object currently being processed. + * @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission. + * @param null|array $entry Null or the Entry Object currently being edited. + * + * @return string + */ + public function get_field_input($form, $value = '', $entry = null) + { + $form_id = absint($form['id']); + $is_entry_detail = $this->is_entry_detail(); + $is_form_editor = $this->is_form_editor(); + + $html_input_type = 'text'; + + if ($this->enablePasswordInput && !$is_entry_detail) { + $html_input_type = 'password'; + } + + $logic_event = ''; // !$is_form_editor && !$is_entry_detail ? $this->get_conditional_logic_event('keyup') : ''; + $id = (int) $this->id; + $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; + + $value = esc_attr($value); + $size = $this->size; + $class_suffix = $is_entry_detail ? '_admin' : ''; + $class = $size . $class_suffix; + + $tabindex = $this->get_tabindex(); + $disabled_text = $is_form_editor ? 'disabled="disabled"' : ''; + $placeholder_attribute = $this->get_field_placeholder_attribute(); + $required_attribute = $this->isRequired ? 'aria-required="true"' : ''; + $invalid_attribute = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"'; + + $input = "<input name='input_{$id}' id='{$field_id}' type='{$html_input_type}' value='{$value}' class='{$class}' {$tabindex} {$logic_event} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$disabled_text}/>"; + + return sprintf("<div class='ginput_container ginput_container_text'>%s</div>", $input); + } + + /** + * Validate Field + */ + public function validate($value, $form) + { + try { + if (strlen($value) < 5) { + throw new Exception(); + } + + $value = strtolower(str_replace(' ', '', $value)); + $value = preg_replace('/\s/', '', strtoupper($value)); + + $valid = false; + $type = $this->id_type($value); + switch ($type) { + case 'dni': + $valid = $this->validate_dni($value); + break; + case 'nie': + $valid = $this->validate_nie($value); + break; + case 'cif': + $valid = $this->validate_cif($value); + break; + } + + if (!$valid) { + throw new Exception(); + } + } catch (Exception $e) { + $this->failed_validation = true; + $this->validation_message = empty($this->errorMessage) ? __('The VAT number you\'ve inserted is not valid.', 'wpct-erp-forms') : $this->errorMessage; + } + } + + private function id_type($value) + { + if (preg_match(GFField::$_dni_regex, $value)) { + return 'dni'; + } elseif (preg_match(GFField::$_nie_regex, $value)) { + return 'nie'; + } elseif (preg_match(GFField::$_cif_regex, $value)) { + return 'cif'; + } + } + + private function validate_dni($value) + { + $dni_letters = 'TRWAGMYFPDXBNJZSQVHLCKE'; + $number = (int) substr($value, 0, 8); + $index = $number % 23; + $letter = substr($dni_letters, $index, 1); + + return $letter == substr($value, 8, 9); + } + + private function validate_nie($value) + { + $nie_prefix = substr($value, 0, 1); + + switch ($nie_prefix) { + case 'X': + $nie_prefix = 0; + break; + case 'Y': + $nie_prefix = 1; + break; + case 'Z': + $nie_prefix = 2; + break; + } + + return $this->validate_dni($nie_prefix . substr($value, 1, 9)); + } + + private function validate_cif($value) + { + preg_match(GFField::$_cif_regex, $value, $matches); + $letter = $matches[1]; + $number = $matches[2]; + $control = $matches[3]; + + $even_sum = 0; + $odd_sum = 0; + $n = null; + + for ($i = 0; $i < strlen($number); $i++) { + $n = (int) $number[$i]; + + if ($i % 2 === 0) { + // Odd positions are multiplied first. + $n *= 2; + // If the multiplication is bigger than 10 we need to adjust + $odd_sum += $n < 10 ? $n : $n - 9; + + // Even positions + // Just sum them + } else { + $even_sum += $n; + } + } + + $control_digit = 10 - (int) substr(strval($even_sum + $odd_sum), -1); + $control_letter = substr('JABCDEFGHI', $control_digit, 1); + + if (preg_match('/[ABEH]/', $letter)) { + // Control must be a digit + return (int) $control === $control_digit; + } elseif (preg_match('/[KPQS]/', $letter)) { + // Control must be a letter + return (string) $control === $control_letter; + } else { + // Can be either + return (int) $control === $control_digit || (string) $control === $control_letter; + } + } +} diff --git a/includes/integrations/gf/class-integration.php b/includes/integrations/gf/class-integration.php index 064d267..d5e6136 100644 --- a/includes/integrations/gf/class-integration.php +++ b/includes/integrations/gf/class-integration.php @@ -6,17 +6,20 @@ use Exception; use TypeError; use WPCT_ERP_FORMS\Abstract\Integration as BaseIntegration; use WPCT_ERP_FORMS\GF\Fields\Iban\FieldAdapter as IbanField; +use WPCT_ERP_FORMS\GF\Fields\VatID\FieldAdapter as VatIDField; require_once 'attachments.php'; require_once 'fields-population.php'; // Fields require_once dirname(__FILE__, 3) . '/fields/gf/iban/class-field-adapter.php'; +require_once dirname(__FILE__, 3) . '/fields/gf/vat-id/class-field-adapter.php'; class Integration extends BaseIntegration { public static $fields = [ - IbanField::class + IbanField::class, + VatIDField::class, ]; protected function __construct() @@ -24,6 +27,8 @@ class Integration extends BaseIntegration add_action('gform_after_submission', function ($entry, $form) { $this->do_submission($entry, $form); }, 10, 2); + + parent::__construct(); } public function serialize_form($form) @@ -58,21 +63,21 @@ class Integration extends BaseIntegration $inputs = $field->get_entry_inputs(); if (is_array($inputs)) { - $inputs = array_map(function ($input) { - return ['name' => $input['name'], 'label' => $input['label'], 'id' => $input['id']]; - }, array_filter($inputs, function ($input) { - return $input['name']; - })); - } else { - $inputs = []; - } - - $options = []; - if (is_array($field->choices)) { - $options = array_map(function ($opt) { - return ['value' => $opt['value'], 'label' => $opt['text']]; - }, $field->choices); - } + $inputs = array_map(function ($input) { + return ['name' => $input['name'], 'label' => $input['label'], 'id' => $input['id']]; + }, array_filter($inputs, function ($input) { + return $input['name']; + })); + } else { + $inputs = []; + } + + $options = []; + if (is_array($field->choices)) { + $options = array_map(function ($opt) { + return ['value' => $opt['value'], 'label' => $opt['text']]; + }, $field->choices); + } return [ 'id' => $field->id, @@ -81,8 +86,8 @@ class Integration extends BaseIntegration 'label' => $field->label, 'required' => $field->isRequired, 'options' => $options, - 'inputs' => $inputs, - 'conditional' => is_array($field->conditionalLogic) && $field->conditionalLogic['enabled'], + 'inputs' => $inputs, + 'conditional' => is_array($field->conditionalLogic) && $field->conditionalLogic['enabled'], ]; } diff --git a/wpct-erp-forms.php b/wpct-erp-forms.php index aa31f03..eac352d 100755 --- a/wpct-erp-forms.php +++ b/wpct-erp-forms.php @@ -36,6 +36,9 @@ require_once 'includes/class-settings.php'; require_once 'custom-blocks/form/form.php'; require_once 'custom-blocks/form-control/form-control.php'; +require_once 'includes/fields/gf/iban/class-field-adapter.php'; +require_once 'includes/fields/gf/vat-id/class-field-adapter.php'; + class Wpct_Erp_Forms extends Abstract\Plugin { private $_integrations = []; @@ -61,10 +64,6 @@ class Wpct_Erp_Forms extends Abstract\Plugin public function init() { - foreach ($this->_integrations as $integration) { - $integration->init(); - } - add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']); } @@ -106,4 +105,4 @@ class Wpct_Erp_Forms extends Abstract\Plugin add_action('plugins_loaded', function () { $plugin = Wpct_Erp_Forms::get_instance(); -}, 10); +}, 5); -- GitLab