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

Merge branch 'Feature_4437_sanatize_specific_message'

parents 5f71bbde e48fa888
......@@ -4472,52 +4472,59 @@ HTML output:
.. _`syntax-of-report`:
Syntax of `report`
Syntax of *report*
------------------
All **root level queries** will be fired in the order specified by 'level' (Integer value).
All **root level queries** will be fired in the order specified by 'level' (Integer value).
For **each** row of a query (this means *all* queries), all subqueries will be fired once.
For **each** row of a query (this means *all* queries), all subqueries will be fired once.
* E.g. if the outer query selects 5 rows, and a nested query select 3 rows, than the total number of rows are 5 x 3 = 15 rows.
* E.g. if the outer query selects 5 rows, and a nested query select 3 rows, than the total number of rows are 5 x 3 = 15 rows.
There is a set of **variables** that will get replaced before the SQL-Query gets executed:
There is a set of **variables** that will get replaced before the SQL-Query gets executed:
Variables from specific stores: {{<name>[:<store/s>[:...]]}}
``{{<name>[:<store/s>[:...]]}}``
Variables from specific stores.
STORE_RECORD is automatically merged with the existing STORE_RECORD content and the current row. Use the STORE_RECORD
to access outer level values.
``{{<name>:R}}`` - use case of the above generic definition.
STORE_RECORD is automatically merged with a) the existing STORE_RECORD content and b) the *current row*. Use the STORE_RECORD
to access outer and/or previous level values.
In case of previous level values: only the one of the last record is available.
Always access variables without an optional leading '_' - it's removed before the variable is copied to STORE_RECORD.
Column values of a given row: {{<level>.<columnname>}}
``{{<level>.<columnname>}}``
Similar to ``{{<name>:R}}`` but more specific. There is no sanitize class, escape mode or default value.
Global variables: {{global.<name>}}
``{{<level>.line.count}}`` - Current row index
This variable is specific, as it will be replaced before the query is fired in case of ``<level>`` is an outer/previous
level or it will be replaced after a query is fired in case ``<level>`` is the current level.
Current row index: {{<level>.line.count}}
``{{<level>.line.total}}``
Total rows (MySQL ``num_rows`` for *SELECT* and *SHOW*, MySQL ``affected_rows`` for *UPDATE* and *INSERT*.
Total rows (num_rows for SELECT and SHOW, affected_rows for UPDATE and INSERT): {{<level>.line.total}}
``{{<level>.line.insertId}}``
Last insert id for *INSERT*.
Last insert id for INSERT: {{<level>.line.insertId}}
See :ref:`variables` for a full list of all available variables.
See :ref:`variables` for a full list of all available variables.
Be aware that line.count / line.total have to be known before the query is fired. E.g. `10.sql = SELECT {{10.line.count}}, ... WHERE {{10.line.count}} = ...`
won't work as expected. `{{10.line.count}}` can't be replaced before the query is fired, but will be replaced during processing the result!
Different types of SQL queries are possible: SELECT, INSERT, UPDATE, DELETE, SHOW, REPLACE
Different types of SQL queries are possible: SELECT, INSERT, UPDATE, DELETE, SHOW
Only SELECT and SHOW queries will fire subqueries.
Only SELECT and SHOW queries will fire subqueries.
Processing of the resulting rows and columns:
Processing of the resulting rows and columns:
* In general, all columns of all rows will be printed out sequentially.
* On a per column base, printing of columns can be suppressed by starting the columnname with an underscore '_'. E.g.
`SELECT id AS _id`.
* In general, all columns of all rows will be printed out sequentially.
This might be useful to store values, which will be used later on in another query via the `{{id:R}}` or
`{{level.columnname}}` variable. To suppress printing of a column, use a underscore as column name prefix. E.g.
`SELECT id AS _id`
* On a per column base, printing of columns can be suppressed by starting the columnname with an underscore '_'. E.g. `SELECT id AS _id`.
This might be useful to store values, which will be used later on in another query via the `{{id:R}}` or `{{level.columnname}}` variable. To suppress printing of a column, use a
underscore as column name prefix. E.g. `SELECT id AS _id`
*Reserved column names* have a special meaning and will be processed in a special way. See `Processing of columns in the SQL result`_ for details.
Reserved column names have a special meaning and will be processed in a special way. See `Processing of columns in the SQL result`_ for details.
There are extensive ways to wrap columns and rows automatically. See :ref:`wrapping-rows-and-columns`
There are extensive ways to wrap columns and rows. See :ref:`wrapping-rows-and-columns`
Debug the bodytext
------------------
......@@ -5932,7 +5939,7 @@ QFQ CSS Classes
* `qfq-left`: Text align left.
Bootstrap
'''''''''
^^^^^^^^^
* Table: `table`
* Table > hover: `table-hover`
......
......@@ -1129,14 +1129,16 @@ abstract class AbstractBuildForm {
}
$attribute .= $this->getAttributeList($formElement, [FE_INPUT_AUTOCOMPLETE, 'autofocus', 'placeholder']);
$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]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], $formElement[FE_DECIMAL_FORMAT]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], $formElement[FE_DECIMAL_FORMAT], $sanitizeMessage);
$attribute .= ($pattern === '') ? '' : 'pattern="' . $pattern . '" ';
if (empty($formElement[F_FE_DATA_PATTERN_ERROR])) {
$formElement[F_FE_DATA_PATTERN_ERROR] = $sanitizeMessage;
};
$attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR, FE_MIN, FE_MAX]);
$attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement[FE_TOOLTIP]);
$attribute .= $this->getAttributeList($formElement, [FE_MIN, FE_MAX]);
$attribute .= $this->getAttributeFeMode($formElement[FE_MODE], false);
......@@ -3113,7 +3115,7 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement[FE_TOOLTIP]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN]);
$pattern = Sanitize::getInputCheckPattern($formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN], '', $rcSanitizeMessage);
$attribute .= ($pattern === '') ? '' : 'pattern="' . $pattern . '" ';
$attribute .= $this->getAttributeList($formElement, [FE_MIN, FE_MAX]);
......
......@@ -22,6 +22,26 @@ require_once(__DIR__ . '/../../qfq/Constants.php');
*/
class Sanitize {
private static $sanitizePattern = [
SANITIZE_ALLOW_ALNUMX => '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿç]*$', // ':alnum:' does not work here in FF
SANITIZE_ALLOW_DIGIT => '^[\d]*$',
SANITIZE_ALLOW_NUMERICAL => '^[\d.+-]*$',
SANITIZE_ALLOW_EMAIL => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
SANITIZE_ALLOW_PATTERN => '',
SANITIZE_ALLOW_ALLBUT => '^[^\[\]{}%&\\\\#]*$',
SANITIZE_ALLOW_ALL => '.*',
];
private static $sanitizeMessage = [
SANITIZE_ALLOW_ALNUMX => 'Allowed characters: 0...9, [latin character], @-_.m;: /()',
SANITIZE_ALLOW_DIGIT => 'Allowed characters: 0...9',
SANITIZE_ALLOW_NUMERICAL => 'Allowed characters: 0...9 and .+-',
SANITIZE_ALLOW_EMAIL => 'Requested format: string@domain',
SANITIZE_ALLOW_PATTERN => 'Please match the requested format',
SANITIZE_ALLOW_ALLBUT => 'Forbidden characters: ^[]{}%&\#',
SANITIZE_ALLOW_ALL => '',
];
private function __construct() {
// Class should never be instantiated
......@@ -42,7 +62,8 @@ class Sanitize {
* @throws \qfq\CodeException
*/
public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '', $mode = SANITIZE_EMPTY_STRING) {
$pattern = self::getInputCheckPattern($sanitizeClass, $pattern, $decimalFormat);
$pattern = self::getInputCheckPattern($sanitizeClass, $pattern, $decimalFormat, $dummy);
// Pattern check
if ($pattern === '' || preg_match("/$pattern/", $value) === 1) {
......@@ -65,10 +86,12 @@ class Sanitize {
* @param string $pattern
* @param string $decimalFormat e.g. "10,2"
*
* @param string $rcSanitizeMessage Message specific to a pattern
* @return string
* @throws CodeException
*/
public static function getInputCheckPattern($checkType = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '') {
public static function getInputCheckPattern($checkType = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '', &$rcSanitizeMessage) {
switch ($checkType) {
case SANITIZE_ALLOW_PATTERN:
return $pattern;
......@@ -82,19 +105,21 @@ class Sanitize {
case SANITIZE_ALLOW_EMAIL:
case SANITIZE_ALLOW_ALNUMX:
case SANITIZE_ALLOW_ALLBUT:
$arr = self::inputCheckPatternArray();
$pattern = $arr[$checkType];
$pattern = self::$sanitizePattern[$checkType];
break;
default:
throw new CodeException("Unknown checkType: " . $checkType, ERROR_UNKNOWN_CHECKTYPE);
}
$rcSanitizeMessage = self::$sanitizeMessage[$checkType];
// 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]})?$";
$rcSanitizeMessage = "Requested decimal format (mantis,decimal): $decimalFormat";
}
return $pattern;
......@@ -136,22 +161,6 @@ class Sanitize {
return '';
}
/**
* @return array
*/
public static function inputCheckPatternArray() {
//EMail Regex: http://www.regular-expressions.info/email.html
return [
SANITIZE_ALLOW_ALNUMX => '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿç]*$', // ':alnum:' does not work here in FF
SANITIZE_ALLOW_DIGIT => '^[\d]*$',
SANITIZE_ALLOW_NUMERICAL => '^[\d.+-]*$',
SANITIZE_ALLOW_EMAIL => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
SANITIZE_ALLOW_PATTERN => '',
SANITIZE_ALLOW_ALLBUT => '^[^\[\]{}%&\\\\#]*$',
SANITIZE_ALLOW_ALL => '.*',
];
}
/**
* Sanitizes a filename. Copied from http://www.phpit.net/code/filename-safe/
*
......
......@@ -117,13 +117,13 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
$formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_DIGIT;
$formElement[FE_CHECK_PATTERN] = '';
$result = $build->buildInput($formElement, 'name:1', '', $json);
$this->assertEquals('<input id="123" name="name:1" class="form-control" maxlength="255" type="input" value="" pattern="^[\d]*$" data-hidden="no" data-required="no" ><div class="help-block with-errors hidden"></div>', $result);
$this->assertEquals('<input id="123" name="name:1" class="form-control" maxlength="255" type="input" value="" pattern="^[\d]*$" data-pattern-error="Allowed characters: 0...9" 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] = SANITIZE_ALLOW_EMAIL;
$formElement[FE_CHECK_PATTERN] = '';
$result = $build->buildInput($formElement, 'name:1', '', $json);
$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('<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-pattern-error="Requested format: string@domain" 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] = SANITIZE_ALLOW_ALL;
......@@ -133,7 +133,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
// 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('<input id="123" name="name:1" class="form-control" maxlength="255" type="input" value="" pattern="^-?[0-9]{0,3}(\.[0-9]{0,2})?$" data-pattern-error="Requested decimal format (mantis,decimal): 5,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] = '';
......
......@@ -53,6 +53,7 @@ var QfqNS = QfqNS || {};
this.lockAcquired = false;
this.formImmutableDueToConcurrentAccess = false;
this.lockRenewalPhase = false;
this.goToAfterSave = false;
this.additionalQueryParameters = {
'recordHashMd5': this.getRecordHashMd5()
......@@ -95,6 +96,12 @@ var QfqNS = QfqNS || {};
this.getNewButton().click(this.handleNewClick.bind(this));
this.getDeleteButton().click(this.handleDeleteClick.bind(this));
var that = this;
$('.external-save').click(function(e) {
var uri = $(this).data('target');
that.callSave(uri);
});
this.setupFormUpdateHandler();
if (!!$('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('disable-return-key-submit')) {
// Nothing to do
......@@ -522,6 +529,12 @@ var QfqNS = QfqNS || {};
}
};
n.QfqForm.prototype.callSave = function(uri) {
console.log("target: " + uri);
this.handleSaveClick();
this.goToAfterSave = uri;
};
/**
* @private
*/
......@@ -824,6 +837,7 @@ var QfqNS = QfqNS || {};
};
/**
* @private
*/
......@@ -954,6 +968,12 @@ var QfqNS = QfqNS || {};
return;
}
if (this.goToAfterSave) {
console.log("Called goToAfterSave = " + this.goToAfterSave);
window.location = this.goToAfterSave;
return;
}
break;
case 'close':
if (!data.redirect || data.redirect === "no") {
......
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