", $htmlElement) . $br;
$html .= $htmlElement;
}
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
return $html;
}
/**
* Builds a Selct (Dropdown) Box.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return mixed
* @throws CodeException
* @throws \qfq\UserFormException
*/
public function buildSelect(array $formElement, $htmlFormElementId, $value, array &$json) {
$itemKey = array();
$itemValue = array();
// Fill $itemKey & $itemValue
$this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue);
$attribute = $this->getAttributeFeMode($formElement[FE_MODE]);
$attribute .= Support::doAttribute('name', $htmlFormElementId);
$attribute .= Support::doAttribute('class', 'form-control');
$attribute .= Support::doAttribute('title', $formElement['tooltip']);
$attribute .= $this->getAttributeList($formElement, ['autofocus']);
$attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
if (isset($formElement['size']) && $formElement['size'] > 1) {
$attribute .= Support::doAttribute('size', $formElement['size']);
$attribute .= Support::doAttribute('multiple', 'multiple');
}
$option = '';
$firstSelect = true;
$jsonValues = array();
for ($ii = 0; $ii < count($itemValue); $ii++) {
$option .= '
';
}
$json = $this->getJsonElementUpdate($htmlFormElementId, $jsonValues, $formElement[FE_MODE]);
return '
' . $this->getHelpBlock();
}
/**
* Constuct a HTML table of the subrecord data.
* Column syntax definition: https://wikiit.math.uzh.ch/it/projekt/qfq/qfq-jqwidgets/Documentation#Type:_subrecord
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return string
* @throws CodeException
* @throws \qfq\UserFormException
*/
public function buildSubrecord(array $formElement, $htmlFormElementId, $value, array &$json) {
$rcText = false;
$nameColumnId = 'id';
$targetTableName = '';
$flagNew = false;
$flagEdit = false;
$flagDelete = false;
$linkNew = '';
$control = array();
$primaryRecord = $this->store->getStore(STORE_RECORD);
if (!$this->prepareSubrecod($formElement, $primaryRecord, $rcText, $nameColumnId)) {
return $rcText;
}
if (isset($formElement[SUBRECORD_PARAMETER_FORM])) {
$linkNew = Support::wrapTag('
', $columns);
foreach ($formElement['sql1'] as $row) {
$rowHtml = '';
if ($flagEdit) {
$rowHtml .= Support::wrapTag('', $this->createFormLink($formElement, $row[$nameColumnId], $primaryRecord, $this->symbol[SYMBOL_EDIT], 'Edit'));
} elseif ($flagNew) {
$rowHtml .= Support::wrapTag(' ', $rowHtml, false);
}
// All columns
foreach ($row as $columnName => $value) {
if (isset($control['title'][$columnName]))
$rowHtml .= Support::wrapTag(' ', $this->renderCell($control, $columnName, $value));
}
if ($flagDelete) {
$s = $this->createDeleteUrl($targetTableName, $row['id'], RETURN_SIP);
$rowHtml .= Support::wrapTag(' ', Support::wrapTag("", ' '));
}
Support::setIfNotSet($row, FE_SUBRECORD_ROW_CLASS);
$rowClass = 'record ';
$rowClass .= $row[FE_SUBRECORD_ROW_CLASS];
Support::setIfNotSet($row, FE_SUBRECORD_ROW_TITLE);
$rowTitle = $row[FE_SUBRECORD_ROW_TITLE];
$rowAttribute = Support::doAttribute('class', $rowClass);
$rowAttribute .= Support::doAttribute('title', $rowTitle);
$html .= Support::wrapTag("", $rowHtml, true);
}
return Support::wrapTag('', $html, true);
}
/**
* Prepare Subrecord:
* - check if the current record has an recordId>0. If not, subrecord can't be edited. Return a message.
* - check if there is an SELECT statement for the subrecords.
*
* @param $formElement
* @param $primaryRecord
* @param $rcText
* @param $nameColumnId
* @return bool
* @throws \qfq\UserFormException
*/
private function prepareSubrecod(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) {
if (!isset($primaryRecord['id'])) {
$rcText = 'Please save record first.';
return false;
}
if (!is_array($formElement['sql1'])) {
throw new UserFormException('Missing \'sql1\' Query', ERROR_MISSING_SQL1);
}
// No records?
if (count($formElement['sql1']) == 0) {
$rcText = '';
return true;
}
if (!isset($formElement['sql1'][0][$nameColumnId]))
$nameColumnId = '_id';
if (!isset($formElement['sql1'][0][$nameColumnId])) {
throw new UserFormException('Missing column \'id\' (or "_id") in \'sql1\' Query', ERROR_DB_MISSING_COLUMN_ID);
}
return true;
}
/**
* Renders an Link with a symbol (edit/new) and register a new SIP to grant permission to the link..
*
* Returns [icon]
*
* Link: ?s=&
* SIP: form = $formElement['form'] (provided via formElement['parameter'])
* r = $targetRecordId
* Parse $formElement['detail'] with possible key/value pairs. E.g.: detail=id:gr_id,#{{a}}:p_id,#12:x_id
* gr_id = <>
* p_id = <>
* x_id = 12 (constant)
*
*
* @param $formElement
* @param $targetRecordId
* @param $record
* @return string
* @throws UserFormException
*/
private function createFormLink(array $formElement, $targetRecordId, array $record, $symbol, $toolTip) {
$queryStringArray = [
SIP_FORM => $formElement[SUBRECORD_PARAMETER_FORM],
SIP_RECORD_ID => $targetRecordId
];
// Add custom query parameter
if (isset($formElement[SUBRECORD_PARAMETER_DETAIL])) {
$detailParam = KeyValueStringParser::parse($formElement[SUBRECORD_PARAMETER_DETAIL]);
foreach ($detailParam as $src => $dest) {
// Constants
if ($src[0] == '&') {
$queryStringArray[$dest] = substr($src, 1);
continue;
}
// Form record values or parameter
if (isset($record[$src])) {
$queryStringArray[$dest] = $record[$src];
}
}
}
if ($this->showDebugInfo)
$toolTip .= PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
Support::appendTypo3ParameterToArray($queryStringArray);
// If there is a specific targetpage defined, take it.
if (isset($formElement[SUBRECORD_PARAMETER_PAGE]) && $formElement[SUBRECORD_PARAMETER_PAGE] !== '') {
$queryStringArray['id'] = $formElement[SUBRECORD_PARAMETER_PAGE];
}
//-----------------
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
$url = $sip->queryStringToSip($queryString);
return Support::wrapTag('', $symbol);
}
/**
* Get the name for the given form $formName. If not found, return ''.
*
* @param $formName
* @return string tableName for $formName
* @throws CodeException
* @throws DbException
*/
private function getFormTable($formName) {
$row = $this->db->sql("SELECT tableName FROM Form AS f WHERE f.name = ?", ROW_EXPECT_0_1, [$formName]);
if (isset($row['tableName'])) {
return $row['tableName'];
}
return '';
}
/**
* Get various column format information based on the 'raw' column title. The attributes are separated by '|' and specified as 'key' or 'key=value'.
*
* - Return all parsed values as an assoc array.
* - For regular columns: If there is no 'width' specified, take the default 'SUBRECORD_COLUMN_WIDTH'
* - For 'icon / url / mailto': no width limit.
*
* Returned assoc array:
* title Only key. Element is non numeric, which is not a keyword 'width/nostrip/icon/url/mailto'
* width Key/Value Pair. Not provided for 'icon/url/mailto'.
* nostrip Only key. Do not strip HTML Tags from the content.
* icon Only key. Value will rendered (later) as an image.
* url Only key. Value will rendered (later) as a 'href'
* mailto Only key. Value will rendered (later) as a 'href mailto'
*
*
* @param $titleRaw
* @return array
* @throws UserFormException
*/
private function getSubrecordColumnControl(array $titleRaw) {
$control = array();
foreach ($titleRaw AS $columnName) {
switch ($columnName) {
case FE_SUBRECORD_ROW_CLASS:
case FE_SUBRECORD_ROW_TITLE:
continue 2;
default:
break;
}
$flagWidthLimit = true;
$control['width'][$columnName] = SUBRECORD_COLUMN_WIDTH;
// a) 'City@width=40', b) 'Status@icon', c) 'Mailto@width=80@nostrip'
$arr = KeyValueStringParser::parse($columnName, '=', '|', KVP_IF_VALUE_EMPTY_COPY_KEY);
foreach ($arr as $attribute => $value) {
switch ($attribute) {
case 'width':
case 'nostrip':
case 'title':
break;
case 'icon':
case 'url':
case 'mailto':
$flagWidthLimit = false;
break;
default:
$attribute = is_numeric($value) ? 'width' : 'title';
break;
}
$control[$attribute][$columnName] = $value;
}
if (!isset($control['title'][$columnName]))
$control['title'][$columnName] = ''; // Fallback: Might be wrong, but better than nothing.
// Limit title length
$control['title'][$columnName] = substr($control['title'][$columnName], 0, $control['width'][$columnName]);
if (!$flagWidthLimit) {
$control['width'][$columnName] = false;
}
}
return $control;
}
/**
* Renders $value as specified in array $control
*
* nostrip: by default, HTML tags are removed. With this attribute, the value will be delivered as it is.
* width: if there is a size limit - apply it.
* icon: The cell will be rendered as an image. $value should contain the name of an image in 'fileadmin/icons/'
* mailto: The cell will be rendered as an tag with the 'mailto' attribute.
* url: The cell will be rendered as an tag. The value will be exploded by '|'. $value[0] = href, value[1] = text.
* E.g. $value = 'www.math.uzh.ch/?id=45&v=234|Show details for Modul 123' >> Show details for Modul 123
*
* @param array $control
* @param $columnName
* @param $value
* @return string
*/
private function renderCell(array $control, $columnName, $value) {
switch ($columnName) {
case FE_SUBRECORD_ROW_CLASS:
case FE_SUBRECORD_ROW_TITLE:
return '';
default:
break;
}
$arr = explode('|', $value);
if (count($arr) == 1)
$arr[1] = $arr[0];
$cell = isset($control['nostrip'][$columnName]) ? $value : strip_tags($value);
if ($control['width'][$columnName] !== false)
$cell = substr($cell, 0, $control['width'][$columnName]);
if (isset($control['icon'][$columnName])) {
$cell = ($cell === '') ? '' : "";
}
if (isset($control['mailto'][$columnName])) {
$cell = "$arr[1] ";
}
if (isset($control['url'][$columnName])) {
$cell = "$arr[1] ";
}
return $cell;
}
/**
* Create a link (incl. SIP) to delete the current record.
*
* @return string String: "API_DIR/delete.php?sip=...."
*/
public function createDeleteUrl($table, $recordId, $mode = RETURN_URL) {
$queryStringArray = [
SIP_TABLE => $table,
SIP_RECORD_ID => $recordId
];
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
return $sip->queryStringToSip($queryString, $mode, API_DIR . '/delete.php');
}
/**
* Builds an Upload (File) Button.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return string
* @throws CodeException
* @throws \qfq\UserFormException
*/
public function buildFile(array $formElement, $htmlFormElementId, $value, array &$json) {
$attribute = '';
$arr = array();
$arr['fake_uniq_never_use_this'] = uniqid(); // make sure we get a new SIP. This is needed for multiple forms (same user) with r=0
$arr[CLIENT_SIP_FOR_FORM] = $this->store->getVar(SIP_SIP, STORE_SIP);
$arr[CLIENT_FE_NAME] = $formElement['name'];
$arr[CLIENT_FORM] = $this->formSpec['name'];
$arr[CLIENT_RECORD_ID] = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);
$arr[CLIENT_PAGE_ID] = 'fake';
$sipUpload = $this->sip->queryStringToSip(OnArray::toString($arr), RETURN_SIP);
$hiddenSipUpload = $this->buildNativeHidden($htmlFormElementId, $sipUpload);
$attribute .= Support::doAttribute('name', $htmlFormElementId);
// $attribute .= Support::doAttribute('class', 'form-control');
$attribute .= Support::doAttribute('type', 'file');
$attribute .= Support::doAttribute('title', $formElement['tooltip']);
$attribute .= $this->getAttributeList($formElement, ['autofocus', 'accept']);
$attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('data-sip', $sipUpload);
if ($value === '') {
$textDeleteClass = 'hidden';
$uploadClass = '';
} else {
$textDeleteClass = '';
$uploadClass = 'hidden';
$formElement[FE_MODE] = FE_MODE_HIDDEN;
}
$attribute .= $this->getAttributeFeMode($formElement[FE_MODE]);
$attribute .= Support::doAttribute('class', $uploadClass, true);
$htmlInputFile = ' ' . $this->getHelpBlock();
$deleteButton = Support::wrapTag("", $this->symbol[SYMBOL_DELETE]);
$htmlFilename = Support::wrapTag("", $value, false);
$htmlTextDelete = Support::wrapTag("", $htmlFilename . ' ' . $deleteButton);
//
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
return $htmlTextDelete . $htmlInputFile . $hiddenSipUpload;
}
/**
* Builds HTML 'input' element.
* Format:
*
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return string
* @throws \qfq\UserFormException
*/
public function builddateJQW(array $formElement, $htmlFormElementId, $value, array &$json) {
$textarea = '';
$attribute = Support::doAttribute('name', $htmlFormElementId);
$htmlTag = '
adjustMaxLength($formElement);
// Get date format
if (!isset($formElement['dateFormat'])) {
$formElement['dateFormat'] = $this->store->getVar(SYSTEM_DATE_FORMAT, STORE_SYSTEM);
}
// Format date
$value = Support::convertDateTime($value, $formElement[FE_DATE_FORMAT], $formElement[FE_SHOW_ZERO], 0, $formElement[FE_SHOW_SECONDS]);
//
if ($formElement['maxLength'] > 0) {
// crop string only if it's not empty (substr returns false on empty strings)
if ($value !== '')
$value = substr($value, 0, $formElement['maxLength']);
// 'maxLength' needs an upper 'L': naming convention for DB tables!
$attribute .= $this->getAttributeList($formElement, ['type', 'size', 'maxLength']);
$attribute .= Support::doAttribute('value', htmlentities($value), false);
}
$attribute .= $this->getAttributeList($formElement, ['autocomplete', 'autofocus', 'placeholder']);
$attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement['tooltip']);
$attribute .= $this->getInputCheckPattern($formElement['checkType'], $formElement['checkPattern']);
$attribute .= $this->getAttributeFeMode($formElement[FE_MODE]);
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
return "$htmlTag $attribute>$textarea";
}
/**
* Builds HTML 'input' element.
* Format:
*
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return string
* @throws UserFormException
*/
public function buildDateTime(array $formElement, $htmlFormElementId, $value, array &$json) {
$attribute = Support::doAttribute('name', $htmlFormElementId);
$attribute .= Support::doAttribute('class', 'form-control');
$arrMinMax = null;
$this->adjustMaxLength($formElement);
$showTime = ($formElement[FE_TYPE] == 'time' || $formElement[FE_TYPE] == 'datetime') ? 1 : 0;
$value = Support::convertDateTime($value, $formElement[FE_DATE_FORMAT], $formElement[FE_SHOW_ZERO], $showTime, $formElement[FE_SHOW_SECONDS]);
$tmpPattern = $formElement['checkPattern'];
$formElement['checkPattern'] = Support::dateTimeRegexp($formElement[FE_TYPE], $formElement['dateFormat']);
switch ($formElement['checkType']) {
case SANITIZE_ALLOW_PATTERN:
$formElement['checkPattern'] = $tmpPattern;
break;
case SANITIZE_ALLOW_MIN_MAX_DATE:
$arrMinMax = explode('|', $formElement['checkPattern'], 2);
if (count($arrMinMax) != 2) {
throw new UserFormException('Missing min|max definition', ERROR_MISSING_MIN_MAX);
}
break;
case SANITIZE_ALLOW_ALL:
case SANITIZE_ALLOW_ALNUMX:
case SANITIZE_ALLOW_ALLBUT:
$formElement['checkType'] = SANITIZE_ALLOW_PATTERN;
break;
default:
throw new UserFormException("Checktype not applicable for date/time: '" . $formElement['checkType'] . "'", ERROR_NOT_APPLICABLE);
}
// truncate if necessary
if ($value != '' && $formElement['maxLength'] > 0) {
$value = substr($value, 0, $formElement['maxLength']);
}
$type = $formElement[FE_TYPE];
if ($type === 'datetime')
$type = 'datetime-local';
$attribute .= Support::doAttribute('type', $type);
// 'maxLength' needs an upper 'L': naming convention for DB tables!
$attribute .= $this->getAttributeList($formElement, ['size', 'maxLength']);
$attribute .= Support::doAttribute('value', htmlentities($value), false);
if ($formElement['placeholder'] == '') {
$timePattern = ($formElement[FE_SHOW_SECONDS] == 1) ? 'hh:mm:ss' : 'hh:mm';
switch ($formElement[FE_TYPE]) {
case 'date':
$placeholder = $formElement['dateFormat'];
break;
case 'datetime':
$placeholder = $formElement['dateFormat'] . ' ' . $timePattern;
break;
case 'time':
$placeholder = $timePattern;
break;
default:
throw new UserFormException("Unexpected Formelement type: '" . $formElement[FE_TYPE] . "'", ERROR_FORMELEMENT_TYPE);
}
$formElement['placeholder'] = $placeholder;
}
$attribute .= $this->getAttributeList($formElement, ['autocomplete', 'autofocus', 'placeholder']);
$attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('title', $formElement['tooltip']);
$attribute .= $this->getInputCheckPattern($formElement['checkType'], $formElement['checkPattern']);
if (is_array($arrMinMax)) {
$attribute .= Support::doAttribute('min', $arrMinMax[0]);
$attribute .= Support::doAttribute('max', $arrMinMax[1]);
}
$attribute .= $this->getAttributeFeMode($formElement[FE_MODE]);
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
return "
" . $this->getHelpBlock();
}
/**
* Build Grid JQW element.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @throws UserFormException
*/
public function buildGridJQW(array $formElement, $htmlFormElementId, $value) {
// TODO: implement
throw new UserFormException("Not implemented yet: buildGridJQW()", ERROR_NOT_IMPLEMENTED);
}
/**
* Build Note.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return mixed
*/
public function buildNote(array $formElement, $htmlFormElementId, $value, array &$json) {
return $value;
}
/**
* Build Pill
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return mixed
*/
public function buildPill(array $formElement, $htmlFormElementId, $value, array &$json) {
return $value;
}
/**
* Build a HTML fieldset. Renders all assigned FormElements inside the fieldset.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @return mixed
* @throws CodeException
* @throws DbException
*/
public function buildFieldset(array $formElement, $htmlFormElementId, $value, array &$json) {
$attribute = '';
// save parent processed FE's
$tmpStore = $this->feSpecNative;
$attribute .= Support::doAttribute('name', $htmlFormElementId);
$attribute .= Support::doAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
//
$html = '';
if ($formElement['label'] !== '') {
$html .= '' . $formElement['label'] . ' ';
}
$html .= $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START];
// child FE's
$sql = SQL_FORM_ELEMENT_SPECIFIC_CONTAINER;
$this->feSpecNative = $this->db->sql($sql, ROW_REGULAR, ['yes', $this->formSpec["id"], 'native,container', $formElement['id']]);
HelperFormElement::explodeParameterInArrayElements($this->feSpecNative);
$html .= $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE_SUBRECORD, 0, $json);
$html .= $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END];
$html .= ' ';
// restore parent processed FE's
$this->feSpecNative = $tmpStore;
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
return $html;
}
/**
* @param $table
* @param $recordId
* @param $symbol
* @param $toolTip
* @return string
*/
private function createDeleteLink($table, $recordId, $symbol, $toolTip) {
if ($this->showDebugInfo) {
$toolTip .= PHP_EOL . "table = '$table'" . PHP_EOL . "id = '$recordId'";
}
$url = $this->createDeleteUrl($table, $recordId);
return Support::wrapTag('', $symbol);
}
}