Commit 7658d95a authored by Carsten  Rose's avatar Carsten Rose

Refs #9517. First server side implementation of TypeAhead Tag. OK: Load & Save...

Refs #9517. First server side implementation of TypeAhead Tag. OK: Load & Save tag list as a string. Broken: update after save.
parent 233d74b2
Pipeline #3277 passed with stages
in 3 minutes and 42 seconds
...@@ -1318,8 +1318,23 @@ abstract class AbstractBuildForm { ...@@ -1318,8 +1318,23 @@ abstract class AbstractBuildForm {
$class = 'form-control'; $class = 'form-control';
$elementCharacterCount = ''; $elementCharacterCount = '';
$typeAheadUrlParam = $this->typeAheadBuildParam($formElement); if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') {
if ($typeAheadUrlParam != '') { // crop string only if it's not empty (substr returns false on empty strings)
$value = mb_substr($value, 0, $formElement[FE_MAX_LENGTH]);
}
if ($formElement[FE_HIDE_ZERO] != '0' && $value == '0') {
$value = '';
}
if ($formElement[FE_DECIMAL_FORMAT] !== '') {
if ($value !== '') { // empty string causes exception in number_format()
$decimalScale = explode(',', $formElement[FE_DECIMAL_FORMAT])[1]; // scale: Nachkommastellen
$value = number_format($value, $decimalScale, '.', '');
}
}
if ('' != ($typeAheadUrlParam = $this->typeAheadBuildParam($formElement))) {
if (empty($formElement[FE_INPUT_TYPE])) { if (empty($formElement[FE_INPUT_TYPE])) {
$formElement[FE_INPUT_TYPE] = FE_TYPE_SEARCH; // typeahead behaves better with 'search' instead of 'text' $formElement[FE_INPUT_TYPE] = FE_TYPE_SEARCH; // typeahead behaves better with 'search' instead of 'text'
...@@ -1334,9 +1349,24 @@ abstract class AbstractBuildForm { ...@@ -1334,9 +1349,24 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip);
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_LIMIT, $formElement[FE_TYPEAHEAD_LIMIT]); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_LIMIT, $formElement[FE_TYPEAHEAD_LIMIT]);
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_MINLENGTH, $formElement[FE_TYPEAHEAD_MINLENGTH]); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_MINLENGTH, $formElement[FE_TYPEAHEAD_MINLENGTH]);
if (isset($formElement[FE_TYPEAHEAD_PEDANTIC]) && $formElement[FE_TYPEAHEAD_PEDANTIC] === '1') { if (HelperFormElement::booleParameter($formElement[FE_TYPEAHEAD_PEDANTIC] ?? '-')) {
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_PEDANTIC, 'true'); $attribute .= Support::doAttribute(DATA_TYPEAHEAD_PEDANTIC, 'true');
} }
// Tag
if (HelperFormElement::booleParameter($formElement[FE_TYPEAHEAD_TAG] ?? '-')) {
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_TAG, 'true');
$kk = '[' . ($formElement[FE_TYPEAHEAD_TAG_DELIMITER] ?? '9,44') . ']';
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_TAG_DELIMITER, $kk);
// TAG handling expects the '$value' as a JSON string.
$kk = KeyValueStringParser::parse($value, ':', ',', KVP_IF_VALUE_EMPTY_COPY_KEY);
$jj = '';
foreach ($kk as $arrKey => $arrValue) {
$jj .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]);
}
$value = '[' . substr($jj, 1) . ']';
}
} }
if (isset($formElement[FE_CHARACTER_COUNT_WRAP])) { if (isset($formElement[FE_CHARACTER_COUNT_WRAP])) {
...@@ -1364,26 +1394,11 @@ abstract class AbstractBuildForm { ...@@ -1364,26 +1394,11 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute('data-match', '[name=' . str_replace(':', '\\:', $htmlFormElementNamePrimary) . ']'); $attribute .= Support::doAttribute('data-match', '[name=' . str_replace(':', '\\:', $htmlFormElementNamePrimary) . ']');
} }
if ($formElement[FE_MAX_LENGTH] > 0 && $value !== '') {
// crop string only if it's not empty (substr returns false on empty strings)
$value = mb_substr($value, 0, $formElement[FE_MAX_LENGTH]);
}
// 'maxLength' needs an upper 'L': naming convention for DB tables! // 'maxLength' needs an upper 'L': naming convention for DB tables!
if ($formElement[FE_MAX_LENGTH] > 0) { if ($formElement[FE_MAX_LENGTH] > 0) {
$attribute .= Support::doAttribute('maxlength', $formElement[FE_MAX_LENGTH], false); $attribute .= Support::doAttribute('maxlength', $formElement[FE_MAX_LENGTH], false);
} }
if ($formElement[FE_HIDE_ZERO] != '0' && $value == '0') {
$value = '';
}
if ($formElement[FE_DECIMAL_FORMAT] !== '') {
if ($value !== '') { // empty string causes exception in number_format()
$decimalScale = explode(',', $formElement[FE_DECIMAL_FORMAT])[1]; // scale: Nachkommastellen
$value = number_format($value, $decimalScale, '.', '');
}
}
// In case the user specifies MIN & MAX with numbers, the html tag 'type' has to be 'number', to make the range check work in the browser. // In case the user specifies MIN & MAX with numbers, the html tag 'type' has to be 'number', to make the range check work in the browser.
if (empty($formElement[FE_INPUT_TYPE]) && !empty($formElement[FE_MIN]) && !empty($formElement[FE_MAX]) && if (empty($formElement[FE_INPUT_TYPE]) && !empty($formElement[FE_MIN]) && !empty($formElement[FE_MAX]) &&
is_numeric($formElement[FE_MIN]) && is_numeric($formElement[FE_MAX]) is_numeric($formElement[FE_MIN]) && is_numeric($formElement[FE_MAX])
...@@ -1443,7 +1458,6 @@ abstract class AbstractBuildForm { ...@@ -1443,7 +1458,6 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute(F_FE_DATA_PATTERN_ERROR, $formElement[F_FE_DATA_PATTERN_ERROR], true, ESCAPE_WITH_BACKSLASH); $attribute .= Support::doAttribute(F_FE_DATA_PATTERN_ERROR, $formElement[F_FE_DATA_PATTERN_ERROR], true, ESCAPE_WITH_BACKSLASH);
} }
if ($formElement[FE_MODE] == FE_MODE_REQUIRED) { if ($formElement[FE_MODE] == FE_MODE_REQUIRED) {
$attribute .= Support::doAttribute(F_FE_DATA_REQUIRED_ERROR, $formElement[F_FE_DATA_REQUIRED_ERROR]); $attribute .= Support::doAttribute(F_FE_DATA_REQUIRED_ERROR, $formElement[F_FE_DATA_REQUIRED_ERROR]);
} }
...@@ -1503,11 +1517,10 @@ abstract class AbstractBuildForm { ...@@ -1503,11 +1517,10 @@ abstract class AbstractBuildForm {
if (isset($formElement[FE_TYPEAHEAD_SQL])) { if (isset($formElement[FE_TYPEAHEAD_SQL])) {
$sql = $this->checkSqlAppendLimit($formElement[FE_TYPEAHEAD_SQL], $formElement[FE_TYPEAHEAD_LIMIT]); $sql = $this->checkSqlAppendLimit($formElement[FE_TYPEAHEAD_SQL], $formElement[FE_TYPEAHEAD_LIMIT]);
$formElement[FE_TYPEAHEAD_SQL_PREFETCH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_SQL_PREFETCH);
$arr = [ $arr = [
FE_TYPEAHEAD_SQL => $sql, FE_TYPEAHEAD_SQL => $sql,
FE_TYPEAHEAD_SQL_PREFETCH => $formElement[FE_TYPEAHEAD_SQL_PREFETCH] FE_TYPEAHEAD_SQL_PREFETCH => $formElement[FE_TYPEAHEAD_SQL_PREFETCH] ?? ''
]; ];
} elseif (isset($formElement[FE_TYPEAHEAD_LDAP])) { } elseif (isset($formElement[FE_TYPEAHEAD_LDAP])) {
$formElement[FE_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_LDAP_SERVER); $formElement[FE_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_LDAP_SERVER);
...@@ -1566,6 +1579,14 @@ abstract class AbstractBuildForm { ...@@ -1566,6 +1579,14 @@ abstract class AbstractBuildForm {
$sqlTest = ''; $sqlTest = '';
$sql = trim($sql); $sql = trim($sql);
// If exist, unwrap '{{!', '}}'
if (substr($sql, 0, 2) == '{{') {
$sql = trim(substr($sql, 2, strlen($sql) - 4));
if ($sql[0] ?? '' == '!') {
$sql = trim(substr($sql, 1));
}
}
if ($sql[0] == '[') { if ($sql[0] == '[') {
// Remove optional existing dbIndex token. // Remove optional existing dbIndex token.
......
...@@ -858,6 +858,8 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button'; ...@@ -858,6 +858,8 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button';
const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit'; const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit';
const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength'; const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength';
const DATA_TYPEAHEAD_PEDANTIC = 'data-typeahead-pedantic'; const DATA_TYPEAHEAD_PEDANTIC = 'data-typeahead-pedantic';
const DATA_TYPEAHEAD_TAG = 'data-typeahead-tags';
const DATA_TYPEAHEAD_TAG_DELIMITER = 'data-typeahead-tag-delimiters';
const CLASS_CHARACTER_COUNT = 'qfq-character-count'; const CLASS_CHARACTER_COUNT = 'qfq-character-count';
const DATA_CHARACTER_COUNT_ID = 'data-character-count-id'; const DATA_CHARACTER_COUNT_ID = 'data-character-count-id';
...@@ -1237,6 +1239,8 @@ const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS; ...@@ -1237,6 +1239,8 @@ const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS;
const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT; const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT;
const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH; const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH;
const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC; const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC;
const FE_TYPEAHEAD_TAG = 'typeAheadTag';
const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter';
const FE_TYPEAHEAD_SQL = 'typeAheadSql'; const FE_TYPEAHEAD_SQL = 'typeAheadSql';
const FE_TYPEAHEAD_SQL_PREFETCH = 'typeAheadSqlPrefetch'; const FE_TYPEAHEAD_SQL_PREFETCH = 'typeAheadSqlPrefetch';
const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = F_TYPEAHEAD_LDAP_VALUE_PRINTF; const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = F_TYPEAHEAD_LDAP_VALUE_PRINTF;
......
...@@ -11,6 +11,7 @@ namespace IMATHUZH\Qfq\Core\Store; ...@@ -11,6 +11,7 @@ namespace IMATHUZH\Qfq\Core\Store;
use IMATHUZH\Qfq\Core\Database\Database; use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Evaluate; use IMATHUZH\Qfq\Core\Evaluate;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement; use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Logger; use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\Sanitize; use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\Helper\Support;
...@@ -292,45 +293,21 @@ class FillStoreForm { ...@@ -292,45 +293,21 @@ class FillStoreForm {
$formElement[FE_MODE] === FE_MODE_SHOW || $formElement[FE_MODE] === FE_MODE_SHOW ||
(isset($formElement[FE_PROCESS_READ_ONLY]) && $formElement[FE_PROCESS_READ_ONLY] != '0')) { (isset($formElement[FE_PROCESS_READ_ONLY]) && $formElement[FE_PROCESS_READ_ONLY] != '0')) {
$val = $clientValues[$clientFieldName]; if (HelperFormElement::booleParameter($formElement[FE_TYPEAHEAD_TAG] ?? '-')) {
// TYPEAHEAD_TAG will be delivered as JSON. Check and sanitize every key/value pair.
$arr = json_decode($clientValues[$clientFieldName], true);
$arrTmp = array();
foreach ($arr as $row) {
// Trim input $arrKey = $this->doValue($formElement, $formMode, $row['key']);
if (empty($formElement[FE_TRIM])) { $arrValue = $this->doValue($formElement, $formMode, $row['value']);
$val = trim($val); $arrTmp[$arrKey] = $arrValue;
} elseif ($formElement[FE_TRIM] !== FE_TRIM_NONE) { }
$val = trim($val, $formElement[FE_TRIM]); $val = KeyValueStringParser::unparse($arrTmp);
}
switch ($formElement[FE_TYPE]) {
case FE_TYPE_DATE:
case FE_TYPE_DATETIME:
case FE_TYPE_TIME:
if ($clientValues[$clientFieldName] !== '') { // do not check empty values
$val = $this->doDateTime($formElement, $val);
}
break;
default:
if ($formElement[FE_TYPE] == FE_TYPE_EDITOR) {
// Tiny MCE always wrap a '<p>' around the content. Remove it before saving.
$val = Support::unWrapTag('<p>', $val);
}
// Check only if there is something.
if ($val !== '' && $formMode != FORM_UPDATE && $formElement[FE_MODE] != FE_MODE_HIDDEN) {
$val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN],
$formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION, $formElement[F_FE_DATA_PATTERN_ERROR] ?? '');
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
// $val = htmlspecialchars($val, ENT_QUOTES);
$val = Support::htmlEntityEncodeDecode(MODE_ENCODE, $val);
}
}
break;
}
if ($val !== '') { } else {
$val = Sanitize::checkMinMax($val, $formElement[FE_MIN], $formElement[FE_MAX], SANITIZE_EXCEPTION); // Single Value
$val = $this->doValue($formElement, $formMode, $clientValues[$clientFieldName]);
} }
$newValues[$formElement[FE_NAME]] = $val; $newValues[$formElement[FE_NAME]] = $val;
...@@ -342,6 +319,56 @@ class FillStoreForm { ...@@ -342,6 +319,56 @@ class FillStoreForm {
} }
/**
* @param $formElement
* @param $value
* @return string
* @throws \CodeException
* @throws \UserFormException
*/
private function doValue($formElement, $formMode, $value) {
// Trim input
if (empty($formElement[FE_TRIM])) {
$value = trim($value);
} elseif ($formElement[FE_TRIM] !== FE_TRIM_NONE) {
$value = trim($value, $formElement[FE_TRIM]);
}
switch ($formElement[FE_TYPE]) {
case FE_TYPE_DATE:
case FE_TYPE_DATETIME:
case FE_TYPE_TIME:
if ($value !== '') { // do not check empty values
$value = $this->doDateTime($formElement, $value);
}
break;
default:
if ($formElement[FE_TYPE] == FE_TYPE_EDITOR) {
// Tiny MCE always wrap a '<p>' around the content. Remove it before saving.
$value = Support::unWrapTag('<p>', $value);
}
// Check only if there is something.
if ($value !== '' && $formMode != FORM_UPDATE && $formElement[FE_MODE] != FE_MODE_HIDDEN) {
$value = Sanitize::sanitize($value, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN],
$formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION, $formElement[F_FE_DATA_PATTERN_ERROR] ?? '');
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
// $value = htmlspecialchars($value, ENT_QUOTES);
$value = Support::htmlEntityEncodeDecode(MODE_ENCODE, $value);
}
}
break;
}
if ($value !== '') {
$value = Sanitize::checkMinMax($value, $formElement[FE_MIN], $formElement[FE_MAX], SANITIZE_EXCEPTION);
}
return $value;
}
/** /**
* Steps through all $clientValues (POST vars) and collect all with the name _?_${clientFieldName} in a comma * Steps through all $clientValues (POST vars) and collect all with the name _?_${clientFieldName} in a comma
* separated string (MYSQL ENUM type). If there is no element '_h_${clientFieldName}', than there are no multi * separated string (MYSQL ENUM type). If there is no element '_h_${clientFieldName}', than there are no multi
...@@ -369,7 +396,6 @@ class FillStoreForm { ...@@ -369,7 +396,6 @@ class FillStoreForm {
return $unchecked; return $unchecked;
// For templateGroups: all expanded FormElements will be tried to collect - this fails for not submitted fields. // For templateGroups: all expanded FormElements will be tried to collect - this fails for not submitted fields.
// Therefore skip not existing clientvalues. // Therefore skip not existing clientvalues.
if (!isset($clientValues[$checkboxKey])) { if (!isset($clientValues[$checkboxKey])) {
......
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