diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 3f609589a76d2eb75c97ab2623efe13e72dd1a2a..0a863bcbcf02469899050a3920fb2bde68f4eb91 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -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); diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 2ccb7903cec82678599e6b2d731c1bc8b9a5e7dc..42e245a34c487204f45ccc65ad88d2e8d26362a6 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -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'; diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php index c936930ef9f156d6bedb2b6be79175dbc0cb84a2..fc484ae1633819f7a0f67e6b258fac07c85513c8 100644 --- a/extension/qfq/qfq/helper/Support.php +++ b/extension/qfq/qfq/helper/Support.php @@ -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; } /** diff --git a/extension/qfq/sql/formEditor.sql b/extension/qfq/sql/formEditor.sql index 028095558ae051fde3a41608bd56b567ae6b1d62..c5f0fb6cb822b06112896987765478307d3cfc7a 100644 --- a/extension/qfq/sql/formEditor.sql +++ b/extension/qfq/sql/formEditor.sql @@ -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 '',