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 {
$class = 'form-control';
$elementCharacterCount = '';
$typeAheadUrlParam = $this->typeAheadBuildParam($formElement);
if ($typeAheadUrlParam != '') {
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]);
}
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])) {
$formElement[FE_INPUT_TYPE] = FE_TYPE_SEARCH; // typeahead behaves better with 'search' instead of 'text'
......@@ -1334,9 +1349,24 @@ abstract class AbstractBuildForm {
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip);
$attribute .= Support::doAttribute(DATA_TYPEAHEAD_LIMIT, $formElement[FE_TYPEAHEAD_LIMIT]);
$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');
}
// 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])) {
......@@ -1364,26 +1394,11 @@ abstract class AbstractBuildForm {
$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!
if ($formElement[FE_MAX_LENGTH] > 0) {
$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.
if (empty($formElement[FE_INPUT_TYPE]) && !empty($formElement[FE_MIN]) && !empty($formElement[FE_MAX]) &&
is_numeric($formElement[FE_MIN]) && is_numeric($formElement[FE_MAX])
......@@ -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);
}
if ($formElement[FE_MODE] == FE_MODE_REQUIRED) {
$attribute .= Support::doAttribute(F_FE_DATA_REQUIRED_ERROR, $formElement[F_FE_DATA_REQUIRED_ERROR]);
}
......@@ -1503,11 +1517,10 @@ abstract class AbstractBuildForm {
if (isset($formElement[FE_TYPEAHEAD_SQL])) {
$sql = $this->checkSqlAppendLimit($formElement[FE_TYPEAHEAD_SQL], $formElement[FE_TYPEAHEAD_LIMIT]);
$formElement[FE_TYPEAHEAD_SQL_PREFETCH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_SQL_PREFETCH);
$arr = [
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])) {
$formElement[FE_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_LDAP_SERVER);
......@@ -1566,6 +1579,14 @@ abstract class AbstractBuildForm {
$sqlTest = '';
$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] == '[') {
// Remove optional existing dbIndex token.
......
......@@ -858,6 +858,8 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button';
const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit';
const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength';
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 DATA_CHARACTER_COUNT_ID = 'data-character-count-id';
......@@ -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_MINLENGTH = F_TYPEAHEAD_MINLENGTH;
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_PREFETCH = 'typeAheadSqlPrefetch';
const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = F_TYPEAHEAD_LDAP_VALUE_PRINTF;
......
......@@ -11,6 +11,7 @@ namespace IMATHUZH\Qfq\Core\Store;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Evaluate;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
......@@ -292,45 +293,21 @@ class FillStoreForm {
$formElement[FE_MODE] === FE_MODE_SHOW ||
(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
if (empty($formElement[FE_TRIM])) {
$val = trim($val);
} elseif ($formElement[FE_TRIM] !== FE_TRIM_NONE) {
$val = trim($val, $formElement[FE_TRIM]);
}
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;
}
$arrKey = $this->doValue($formElement, $formMode, $row['key']);
$arrValue = $this->doValue($formElement, $formMode, $row['value']);
$arrTmp[$arrKey] = $arrValue;
}
$val = KeyValueStringParser::unparse($arrTmp);
if ($val !== '') {
$val = Sanitize::checkMinMax($val, $formElement[FE_MIN], $formElement[FE_MAX], SANITIZE_EXCEPTION);
} else {
// Single Value
$val = $this->doValue($formElement, $formMode, $clientValues[$clientFieldName]);
}
$newValues[$formElement[FE_NAME]] = $val;
......@@ -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
* separated string (MYSQL ENUM type). If there is no element '_h_${clientFieldName}', than there are no multi
......@@ -369,7 +396,6 @@ class FillStoreForm {
return $unchecked;
// For templateGroups: all expanded FormElements will be tried to collect - this fails for not submitted fields.
// Therefore skip not existing clientvalues.
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