Commit 3bb338a2 authored by Elias Villiger's avatar Elias Villiger
Browse files

Feature #4542 - Working version with client- and server-side decimalFormat validation

parent a52e56ed
......@@ -26,6 +26,7 @@ require_once(__DIR__ . '/helper/Support.php');
require_once(__DIR__ . '/helper/OnArray.php');
require_once(__DIR__ . '/helper/Ldap.php');
require_once(__DIR__ . '/report/Link.php');
require_once(__DIR__ . '/helper/Sanitize.php');
require_once(__DIR__ . '/report/Report.php');
/**
......@@ -41,7 +42,6 @@ abstract class AbstractBuildForm {
protected $wrap = array();
protected $symbol = array();
protected $showDebugInfoFlag = false;
protected $inputCheckPattern = array();
// protected $feDivClass = array(); // Wrap FormElements in <div class="$feDivClass[type]">
......@@ -156,8 +156,6 @@ abstract class AbstractBuildForm {
$this->symbol[SYMBOL_SHOW] = "<span class='glyphicon " . GLYPH_ICON_SHOW . "'></span>";
$this->symbol[SYMBOL_NEW] = "<span class='glyphicon " . GLYPH_ICON_NEW . "'></span>";
$this->symbol[SYMBOL_DELETE] = "<span class='glyphicon " . GLYPH_ICON_DELETE . "'></span>";
$this->inputCheckPattern = Sanitize::inputCheckPatternArray();
}
abstract public function fillWrap();
......@@ -1129,7 +1127,10 @@ abstract class AbstractBuildForm {
$attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
$attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement[FE_TOOLTIP]);
$attribute .= $this->getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], $formElement[FE_DECIMAL_FORMAT]);
$attribute .= ($pattern === '') ? '' : 'pattern="' . $pattern . '" ';
$attribute .= $this->getAttributeList($formElement, [FE_MIN, FE_MAX]);
$attribute .= $this->getAttributeFeMode($formElement[FE_MODE], false);
......@@ -1374,47 +1375,6 @@ abstract class AbstractBuildForm {
return $attribute;
}
/**
* Construct HTML Input attribute for Client Validation:
*
* type data result
* ------- ----------------------- ----------------------------------------------------------------
* pattern <regexp> pattern="$data"
* digit - pattern="^[0-9]*$"
* email - pattern="^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$"
* alnumx -
*
* For 'pattern' the 'data' will be injected in the attribute string via '%s'.
*
* @param string $type
* @param string $data
*
* @return string
* @throws \qfq\UserFormException
*/
private function getInputCheckPattern($type, $data) {
$attribute = '';
if ($type === '') {
return '';
}
switch ($type) {
case SANITIZE_ALLOW_PATTERN:
$attribute = 'pattern="' . $data . '" ';
break;
case SANITIZE_ALLOW_ALL:
break;
default:
$attribute = 'pattern="' . $this->inputCheckPattern[$type] . '" ';
break;
}
return $attribute;
}
/**
* Set corresponding html attributes readonly/required/disabled, based on $formElement[FE_MODE].
*
......@@ -1642,44 +1602,6 @@ abstract class AbstractBuildForm {
return $items;
}
/**
* Get the size and decimal-point precision of a number from the table definition.
* Returns an array with the first item being the size, the second item being the precision.
* Returns null when no info found.
*
* @param string $column
*
* @return array
*/
public function getDecimalInfoFromTable($column) {
// Get column definition
$fieldTypeDefinition = $this->store->getVar($column, STORE_TABLE_COLUMN_TYPES);
if ($fieldTypeDefinition === false)
return null; // not part of the table definition
$fieldTypeInfoArray = preg_split("/[()]/", $fieldTypeDefinition);
switch ($fieldTypeInfoArray[0]) {
case 'decimal':
case 'float':
case 'double':
case 'real':
$sizeAndPrecision = explode(",", $fieldTypeInfoArray[1]);
return [ $sizeAndPrecision[0], $sizeAndPrecision[1] ];
case 'int':
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'bigint':
return [ $fieldTypeInfoArray[1], 0 ];
default:
return null;
}
}
/**
* For CheckBox's with only one checkbox: if no parameter:checked|unchecked is defined, take defaults:
*
......@@ -3181,7 +3103,10 @@ abstract class AbstractBuildForm {
$attribute .= $this->getAttributeList($formElement, [FE_INPUT_AUTOCOMPLETE, 'autofocus', 'placeholder']);
$attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement[FE_TOOLTIP]);
$attribute .= $this->getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN]);
$attribute .= ($pattern === '') ? '' : 'pattern="' . $pattern . '" ';
$attribute .= $this->getAttributeList($formElement, [FE_MIN, FE_MAX]);
$json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement);
......
......@@ -41,10 +41,40 @@ class Sanitize {
* @throws UserFormException
* @throws \qfq\CodeException
*/
public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = null, $mode = SANITIZE_EMPTY_STRING) {
// Prepare pattern check
switch ($sanitizeClass) {
public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '', $mode = SANITIZE_EMPTY_STRING) {
$pattern = self::getInputCheckPattern($sanitizeClass, $pattern, $decimalFormat);
// Pattern check
if ($pattern === '' || preg_match("/$pattern/", $value) === 1) {
return $value;
}
// check failed
if ($mode === SANITIZE_EXCEPTION) {
$errorCode = ERROR_PATTERN_VIOLATION;
$errorText = "Value '$value' violates checkrule " . $sanitizeClass . " with pattern '$pattern'.";
throw new UserFormException($errorText, $errorCode);
}
return SANITIZE_VIOLATE . $sanitizeClass . SANITIZE_VIOLATE;
}
/**
* Returns the final validation pattern based on a given $checkType, $pattern, and $decimalFormat
* @param string $checkType
* @param string $pattern
* @param string $decimalFormat e.g. "10,2"
*
* @return string
* @throws CodeException
*/
public static function getInputCheckPattern($checkType = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '') {
switch ($checkType) {
case SANITIZE_ALLOW_PATTERN:
return $pattern;
case SANITIZE_ALLOW_ALL:
$pattern = '';
break;
case SANITIZE_ALLOW_DIGIT:
......@@ -53,37 +83,21 @@ class Sanitize {
case SANITIZE_ALLOW_ALNUMX:
case SANITIZE_ALLOW_ALLBUT:
$arr = self::inputCheckPatternArray();
$pattern = $arr[$sanitizeClass];
$pattern = $arr[$checkType];
break;
case SANITIZE_ALLOW_ALL: // no checkType specified.
return $value;
default:
throw new CodeException("Unknown checkType: " . $sanitizeClass, ERROR_UNKNOWN_CHECKTYPE);
throw new CodeException("Unknown checkType: " . $checkType, ERROR_UNKNOWN_CHECKTYPE);
}
// decimalFormat
if ($decimalFormat !== null) {
if ($sanitizeClass !== SANITIZE_ALLOW_PATTERN && $sanitizeClass !== SANITIZE_ALLOW_DIGIT) {
// overwrite pattern
$pattern = self::getDecimalFormatPattern($decimalFormat);
}
}
// Pattern check
if ($pattern === '' || preg_match("/$pattern/", $value) === 1) {
return $value;
}
// check failed
if ($mode === SANITIZE_EXCEPTION) {
$errorCode = ERROR_PATTERN_VIOLATION;
$errorText = "Value '$value' violates checkrule " . $sanitizeClass . " with pattern '$pattern'.";
throw new UserFormException($errorText, $errorCode);
if ($decimalFormat != '' && $checkType !== SANITIZE_ALLOW_DIGIT) {
// overwrite pattern with decimalFormat pattern
$decimalFormatArray = explode(',', $decimalFormat);
$pattern = "^[0-9]{0,$decimalFormatArray[0]}(\.[0-9]{0,$decimalFormatArray[1]})?$";
}
return SANITIZE_VIOLATE . $sanitizeClass . SANITIZE_VIOLATE;
return $pattern;
}
/**
......@@ -98,9 +112,7 @@ class Sanitize {
* @throws UserFormException
* @throws \qfq\CodeException
*/
public static function checkMinMax($value, $formElement, $mode = SANITIZE_EMPTY_STRING) {
$min = Support::setIfNotSet($formElement, FE_MIN);
$max = Support::setIfNotSet($formElement, FE_MAX);
public static function checkMinMax($value, $min, $max, $mode = SANITIZE_EMPTY_STRING) {
$errorCode = 0;
$errorText = '';
......@@ -124,17 +136,6 @@ class Sanitize {
return '';
}
/**
* Returns the regexp pattern to match a decimal number with the format in $decimalFormat.
*
* @param array $decimalFormat with [ size, precision ]
*
* @return string
*/
public static function getDecimalFormatPattern($decimalFormat) {
return "^[0-9]{0,$decimalFormat[0]}(\.[0-9]{0,$decimalFormat[1]})?$";
}
/**
* @return array
*/
......@@ -152,7 +153,7 @@ class Sanitize {
}
/**
* Sanatizes a filename. Copied from http://www.phpit.net/code/filename-safe/
* Sanitizes a filename. Copied from http://www.phpit.net/code/filename-safe/
*
* @param $filename
*
......
......@@ -767,6 +767,27 @@ class Support {
break;
}
self::setIfNotSet($formElement, FE_MIN);
self::setIfNotSet($formElement, FE_MAX);
// decimalFormat
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 ($fieldTypeInfoArray[0] === 'decimal')
$formElement[FE_DECIMAL_FORMAT] = $fieldTypeInfoArray[1];
}
} else {
// Decimal format is defined in parameter field
if (!preg_match("/^[0-9]+,[0-9]+$/", $formElement[FE_DECIMAL_FORMAT]))
throw new UserFormException("Invalid decimalFormat.", ERROR_INVALID_DECIMAL_FORMAT);
}
}
self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);
return $formElement;
}
......
......@@ -237,28 +237,10 @@ class FillStoreForm {
$val = Support::unWrapTag('<p>', $val);
}
$decimalFormat = null;
if (isset($formElement[FE_DECIMAL_FORMAT])) {
// Read decimal format from parameter field
if (preg_match("/^([0-9]*)(,[0-9]+)?$", $formElement[FE_DECIMAL_FORMAT])
&& $formElement[FE_DECIMAL_FORMAT] != '') {
$decimalFormat = explode(',', $formElement[FE_DECIMAL_FORMAT]);
if ($decimalFormat[0] === '')
$decimalFormat[0] = 10; // default size
if (count($decimalFormat) == 1)
$decimalFormat[1] = 2; // default precision
} else {
throw new UserFormException("Invalid decimalFormat.", ERROR_INVALID_DECIMAL_FORMAT);
}
} else {
// Get decimal format from column definition
$decimalFormat = AbstractBuildForm::getDecimalInfoFromTable($formElement[FE_NAME]);
}
// Check only if there is something.
if ($val !== '') {
$val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN],
$decimalFormat, SANITIZE_EXCEPTION);
$formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION);
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
// $val = htmlspecialchars($val, ENT_QUOTES);
$val = Support::htmlEntityEncodeDecode(MODE_ENCODE, $val);
......@@ -268,7 +250,7 @@ class FillStoreForm {
}
if ($val !== '')
$val = Sanitize::checkMinMax($val, $formElement, SANITIZE_EXCEPTION);
$val = Sanitize::checkMinMax($val, $formElement[FE_MIN], $formElement[FE_MAX], SANITIZE_EXCEPTION);
$newValues[$formElement[FE_NAME]] = $val;
}
......
......@@ -478,7 +478,7 @@ class Store {
$sanitizeClass = SANITIZE_ALLOW_ALL;
}
return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass, '', null, SANITIZE_EMPTY_STRING);
return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass, '', '', SANITIZE_EMPTY_STRING);
} else {
if ($store == STORE_SIP && (substr($key, 0, $len) == SIP_PREFIX_BASE64)) {
$rawVal = base64_decode($rawVal);
......
......@@ -94,19 +94,19 @@ class SanitizeTest extends \PHPUnit_Framework_TestCase {
# Check numerical min/max
$val = 56;
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "0", FE_MAX => "2" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "0" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [FE_MAX => "56"]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "0", "2"), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "0", ""), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "", "56"), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [FE_MIN => "57"]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MAX => "2" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "0", FE_MAX => "200" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "-100", FE_MAX => "200" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "57", ""), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "", "2" ), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "0", "200"), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "-100", "200"), $msg);
$val = -56;
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "0", FE_MAX => "2" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "0", FE_MAX => "200" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "-100", FE_MAX => "200" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "0", "2"), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "0", "200"), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "-100", "200"), $msg);
# Check min/max dates
$msg = "SANITIZE_MIN_MAX Date fails";
......
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