View source
<?php
namespace Drupal\webform\Plugin\WebformElement;
use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\Element\WebformHtmlEditor;
use Drupal\webform\Plugin\WebformElementBase;
use Drupal\webform\Utility\WebformElementHelper;
use Drupal\webform\Utility\WebformHtmlHelper;
use Drupal\webform\Utility\WebformTextHelper;
use Drupal\webform\WebformSubmissionInterface;
abstract class TextBase extends WebformElementBase {
use TextBaseTrait;
protected function defineDefaultProperties() {
return [
'readonly' => FALSE,
'size' => NULL,
'minlength' => NULL,
'maxlength' => NULL,
'placeholder' => '',
'autocomplete' => 'on',
'pattern' => '',
'pattern_error' => '',
] + parent::defineDefaultProperties();
}
protected function defineTranslatableProperties() {
return array_merge(parent::defineTranslatableProperties(), [
'default_value',
'counter_minimum_message',
'counter_maximum_message',
'pattern_error',
]);
}
public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) {
parent::prepare($element, $webform_submission);
if (!empty($element['#counter_type']) && (!empty($element['#counter_minimum']) || !empty($element['#counter_maximum'])) && $this->librariesManager
->isIncluded('jquery.textcounter')) {
if ($element['#counter_type'] === 'character') {
if (!empty($element['#counter_minimum'])) {
$element['#minlength'] = $element['#counter_minimum'];
}
if (!empty($element['#counter_maximum'])) {
$element['#maxlength'] = $element['#counter_maximum'];
}
}
$data_attributes = [
'counter_type',
'counter_minimum',
'counter_minimum_message',
'counter_maximum',
'counter_maximum_message',
];
foreach ($data_attributes as $data_attribute) {
if (empty($element['#' . $data_attribute])) {
continue;
}
$data_attribute_name = 'data-' . str_replace('_', '-', $data_attribute);
$data_attribute_value = $element['#' . $data_attribute];
if (in_array($data_attribute, [
'counter_minimum_message',
'counter_maximum_message',
])) {
$data_attribute_value = WebformHtmlEditor::stripTags($data_attribute_value);
}
$element['#attributes'][$data_attribute_name] = $data_attribute_value;
}
$element['#attributes']['class'][] = 'js-webform-counter';
$element['#attributes']['class'][] = 'webform-counter';
$element['#attached']['library'][] = 'webform/webform.element.counter';
$element['#element_validate'][] = [
get_class($this),
'validateCounter',
];
}
if (!empty($element['#input_mask']) && $this->librariesManager
->isIncluded('jquery.inputmask')) {
$input_mask = $element['#input_mask'];
if (preg_match("/^'[^']+'\\s*:/", $input_mask)) {
$element['#attributes']['data-inputmask'] = $input_mask;
}
else {
$element['#attributes']['data-inputmask-mask'] = $input_mask;
}
$input_masks = $this
->getInputMasks();
if (isset($input_masks[$input_mask]) && isset($input_masks[$input_mask]['pattern']) && empty($element['#pattern'])) {
$element['#pattern'] = $input_masks[$input_mask]['pattern'];
}
$element['#attributes']['class'][] = 'js-webform-input-mask';
$element['#attached']['library'][] = 'webform/webform.element.inputmask';
$element['#element_validate'][] = [
get_called_class(),
'validateInputMask',
];
}
if (!empty($element['#input_hide'])) {
$element['#attributes']['class'][] = 'js-webform-input-hide';
$element['#attached']['library'][] = 'webform/webform.element.inputhide';
}
if (isset($element['#pattern'])) {
$element['#attributes']['pattern'] = $element['#pattern'];
$element['#element_validate'][] = [
get_called_class(),
'validatePattern',
];
if (!empty($element['#pattern_error'])) {
$element['#attributes']['data-webform-pattern-error'] = WebformHtmlHelper::toPlainText($element['#pattern_error']);
}
}
if (isset($element['#minlength'])) {
$element['#attributes']['minlength'] = $element['#minlength'];
}
}
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['form']['input_mask'] = [
'#type' => 'webform_select_other',
'#title' => $this
->t('Input masks'),
'#description' => $this
->t('An <a href=":href">inputmask</a> helps the user with the element by ensuring a predefined format.', [
':href' => 'https://github.com/RobinHerbots/jquery.inputmask',
]),
'#other__option_label' => $this
->t('Custom…'),
'#other__placeholder' => $this
->t('Enter input mask…'),
'#other__description' => $this
->t('(9 = numeric; a = alphabetical; * = alphanumeric)'),
'#empty_option' => $this
->t('- None -'),
'#options' => $this
->getInputMaskOptions(),
];
if ($this->librariesManager
->isExcluded('jquery.inputmask')) {
$form['form']['input_mask']['#access'] = FALSE;
}
$form['form']['input_hide'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Input hiding'),
'#description' => $this
->t('Hide the input of the element when the input is not being focused.'),
'#return_value' => TRUE,
];
$form['validation']['pattern'] = [
'#type' => 'webform_checkbox_value',
'#title' => $this
->t('Pattern'),
'#description' => $this
->t('A <a href=":href">regular expression</a> that the element\'s value is checked against.', [
':href' => 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions',
]),
'#value__title' => $this
->t('Pattern regular expression'),
'#value__description' => $this
->t('Enter a <a href=":href">regular expression</a> that the element\'s value should match.', [
':href' => 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions',
]),
'#value__maxlength' => NULL,
];
$form['validation']['pattern_error'] = [
'#type' => 'textfield',
'#title' => $this
->t('Pattern message'),
'#description' => $this
->t('If set, this message will be used when a pattern is not matched, instead of the default "@message" message.', [
'@message' => $this
->t('%name field is not in the right format.'),
]),
'#states' => [
'visible' => [
':input[name="properties[pattern][checkbox]"]' => [
'checked' => TRUE,
],
],
],
];
$form['validation'] += $this
->buildCounterForm();
if (isset($form['form']['maxlength'])) {
$form['form']['maxlength']['#description'] .= ' ' . $this
->t('If character counter is enabled, maxlength will automatically be set to the count maximum.');
$form['form']['maxlength']['#states'] = [
'invisible' => [
':input[name="properties[counter_type]"]' => [
'value' => 'character',
],
],
];
}
return $form;
}
public static function validateCounter(array &$element, FormStateInterface $form_state) {
$value = $element['#value'];
if ($value === '') {
return;
}
$type = $element['#counter_type'];
$max = !empty($element['#counter_maximum']) ? $element['#counter_maximum'] : NULL;
$min = !empty($element['#counter_minimum']) ? $element['#counter_minimum'] : NULL;
$t_args = [
'@type' => $type === 'character' ? t('characters') : t('words'),
'@name' => $element['#title'],
'%max' => $max,
'%min' => $min,
];
if ($type === 'character') {
$length = mb_strlen($value);
$t_args['%length'] = $length;
}
elseif ($type === 'word') {
$length = WebformTextHelper::wordCount($value);
$t_args['%length'] = $length;
}
if ($max && $length > $max) {
$form_state
->setError($element, t('@name cannot be longer than %max @type but is currently %length @type long.', $t_args));
}
elseif ($min && $length < $min) {
$form_state
->setError($element, t('@name must be longer than %min @type but is currently %length @type long.', $t_args));
}
}
public static function validateInputMask(&$element, FormStateInterface $form_state, &$complete_form) {
if (!empty($element['#required']) && static::isDefaultInputMask($element, $element['#value'])) {
WebformElementHelper::setRequiredError($element, $form_state);
}
}
public static function isDefaultInputMask(array $element, $value) {
if (empty($element['#input_mask']) || $value === '') {
return FALSE;
}
$input_mask = $element['#input_mask'];
$input_masks = [
"'alias': 'currency'" => '$ 0.00',
"'alias': 'currency_negative'" => '-$ 0.00',
"'alias': 'currency_positive_negative'" => '$ 0.00',
];
return isset($input_masks[$input_mask]) && $input_masks[$input_mask] === $value ? TRUE : FALSE;
}
public static function validatePattern(&$element, FormStateInterface $form_state, &$complete_form) {
if ($element['#value'] !== '') {
$pcre_pattern = preg_replace('/\\\\u([a-fA-F0-9]{4})/', '\\x{\\1}', $element['#pattern']);
$pattern = '{^(?:' . $pcre_pattern . ')$}u';
if (!preg_match($pattern, $element['#value'])) {
if (!empty($element['#pattern_error'])) {
$form_state
->setError($element, WebformHtmlHelper::toHtmlMarkup($element['#pattern_error']));
}
else {
$form_state
->setError($element, t('%name field is not in the right format.', [
'%name' => $element['#title'],
]));
}
}
}
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
$properties = $this
->getConfigurationFormProperties($form, $form_state);
if (!empty($properties['#pattern'])) {
set_error_handler('_webform_entity_element_validate_rendering_error_handler');
$pcre_pattern = preg_replace('/\\\\u([a-fA-F0-9]{4})/', '\\x{\\1}', $properties['#pattern']);
if (preg_match('{^(?:' . $pcre_pattern . ')$}u', NULL) === FALSE) {
$form_state
->setErrorByName('pattern', $this
->t('Pattern %pattern is not a valid regular expression.', [
'%pattern' => $properties['#pattern'],
]));
}
set_error_handler('_drupal_error_handler');
}
if (!empty($properties['#counter_type']) && empty($properties['#counter_maximum']) && empty($properties['#counter_minimum'])) {
$form_state
->setErrorByName('counter_minimum', $this
->t('Counter minimum or maximum is required.'));
$form_state
->setErrorByName('counter_maximum', $this
->t('Counter minimum or maximum is required.'));
}
}
protected function getInputMasks() {
$input_masks = [
"'alias': 'currency'" => [
'title' => $this
->t('Currency (+)'),
'example' => '$ 9.99',
'pattern' => '^\\$ [0-9]{1,3}(,[0-9]{3})*.\\d\\d$',
],
"'alias': 'currency_negative'" => [
'title' => $this
->t('Currency (-)'),
'example' => '-$ 9.99',
'pattern' => '^(-\\$ [0-9]{1,3}(,[0-9]{3})*.\\d\\d|\\$ 0.00)$',
],
"'alias': 'currency_positive_negative'" => [
'title' => $this
->t('Currency (+/-)'),
'example' => '$ 9.99',
'pattern' => '^[-]?\\$ [0-9]{1,3}(,[0-9]{3})*.\\d\\d$',
],
"'alias': 'datetime'" => [
'title' => $this
->t('Date'),
'example' => '2007-06-09\'T\'17:46:21',
],
"'alias': 'decimal'" => [
'title' => $this
->t('Decimal'),
'example' => '1.234',
'pattern' => '^\\d+(\\.\\d+)?$',
],
"'alias': 'email'" => [
'title' => $this
->t('Email'),
'example' => 'example@example.com',
],
"'alias': 'ip'" => [
'title' => $this
->t('IP address'),
'example' => '255.255.255.255',
'pattern' => '^\\d\\d\\d\\.\\d\\d\\d\\.\\d\\d\\d\\.\\d\\d\\d$',
],
'[9-]AAA-999' => [
'title' => $this
->t('License plate'),
'example' => '[9-]AAA-999',
],
"'alias': 'mac'" => [
'title' => $this
->t('MAC address'),
'example' => '99-99-99-99-99-99',
'pattern' => '^\\d\\d-\\d\\d-\\d\\d-\\d\\d-\\d\\d-\\d\\d$',
],
"'alias': 'percentage'" => [
'title' => $this
->t('Percentage'),
'example' => '99 %',
'pattern' => '^\\d+ %$',
],
'(999) 999-9999' => [
'title' => $this
->t('Phone'),
'example' => '(999) 999-9999',
'pattern' => '^\\(\\d\\d\\d\\) \\d\\d\\d-\\d\\d\\d\\d$',
],
'999-99-9999' => [
'title' => $this
->t('Social Security Number (SSN)'),
'example' => '999-99-9999',
'pattern' => '^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$',
],
"'alias': 'vin'" => [
'title' => $this
->t('Vehicle identification number (VIN)'),
'example' => 'JA3AY11A82U020534',
],
'99999[-9999]' => [
'title' => $this
->t('ZIP Code'),
'example' => '99999[-9999]',
'pattern' => '^\\d\\d\\d\\d\\d(-\\d\\d\\d\\d)?$',
],
"'casing': 'upper'" => [
'title' => $this
->t('Uppercase'),
'example' => 'UPPERCASE',
],
"'casing': 'lower'" => [
'title' => $this
->t('Lowercase'),
'example' => 'lowercase',
],
];
$modules = $this->moduleHandler
->getImplementations('webform_element_input_masks');
foreach ($modules as $module) {
$input_masks += $this->moduleHandler
->invoke($module, 'webform_element_input_masks');
}
$this->moduleHandler
->alter('webform_element_input_masks', $input_masks);
return $input_masks;
}
protected function getInputMaskOptions() {
$input_masks = $this
->getInputMasks();
$options = [];
foreach ($input_masks as $input_mask => $settings) {
$options[$input_mask] = $settings['title'] . (!empty($settings['example']) ? ' - ' . $settings['example'] : '');
}
return $options;
}
}