Commit b0877ed7 authored by Elias Villiger's avatar Elias Villiger
Browse files

Feature #5414 - Add checkType Auto, refactor setDefault methods, add smart detection of defaults

parent 2393562a
......@@ -84,6 +84,7 @@ abstract class AbstractBuildForm {
* @param array $formSpec
* @param array $feSpecAction
* @param array $feSpecNative
* @param array $db
*/
public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $db) {
$this->formSpec = $formSpec;
......@@ -165,11 +166,13 @@ abstract class AbstractBuildForm {
*
* @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
*
* @return string|array $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE: array of all
* @param bool $htmlElementNameIdZero
* @param array $latestFeSpecNative
* @return array|string $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE: array of all
* formElement.dynamicUpdate-yes values/states
* @throws CodeException
* @throws DbException
* @throws \qfq\UserFormException
* @throws UserFormException
*/
public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) {
$htmlHead = '';
......@@ -1094,8 +1097,6 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute('data-match', '[name=' . str_replace(':', '\\:', $htmlFormElementNamePrimary) . ']');
}
$this->adjustMaxLength($formElement);
if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') {
// crop string only if it's not empty (substr returns false on empty strings)
$value = substr($value, 0, $formElement[FE_MAX_LENGTH]);
......@@ -1267,186 +1268,6 @@ abstract class AbstractBuildForm {
return $sql;
}
/**
* Calculates the maxlength of an input field, based on formElement type, formElement user definition and
* table.field definition.
*
* @param array $formElement
*/
private function adjustMaxLength(array &$formElement) {
// MIN( $formElement['maxLength'], tabledefinition)
$maxLength = $this->getColumnSize($formElement);
$feMaxLength = false;
switch ($formElement[FE_TYPE]) {
case 'date':
$feMaxLength = 10;
break;
case 'datetime':
$feMaxLength = 19;
break;
case 'time':
$feMaxLength = 8;
break;
}
// In case there is no limit of the underlying table column, or a non primary table column, and the FE_TYPE is date/time.
if ($maxLength === false && $feMaxLength !== false) {
$maxLength = $feMaxLength;
}
// In case the underlying table column is not of type date/time, the $maxLength might be to high: correct
if ($feMaxLength !== false && $maxLength !== false && $feMaxLength < $maxLength) {
$maxLength = $feMaxLength;
}
// date/datetime
if ($maxLength !== false) {
if (is_numeric($formElement['maxLength']) && $formElement['maxLength'] != 0) {
if ($formElement['maxLength'] > $maxLength) {
$formElement['maxLength'] = $maxLength;
}
} else {
$formElement['maxLength'] = $maxLength;
}
}
}
/**
* Get column spec from tabledefinition and parse size of it. If nothing defined, return false.
*
* @param $formElement
* @return bool|int a) 'false' if there is no length definition, b) length definition, c)
* date|time|datetime|timestamp use hardcoded length
*
*/
private function getColumnSize(array &$formElement) {
$column = $formElement[FE_NAME];
$matches = array();
$inputType = 'number';
$typeSpec = $this->store->getVar($column, STORE_TABLE_COLUMN_TYPES);
switch ($typeSpec) {
case 'date': // yyyy-mm-dd
return 10;
case 'datetime': // yyyy-mm-dd hh:mm:ss
case 'timestamp': // yyyy-mm-dd hh:mm:ss
return 19;
case 'time': // hh:mm:ss
return 8;
default:
if (substr($typeSpec, 0, 4) === 'set(' || substr($typeSpec, 0, 5) === 'enum(') {
return $this->maxLengthSetEnum($typeSpec);
}
break;
}
// $typeSpec = 'tinyint(3) UNSIGNED NOT NULL' | 'int(11) NOT NULL'
$arr = explode(' ', $typeSpec, 2);
if (empty($arr[1])) {
$sign = 'signed';
} else {
$arr = explode(' ', $arr[1], 2);
$sign = $arr[0] == 'unsigned' ? 'unsigned' : 'signed';
}
$arr = explode('(', $typeSpec, 2);
$token = $arr[0];
# s: signed, u: unsigned.
# s-min, s-max, s-checktype, u-min, u-max, u-checktype
$control = [
'tinyint' => [-128, 127, SANITIZE_ALLOW_NUMERICAL, 0, 255, SANITIZE_ALLOW_DIGIT],
'smallint' => [-32768, 32767, SANITIZE_ALLOW_NUMERICAL, 0, 65535, SANITIZE_ALLOW_DIGIT],
'mediumint' => [-8388608, 8388607, SANITIZE_ALLOW_NUMERICAL, 0, 16777215, SANITIZE_ALLOW_DIGIT],
'int' => [0, 4294967295, SANITIZE_ALLOW_NUMERICAL, -2147483648, 2147483647, SANITIZE_ALLOW_DIGIT],
'bigint' => [-9223372036854775808, 9223372036854775807, SANITIZE_ALLOW_NUMERICAL, 0, 18446744073709551615, SANITIZE_ALLOW_DIGIT],
];
$min = false;
$max = false;
$checkType = false;
switch ($token) {
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'int':
case 'bigint':
$arr = $control[$token];
if ($sign == 'signed') {
$min = $arr[0];
$max = $arr[1];
$checkType = $arr[2];
} else {
$min = $arr[3];
$max = $arr[4];
$checkType = $arr[5];
}
break;
case 'decimal':
case 'float':
case 'double':
$checkType = SANITIZE_ALLOW_NUMERICAL;
$inputType = 'text';
break;
case 'bit':
$checkType = SANITIZE_ALLOW_DIGIT;
break;
}
if ($min !== false && $formElement[FE_MIN] == '') {
$formElement[FE_MIN] = $min;
}
if ($max !== false && $formElement[FE_MAX] == '') {
$formElement[FE_MAX] = $max;
}
// if given, force Checktype
if ($checkType !== false) {
$formElement[FE_CHECK_TYPE] = $checkType;
if (empty($formElement[FE_INPUT_TYPE])) {
$formElement[FE_INPUT_TYPE] = $inputType;
}
}
// e.g.: string(64) >> 64, enum('yes','no') >> false
if (1 === preg_match('/\((.+)\)/', $typeSpec, $matches)) {
if (is_numeric($matches[1]))
return $matches[1];
}
return false;
}
/**
* Get the strlen of the longest element in enum('val1','val2',...,'valn') or set('val1','val2',...,'valn')
*
* @param string $typeSpec
*
* @return int
*/
private function maxLengthSetEnum($typeSpec) {
$startPos = (substr($typeSpec, 0, 4) === 'set(') ? 4 : 5;
$max = 0;
$valueList = substr($typeSpec, $startPos, strlen($typeSpec) - $startPos - 1);
$valueArr = explode(',', $valueList);
foreach ($valueArr as $value) {
$value = trim($value, "'");
$len = strlen($value);
if ($len > $max) {
$max = $len;
}
}
return $max;
}
/**
* Builds a HTML attribute list, based on $attributeList.
*
......@@ -3136,7 +2957,6 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute('name', $htmlFormElementName);
$attribute .= Support::doAttribute('class', 'form-control');
$this->adjustMaxLength($formElement);
$showTime = ($formElement[FE_TYPE] == 'time' || $formElement[FE_TYPE] == 'datetime') ? 1 : 0;
if ($value == 'CURRENT_TIMESTAMP') {
$value = date('Y-m-d H:i:s');
......@@ -3282,7 +3102,6 @@ abstract class AbstractBuildForm {
// throw new UserFormException("Checktype not applicable for date/time: '" . $formElement['checkType'] . "'", ERROR_NOT_APPLICABLE);
// }
$this->adjustMaxLength($formElement);
$showTime = ($formElement[FE_TYPE] == 'time' || $formElement[FE_TYPE] == 'datetime') ? 1 : 0;
$value = Support::convertDateTime($value, $formElement[FE_DATE_FORMAT], $formElement[FE_SHOW_ZERO], $showTime, $formElement[FE_SHOW_SECONDS]);
......@@ -3331,8 +3150,6 @@ abstract class AbstractBuildForm {
//TODO plugin autoresize nutzen um Editorgroesse anzugeben
$this->adjustMaxLength($formElement);
$attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
$attribute .= Support::doAttribute('name', $htmlFormElementName);
// $attribute .= Support::doAttribute('id', $htmlFormElementName);
......
......@@ -65,6 +65,7 @@ const NAME_TG_COPIES = '_tgCopies'; // Number of templatesGroup copies to creat
const FE_TG_INDEX = '_tgIndex'; // Index of the current copy of a templateGroup FE.
// SANITIZE Classifier
const SANITIZE_ALLOW_AUTO = "auto"; // Default for FormElements
const SANITIZE_ALLOW_ALNUMX = "alnumx";
const SANITIZE_ALLOW_DIGIT = "digit";
const SANITIZE_ALLOW_NUMERICAL = "numerical";
......@@ -72,7 +73,7 @@ const SANITIZE_ALLOW_EMAIL = "email";
const SANITIZE_ALLOW_PATTERN = "pattern";
const SANITIZE_ALLOW_ALLBUT = "allbut";
const SANITIZE_ALLOW_ALL = "all";
const SANITIZE_DEFAULT = SANITIZE_ALLOW_DIGIT;
const SANITIZE_DEFAULT = SANITIZE_ALLOW_DIGIT; // for {{variable}} expressions without checkType
const SANITIZE_EXCEPTION = 'exception';
const SANITIZE_EMPTY_STRING = 'empty string';
......
......@@ -103,6 +103,10 @@ $UPDATE_ARRAY = array(
"ALTER TABLE `FormElement` CHANGE `checkType` `checkType` ENUM('alnumx','digit','numerical','email','pattern','allbut','all') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'alnumx';",
],
'0.25.12' => [
"ALTER TABLE `FormElement` CHANGE `checkType` `checkType` ENUM('auto','alnumx','digit','numerical','email','pattern','allbut','all') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'auto';"
],
);
......
......@@ -681,7 +681,6 @@ class Support {
*/
public static function mergeUrlComponents($host, $hostOrPath, $query) {
$url = '';
$pre = '';
if ($host != '' && substr($host, -1, 1) != '/') {
......@@ -775,13 +774,133 @@ class Support {
self::setIfNotSet($formElement, FE_MIN);
self::setIfNotSet($formElement, FE_MAX);
// decimalFormat
self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);
self::setIfNotSet($formElement, F_FE_DATA_PATTERN_ERROR);
$fieldTypeDefinition = $store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES);
self::adjustFeToColumnDefinition($formElement, $fieldTypeDefinition);
return $formElement;
}
/**
* Adjusts several FE parameters using smart guesses based on the table column definition and other parameters.
*
* @param array $formElement
* @param $typeSpec
* @throws UserFormException
*/
private function adjustFeToColumnDefinition(array &$formElement, $typeSpec) {
self::adjustMaxLength($formElement, $typeSpec);
self::adjustDecimalFormat($formElement, $typeSpec);
// Make educated guesses about the desired $min, $max, $checkType, and $inputType
// $typeSpec = 'tinyint(3) UNSIGNED NOT NULL' | 'int(11) NOT NULL'
$arr = explode(' ', $typeSpec, 2);
if (empty($arr[1])) {
$sign = 'signed';
} else {
$arr = explode(' ', $arr[1], 2);
$sign = $arr[0] == 'unsigned' ? 'unsigned' : 'signed';
}
$arr = explode('(', $typeSpec, 2);
$token = $arr[0];
# s: signed, u: unsigned.
# s-min, s-max, s-checktype, u-min, u-max, u-checktype
$control = [
'tinyint' => [-128, 127, SANITIZE_ALLOW_NUMERICAL, 0, 255, SANITIZE_ALLOW_DIGIT],
'smallint' => [-32768, 32767, SANITIZE_ALLOW_NUMERICAL, 0, 65535, SANITIZE_ALLOW_DIGIT],
'mediumint' => [-8388608, 8388607, SANITIZE_ALLOW_NUMERICAL, 0, 16777215, SANITIZE_ALLOW_DIGIT],
'int' => [0, 4294967295, SANITIZE_ALLOW_NUMERICAL, -2147483648, 2147483647, SANITIZE_ALLOW_DIGIT],
'bigint' => [-9223372036854775808, 9223372036854775807, SANITIZE_ALLOW_NUMERICAL, 0, 18446744073709551615, SANITIZE_ALLOW_DIGIT],
];
$min = '';
$max = '';
$checkType = false;
$inputType = '';
switch ($token) {
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'int':
case 'bigint':
$inputType = 'number';
$arr = $control[$token];
if ($sign == 'signed') {
$min = $arr[0];
$max = $arr[1];
$checkType = $arr[2];
} else {
$min = $arr[3];
$max = $arr[4];
$checkType = $arr[5];
}
break;
case 'decimal':
case 'float':
case 'double':
$checkType = SANITIZE_ALLOW_NUMERICAL;
break;
case 'bit':
$inputType = 'number';
$checkType = SANITIZE_ALLOW_DIGIT;
break;
case 'text':
case 'varchar':
case 'tinytext':
case 'mediumtext':
case 'longtext':
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR)
$checkType = SANITIZE_ALLOW_ALL;
else
$checkType = SANITIZE_ALLOW_ALNUMX;
break;
}
if (!empty($formElement[FE_TYPEAHEAD_SQL])) {
$inputType = '';
$checkType = SANITIZE_ALLOW_ALNUMX;
}
// Set parameters if not set by user
if ($formElement[FE_CHECK_TYPE] === SANITIZE_ALLOW_AUTO) {
if ($checkType === false) {
$formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALNUMX; // fallback
} else {
$formElement[FE_CHECK_TYPE] = $checkType;
}
}
self::setIfNotSet($formElement, FE_MIN, $min);
self::setIfNotSet($formElement, FE_MAX, $max);
self::setIfNotSet($formElement, FE_INPUT_TYPE, $inputType);
}
/**
* Sets the decimalFormat of a FormElement based on the parameter definition and table.field definition
* Affected FormElement fields: FE_DECIMAL_FORMAT
*
* @param array $formElement
* @param $typeSpec
* @throws UserFormException
*/
private function adjustDecimalFormat(array &$formElement, $typeSpec) {
if (isset($formElement[FE_DECIMAL_FORMAT])) {
if ($formElement[FE_DECIMAL_FORMAT] === '') {
// Get decimal format from column definition
$fieldTypeDefinition = $store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES);
if ($fieldTypeDefinition !== false) {
$fieldTypeInfoArray = preg_split("/[()]/", $fieldTypeDefinition);
if ($typeSpec !== false) {
$fieldTypeInfoArray = preg_split("/[()]/", $typeSpec);
if ($fieldTypeInfoArray[0] === 'decimal')
$formElement[FE_DECIMAL_FORMAT] = $fieldTypeInfoArray[1];
}
......@@ -792,15 +911,118 @@ class Support {
$decimalFormatArray = explode(',', $formElement[FE_DECIMAL_FORMAT]);
$isValidDecimalFormat = $decimalFormatArray[0] >= $decimalFormatArray[1];
}
if (!$isValidDecimalFormat)
throw new UserFormException("Invalid decimalFormat.", ERROR_INVALID_DECIMAL_FORMAT);
if (!$isValidDecimalFormat) {
throw new UserFormException("Invalid decimalFormat: '" . $formElement[FE_DECIMAL_FORMAT] . "'", ERROR_INVALID_DECIMAL_FORMAT);
}
}
}
self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);
}
self::setIfNotSet($formElement, F_FE_DATA_PATTERN_ERROR);
/**
* Calculates the maxLength of an input field, based on formElement type, formElement user definition and
* table.field definition.
* Affected formElement fields: FE_MAX_LENGTH
*
* @param array $formElement
* @param $typeSpec
*/
private function adjustMaxLength(array &$formElement, $typeSpec) {
return $formElement;
// MIN( $formElement['maxLength'], table definition)
$maxLength = self::getColumnSize($typeSpec);
$feMaxLength = false;
switch ($formElement[FE_TYPE]) {
case 'date':
$feMaxLength = 10;
break;
case 'datetime':
$feMaxLength = 19;
break;
case 'time':
$feMaxLength = 8;
break;
}
// In case there is no limit of the underlying table column, or a non primary table column, and the FE_TYPE is date/time.
if ($maxLength === false && $feMaxLength !== false) {
$maxLength = $feMaxLength;
}
// In case the underlying table column is not of type date/time, the $maxLength might be too high: correct
if ($feMaxLength !== false && $maxLength !== false && $feMaxLength < $maxLength) {
$maxLength = $feMaxLength;
}
// date/datetime
if ($maxLength !== false) {
if (is_numeric($formElement[FE_MAX_LENGTH]) && $formElement[FE_MAX_LENGTH] != 0) {
if ($formElement[FE_MAX_LENGTH] > $maxLength) {
$formElement[FE_MAX_LENGTH] = $maxLength;
}
} else {
$formElement[FE_MAX_LENGTH] = $maxLength;
}
}
}
/**
* Get column spec from table definition and parse size of it. If nothing defined, return false.
*
* @param $typeSpec
* @return bool|int a) 'false' if there is no length definition, b) length definition, c)
* date|time|datetime|timestamp use hardcoded length
*/
private function getColumnSize($typeSpec) {
$matches = array();
switch ($typeSpec) {
case 'date': // yyyy-mm-dd
return 10;
case 'datetime': // yyyy-mm-dd hh:mm:ss
case 'timestamp': // yyyy-mm-dd hh:mm:ss
return 19;
case 'time': // hh:mm:ss
return 8;
default:
if (substr($typeSpec, 0, 4) === 'set(' || substr($typeSpec, 0, 5) === 'enum(') {
return self::maxLengthSetEnum($typeSpec);
}
break;
}
// e.g.: string(64) >> 64, enum('yes','no') >> false
if (1 === preg_match('/\((.+)\)/', $typeSpec, $matches)) {
if (is_numeric($matches[1]))
return $matches[1];
}
return false;
}
/**
* Get the strlen of the longest element in enum('val1','val2',...,'valn') or set('val1','val2',...,'valn')
*
* @param string $typeSpec
*
* @return int
*/
private function maxLengthSetEnum($typeSpec) {
$startPos = (substr($typeSpec, 0, 4) === 'set(') ? 4 : 5;
$max = 0;
$valueList = substr($typeSpec, $startPos, strlen($typeSpec) - $startPos - 1);
$valueArr = explode(',', $valueList);
foreach ($valueArr as $value) {
$value = trim($value, "'");
$len = strlen($value);
if ($len > $max) {
$max = $len;
}
}
return $max;
}
/**
......
......@@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS `FormElement` (
'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail', 'paste') NOT NULL DEFAULT 'text',
`subrecordOption` SET('edit', 'delete', 'new') NOT NULL DEFAULT '',
`encode` ENUM('none', 'specialchar') NOT NULL DEFAULT 'specialchar',
`checkType` ENUM('alnumx', 'digit', 'numerical', 'email', 'pattern', 'allbut', 'all') NOT NULL DEFAULT 'alnumx',
`checkType` ENUM('auto', 'alnumx', 'digit', 'numerical', 'email', 'pattern', 'allbut', 'all') NOT NULL DEFAULT 'auto',
`checkPattern` VARCHAR(255) NOT NULL DEFAULT '',
`onChange` VARCHAR(255) NOT NULL DEFAULT '',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment