Commit c4f24888 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge remote-tracking branch 'origin/4542-input-type-decimal' into...

Merge remote-tracking branch 'origin/4542-input-type-decimal' into Feature_4437_sanatize_specific_message
parents ae02d384 3d41da81
......@@ -2448,6 +2448,9 @@ See also at specific *FormElement* definitions.
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| dateFormat | string | yyyy-mm-dd | dd.mm.yyyy |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| decimalFormat | string | [precision,scale] Limits and formats input to a decimal number with the specified precision and scale. |
| | | If no precision and scale are specified, the decimal format is pulled from the table definition. |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| showSeconds | string | 0|1 - Shows the seconds on form load. Default: 0 |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| showZero | string | 0|1 - Empty timestamp: '0'(default) - nothing shown, '1' - the string '0000-00-00 00:00:00' is displayed |
......
......@@ -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();
......@@ -1112,6 +1110,11 @@ abstract class AbstractBuildForm {
$value = '';
}
if ($formElement[FE_DECIMAL_FORMAT] !== '') {
$decimalScale = explode(',', $formElement[FE_DECIMAL_FORMAT])[1]; // scale: Nachkommastellen
$value = number_format($value, $decimalScale, '.', '');
}
$formElement = HelperFormElement::prepareExtraButton($formElement, !$flagTextarea);
if ($flagTextarea) {
$htmlTag = '<textarea';
......@@ -1129,7 +1132,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 +1380,6 @@ abstract class AbstractBuildForm {
return $attribute;
}
/**
* Construct HTML Input attribute for Client Validation:
*
* type data result
* ------- -----------------------
* ------------------------------------------------------------------------------- min|max <min value>|<max
* value> min="$attrData[0]"|max="$attrData[1]" 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 'min/max' and '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].
*
......@@ -3147,7 +3112,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);
......
......@@ -69,8 +69,6 @@ const SANITIZE_ALLOW_ALNUMX = "alnumx";
const SANITIZE_ALLOW_DIGIT = "digit";
const SANITIZE_ALLOW_NUMERICAL = "numerical";
const SANITIZE_ALLOW_EMAIL = "email";
const SANITIZE_ALLOW_MIN_MAX = "min|max";
const SANITIZE_ALLOW_MIN_MAX_DATE = "min|max date";
const SANITIZE_ALLOW_PATTERN = "pattern";
const SANITIZE_ALLOW_ALLBUT = "allbut";
const SANITIZE_ALLOW_ALL = "all";
......@@ -188,6 +186,7 @@ const ERROR_MISSING_FILE_NAME = 1081;
const ERROR_MAX_FILE_SIZE_TOO_BIG = 1082;
const ERROR_SMALLER_THAN_MIN = 1083;
const ERROR_LARGER_THAN_MAX = 1084;
const ERROR_INVALID_DECIMAL_FORMAT = 1085;
// Subrecord
const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
......@@ -858,6 +857,7 @@ const FE_PLACEHOLDER = 'placeholder';
// FormElement columns: via parameter field
const FE_DATE_FORMAT = 'dateFormat'; // value: FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
const FE_DECIMAL_FORMAT = 'decimalFormat'; // value: 10,2
const FE_SHOW_SECONDS = 'showSeconds'; // value: 0|1
const FE_SHOW_ZERO = 'showZero'; // 0|1 - Used for 'date/datime/time': in case of fe.value='0' shows corresponding '00-00-0000'|'00:00:00'
const FE_HIDE_ZERO = 'hideZero'; // 0|1 - In case of fe.value=0|'0', an empty string is shown.
......
......@@ -34,16 +34,47 @@ class Sanitize {
* @param string $value value to check
* @param string $sanitizeClass
* @param string $pattern Pattern as regexp
* @param array $decimalFormat with [ size, precision ]
* @param string $mode SANITIZE_EXCEPTION | SANITIZE_EMPTY_STRING
*
* @return string
* @throws UserFormException
* @throws \qfq\CodeException
*/
public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $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:
......@@ -52,29 +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);
}
// Pattern check
if ($pattern === '' || preg_match("/$pattern/", $value) === 1) {
return $value;
// decimalFormat
if ($decimalFormat != '' && $checkType !== SANITIZE_ALLOW_DIGIT) {
// overwrite pattern with decimalFormat pattern
$decimalFormatArray = explode(',', $decimalFormat);
$pattern = "^-?[0-9]{0," . ($decimalFormatArray[0] - $decimalFormatArray[1]) . "}(\.[0-9]{0,$decimalFormatArray[1]})?$";
}
// 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;
return $pattern;
}
/**
......@@ -89,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 = '';
......@@ -132,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,32 @@ 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
$isValidDecimalFormat = preg_match("/^[0-9]+,[0-9]+$/", $formElement[FE_DECIMAL_FORMAT]);
if ($isValidDecimalFormat) {
$decimalFormatArray = explode(',', $formElement[FE_DECIMAL_FORMAT]);
$isValidDecimalFormat = $decimalFormatArray[0] >= $decimalFormatArray[1];
}
if (!$isValidDecimalFormat)
throw new UserFormException("Invalid decimalFormat.", ERROR_INVALID_DECIMAL_FORMAT);
}
}
self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);
return $formElement;
}
......
......@@ -239,7 +239,8 @@ class FillStoreForm {
// Check only if there is something.
if ($val !== '') {
$val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], SANITIZE_EXCEPTION);
$val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN],
$formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION);
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
// $val = htmlspecialchars($val, ENT_QUOTES);
$val = Support::htmlEntityEncodeDecode(MODE_ENCODE, $val);
......@@ -249,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, '', 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);
......
......@@ -75,6 +75,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
$this->templateFormNFormElement($form, $formElement);
$build = new \qfq\BuildFormPlain($form, array(), [$formElement], $this->dbArray);
$formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALL;
$label['123-l'][API_ELEMENT_CONTENT] = '<label for="name:1" class="control-label" >Name</label>';
$label['123'][API_ELEMENT_ATTRIBUTE] = ['value' => ''];
......@@ -125,11 +126,20 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
$this->assertEquals('<input id="123" name="name:1" class="form-control" maxlength="255" type="input" value="" pattern="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" data-hidden="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result);
$this->assertEquals(['disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json);
$formElement[FE_CHECK_TYPE] = '';
$formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALL;
$formElement[FE_CHECK_PATTERN] = '';
// Explizit size
// Decimal format
$formElement[FE_DECIMAL_FORMAT] = '5,2';
$result = $build->buildInput($formElement, 'name:1', '', $json);
$this->assertEquals('<input id="123" name="name:1" class="form-control" maxlength="255" type="input" value="" pattern="^-?[0-9]{0,3}(\.[0-9]{0,2})?$" data-hidden="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result);
$this->assertEquals(['disabled' => false, FE_MODE_REQUIRED => '', 'form-element' => 'name:1', 'value' => '', 'disabled' => false, API_ELEMENT_UPDATE => $label], $json);
$formElement[FE_DECIMAL_FORMAT] = '';
// Explicit size
$formElement['size'] = 40;
$formElement['maxLength'] = 40;
$result = $build->buildInput($formElement, 'name:1', '', $json);
......
......@@ -83,6 +83,26 @@ class SanitizeTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL), "SANITIZE_ALL fails");
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALLBUT), "SANITIZE_ALLBUT fails");
# Check Decimal Format
$msg = "Sanitize: Decimal Format check fails";
$val = '123.45';
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '10,3'), $msg);
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '4,2'), $msg);
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,1'), $msg);
$val = '-123.45';
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '10,3'), $msg);
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '4,2'), $msg);
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,1'), $msg);
$val = 'a.00';
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
$val = '-0.1e9';
$this->assertEquals('!!all!!', Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
$val = '-4';
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
$val = '.42';
$this->assertEquals($val, Sanitize::sanitize($val, SANITIZE_ALLOW_ALL, '', '5,2'), $msg);
}
/**
......@@ -94,28 +114,28 @@ 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";
$val = "2010-05-01";
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "2010-01-01", FE_MAX => "2010-12-31" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "2010-01-01", FE_MAX => "2010-04-30" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "2010-01-01", FE_MAX => "2009-12-31" ]), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, [ FE_MIN => "2011-01-01", FE_MAX => "2009-12-31" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, [ FE_MIN => "2010-05-01", FE_MAX => "2010-05-01" ]), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "2010-01-01", "2010-12-31"), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "2010-01-01", "2010-04-30"), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "2010-01-01", "2009-12-31"), $msg);
$this->assertEquals('', Sanitize::checkMinMax($val, "2011-01-01", "2009-12-31"), $msg);
$this->assertEquals($val, Sanitize::checkMinMax($val, "2010-05-01", "2010-05-01"), $msg);
}
/**
......@@ -212,7 +232,7 @@ class SanitizeTest extends \PHPUnit_Framework_TestCase {
* @expectedException \qfq\UserFormException
*/
public function testSanitizeExceptionCheckFailed() {
Sanitize::sanitize('string', SANITIZE_ALLOW_DIGIT, '', SANITIZE_EXCEPTION);
Sanitize::sanitize('string', SANITIZE_ALLOW_DIGIT, '', '', SANITIZE_EXCEPTION);
}
/**
......
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