Commit 4000736f authored by Carsten  Rose's avatar Carsten Rose
Browse files

AdministratorManual/Index.rst: added note to install mysqlnd Driver.

UsersManual/Index.rst: added Form.showButton
Support: added findInSet()
AbstractBuildForm: added createDeleteUrl(), prepareSubrecord(), getFormTable(). Rewrote buildSubrecord(): added 'delete' link, rearranged 'new' link. 'ShowDebugInfo' enhanced.
BuildFormBootstrap: rewrote BuildButton() to switch New/Delete Button on or off. Show debugInfo as tooltip.
Constants: New Subrecord contants.
formEditor.sql: added Form.showButton. Adjusted records
parent 4cfe7b62
......@@ -116,3 +116,14 @@ Page loaded: www.example.com?index.php&id=start&s=badcaffee1234&type=2&L=3, with
* $_SESSION[$urlparam] => <sip> >> $_SESSION['form=Person&r=1'] => 'badcaffee1234'
FormElement
===========
Checkbox
--------
<div class="checkbox">
<label>
<input type="checkbox">label 1
</label>
</div>
\ No newline at end of file
......@@ -96,8 +96,9 @@ abstract class AbstractBuildForm {
'pill' => 'Pill'
];
$this->symbol['edit'] = "<span class='glyphicon glyphicon-pencil'></span>";
$this->symbol['new'] = "<span class='glyphicon glyphicon-plus'></span>";
$this->symbol[SYMBOL_EDIT] = "<span class='glyphicon glyphicon-pencil'></span>";
$this->symbol[SYMBOL_NEW] = "<span class='glyphicon glyphicon-plus'></span>";
$this->symbol[SYMBOL_DELETE] = "<span class='glyphicon glyphicon-trash'></span>";
$this->inputCheckPattern = OnArray::inputCheckPatternArray();
}
......@@ -233,8 +234,6 @@ abstract class AbstractBuildForm {
*/
public function getFormTagAtrributes() {
//TODO: ttcontent id eintragen
// $attribute['id'] = $this->store->getVar(STORE_TYPO3,'1234');
$attribute['id'] = $this->getFormId();
$attribute['method'] = 'post';
$attribute['action'] = $this->getActionUrl();
......@@ -308,14 +307,14 @@ abstract class AbstractBuildForm {
// Iterate over all FormElements
foreach ($this->feSpecNative as $fe) {
$debugStack = array();
if (($filter === FORM_ELEMENTS_NATIVE && $fe['type'] === 'subrecord') ||
($filter === FORM_ELEMENTS_SUBRECORD && $fe['type'] !== 'subrecord')
) {
continue; // skip this FE
}
$debugStack = array();
// Log / Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, $fe['name'] . ' / ' . $fe['id'], STORE_SYSTEM);
......@@ -355,25 +354,6 @@ abstract class AbstractBuildForm {
abstract public function doSubrecords();
/**
* Create a link (incl. SIP) to delete the current record.
*
* @return string String: "API_DIR/delete.php?sip=...."
*/
public function createDeleteUrl($table, $recordId) {
$queryStringArray = [
SIP_TABLE => $table,
SIP_RECORD_ID => $recordId
];
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
return $sip->queryStringToSip($queryString, RETURN_URL, API_DIR . '/delete.php');
}
abstract public function buildRowNative($formElement, $elementHtml);
abstract public function buildRowPill($formElement, $elementHtml);
......@@ -997,12 +977,83 @@ abstract class AbstractBuildForm {
* @throws UserException
*/
public function buildSubrecord(array $formElement, $htmlFormElementId, $value) {
$html = '';
$rcText = false;
$nameColumnId = 'id';
$targetTableName = '';
$flagNew = false;
$flagEdit = false;
$flagDelete = false;
$toolTipDelete = '';
$linkNew = '';
$showDebugInfo = false;
$primaryRecord = $this->store->getStore(STORE_RECORD);
if (!$this->prepareSubrecod($formElement, $primaryRecord, $rcText, $nameColumnId)) {
return $rcText;
}
if (isset($formElement[SUBRECORD_PARAMETER_FORM])) {
$showDebugInfo = $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM);
$linkNew = Support::wrapTag('<th>', $this->createFormLink($formElement, 0, $primaryRecord, $this->symbol[SYMBOL_NEW], 'New', $showDebugInfo));
// Decode settings in subrecordOption
$flagNew = Support::findInSet(SUBRECORD_NEW, $formElement['subrecordOption']);
$flagEdit = Support::findInSet(SUBRECORD_EDIT, $formElement['subrecordOption']);
if ($flagDelete = Support::findInSet(SUBRECORD_DELETE, $formElement['subrecordOption'])) {
$targetTableName = $this->getFormTable($formElement[SUBRECORD_PARAMETER_FORM]);
}
}
// construct column attributes
$control = $this->getSubrecordColumnControl(array_keys($formElement['sql1'][0]));
// Subrecord: Column titles
$columns = $linkNew;
$columns .= '<th>' . implode('</th><th>', $control['title']) . '</th>';
if ($flagDelete)
$columns .= '<th></th>';
$html = Support::wrapTag('<tr>', $columns);
foreach ($formElement['sql1'] as $row) {
$rowHtml = '';
if ($flagEdit) {
$rowHtml .= Support::wrapTag('<td>', $this->createFormLink($formElement, $row[$nameColumnId], $primaryRecord, $this->symbol[SYMBOL_EDIT], 'Edit', $showDebugInfo));
} elseif ($flagNew) {
$rowHtml .= Support::wrapTag('<td>', $rowHtml, false);
}
// All columns
foreach ($row as $columnName => $value) {
$rowHtml .= Support::wrapTag('<td>', $this->renderCell($control, $columnName, $value));
}
if ($flagDelete) {
$rowHtml .= Support::wrapTag('<td>', $this->createDeleteLink($targetTableName, $row['id'], $this->symbol[SYMBOL_DELETE], 'Delete', $showDebugInfo));
}
$html .= Support::wrapTag('<tr>', $rowHtml, true);
}
return Support::wrapTag('<table class="table">', $html, true);
}
/**
* @param $formElement
* @param $primaryRecord
* @param $rcText
* @param $nameColumnId
* @return bool
* @throws \qfq\UserException
*/
private function prepareSubrecod(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) {
if (!isset($primaryRecord['id'])) {
return 'Please save main record fist.';
$rcText = 'Please save record fist.';
return false;
}
if (!is_array($formElement['sql1'])) {
......@@ -1011,10 +1062,10 @@ abstract class AbstractBuildForm {
// No records?
if (count($formElement['sql1']) == 0) {
return '';
$rcText = '';
return false;
}
$nameColumnId = 'id';
if (!isset($formElement['sql1'][0][$nameColumnId]))
$nameColumnId = '_id';
......@@ -1022,31 +1073,85 @@ abstract class AbstractBuildForm {
throw new UserException('Missing column \'id\' (or "@_id") in \'sql1\' Query', ERROR_DB_MISSING_COLUMN_ID);
}
// construct column attributes
$control = $this->getSubrecordColumnControl(array_keys($formElement['sql1'][0]));
return true;
}
// $html .= '<b>' . $formElement['label'] . '</b>';
// $html .= '<table border="1">';
/**
* Renders an Link with a symbol (edit/new) and register a new SIP to grant permission to the link..
*
* Returns <a href="<Link>">[icon]</a>
*
* Link: <page>?s=<SIP>&<standard typo3 params>
* 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 = <<primarytable.id>>
* p_id = <<variable defined in SIP or Client>>
* x_id = 12 (constant)
*
*
* @param $formElement
* @param $targetRecordId
* @param $record
* @return string
* @throws UserException
*/
private function createFormLink(array $formElement, $targetRecordId, array $record, $symbol, $toolTip, $showDebugInfo = false) {
$linkNew = $this->createFormLink($formElement, 0, $primaryRecord, $this->symbol[SYMBOL_NEW], 'New');
$html .= '<p>' . $linkNew . '</p>';
$queryStringArray = [
SIP_FORM => $formElement[SUBRECORD_PARAMETER_FORM],
SIP_RECORD_ID => $targetRecordId
];
$html .= '<table class="table">';
$html .= '<tr><th></th><th>' . implode('</th><th>', $control['title']) . '</th></tr>';
// 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];
}
}
}
foreach ($formElement['sql1'] as $row) {
if ($showDebugInfo)
$toolTip .= PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
$html .= '<tr>';
$html .= '<td>' . $this->createFormLink($formElement, $row[$nameColumnId], $primaryRecord, $this->symbol[SYMBOL_EDIT], 'Edit') . '</td>';
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];
}
foreach ($row as $columnName => $value) {
$html .= '<td>' . $this->renderCell($control, $columnName, $value) . '</td>';
}
$html .= '</tr>';
//-----------------
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
$url = $sip->queryStringToSip($queryString);
return Support::wrapTag('<a href="' . $url . '" title="' . $toolTip . '">', $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'];
}
$html .= '</table>';
return $html;
return '';
}
/**
......@@ -1110,64 +1215,6 @@ abstract class AbstractBuildForm {
return $control;
}
/**
* Renders an Link with a symbol (edit) and register a new SIP to grant permission to the link..
*
* Returns <a href="<Link>">[icon]</a>
*
* Link: <page>?s=<SIP>&<standard typo3 params>
* 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 = <<primarytable.id>>
* p_id = <<variable defined in SIP or Client>>
* x_id = 12 (constant)
*
*
* @param $formElement
* @param $targetRecordId
* @param $record
* @return string
* @throws UserException
*/
private function createFormLink(array $formElement, $targetRecordId, array $record, $symbol, $linkTitle) {
$queryStringArray = [
'form' => $formElement['form'],
'r' => $targetRecordId
];
// Add custom query parameter
if (isset($formElement['detail'])) {
$detailParam = KeyValueStringParser::parse($formElement['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];
}
}
}
Support::appendTypo3ParameterToArray($queryStringArray);
// If there is a specific targetpage defined, take it.
if (isset($formElement['page']) && $formElement['page'] !== '') {
$queryStringArray['id'] = $formElement['page'];
}
//-----------------
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
$url = $sip->queryStringToSip($queryString);
return "<a href='$url' title='$linkTitle'>$symbol</a>";
}
/**
* Renders $value as specified in array $control
*
......@@ -1209,6 +1256,44 @@ abstract class AbstractBuildForm {
return $cell;
}
/**
* @param $table
* @param $recordId
* @param $symbol
* @param $toolTip
* @return string
*/
private function createDeleteLink($table, $recordId, $symbol, $toolTip, $showDebugInfo = false) {
if ($showDebugInfo) {
$toolTip .= PHP_EOL . "table = '$table'" . PHP_EOL . "id = '$recordId'";
}
$url = $this->createDeleteUrl($table, $recordId);
return Support::wrapTag('<a href="' . $url . '" title="' . $toolTip . '">', $symbol);
}
/**
* Create a link (incl. SIP) to delete the current record.
*
* @return string String: "API_DIR/delete.php?sip=...."
*/
public function createDeleteUrl($table, $recordId) {
$queryStringArray = [
SIP_TABLE => $table,
SIP_RECORD_ID => $recordId
];
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
return $sip->queryStringToSip($queryString, RETURN_URL, API_DIR . '/delete.php');
}
/**
* Builds an Upload (File) Button.
*
......
......@@ -181,37 +181,59 @@ class BuildFormBootstrap extends AbstractBuildForm {
* @return string
*/
private function buildButtons() {
$toolTipNew = 'New';
$toolTipDelete = 'Delete';
$buttonDelete = '';
$buttonNew = '';
if ($this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes') {
$toolTipNew .= PHP_EOL . "form = '" . $this->formSpec['name'] . "'" . PHP_EOL . "r = 0";
$toolTipDelete .= PHP_EOL . "table = '" . $this->formSpec['tableName'] . "'" . PHP_EOL . "r = '" . $this->store->getVar(SIP_RECORD_ID, STORE_SIP) . "'";
}
$sipParamString = OnArray::toString($this->store->getStore(STORE_SIP), ' = ', PHP_EOL, "'");
$formEditUrl = $this->createFormEditUrl();
$debugButtons = <<<BUTTONS
$buttonDebug = <<<BUTTON
<div class="btn-group" role="group">
<button id="debug-button" type="button" class="btn btn-default navbar-btn" title="$sipParamString"><span class="glyphicon glyphicon-eye-open"></span></button>
<a href="$formEditUrl" id="form-edit-button" class="btn btn-default navbar-btn" title="Edit form"><span class="glyphicon glyphicon-wrench"></span></a>
</div>
BUTTONS;
BUTTON;
if (Support::findInSet(FORM_BUTTON_DELETE, $this->formSpec['showButton'])) {
$buttonDelete = <<<BUTTON
<div class="btn-group" role="group">
<button id="delete-button" type="button" class="btn btn-default navbar-btn" title="$toolTipDelete"><span class="glyphicon glyphicon-trash"></span></button>
</div>
BUTTON;
}
$formEditButton = ($this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes') ? $debugButtons : '';
if (Support::findInSet(FORM_BUTTON_NEW, $this->formSpec['showButton'])) {
$buttonNew = <<<BUTTON
<div class="btn-group" role="group">
<button id="delete-button" type="button" class="btn btn-default navbar-btn" title="$toolTipNew"><span class="glyphicon glyphicon-plus"></span></button>
</div>
BUTTON;
}
$buttonFormEdit = ($this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes') ? $buttonDebug : '';
$html = <<<BUTTONS
$html = <<<BUTTON
<div class="col-md-3 ">
<div class="btn-toolbar pull-right" role="toolbar">
$formEditButton
$buttonFormEdit
<div class="btn-group" role="group">
<button id="save-button" type="button" class="btn btn-default navbar-btn" title="Save"><span class="glyphicon glyphicon-ok"></span></button>
<button id="close-button" type="button" class="btn btn-default navbar-btn" title="Close"><span class="glyphicon glyphicon-remove"></span></button>
</div>
<div class="btn-group" role="group">
<button id="delete-button" type="button" class="btn btn-default navbar-btn" title="Delete"><span class="glyphicon glyphicon-trash"></span></button>
</div>
<div class="btn-group" role="group">
<button id="new-button" type="button" class="btn btn-default navbar-btn" title="New"><span class="glyphicon glyphicon-plus"></span></button>
</div>
$buttonDelete
$buttonNew
</div>
</div>
BUTTONS;
BUTTON;
return $html;
}
......@@ -332,6 +354,4 @@ EOF;
return $html;
}
}
\ No newline at end of file
......@@ -10,7 +10,7 @@ const EXT_KEY = 'qfq';
const CONFIG_INI = "config.ini"; // QFQ configuration file: db access
const GFX_INFO = 'typo3conf/ext/qfq/Resources/Public/icons/note.gif';
const API_DIR = 'typo3conf/ext/qfq/qfq/api/';
const API_DIR = 'typo3conf/ext/qfq/qfq/api';
const QFQ_LOG = 'qfq.log';
......@@ -23,6 +23,8 @@ const FORM_PERMISSION_LOGGED_IN = 'logged_id';
const FORM_PERMISSION_LOGGED_OUT = 'logged_out';
const FORM_PERMISSION_ALWAYS = 'always';
const FORM_PERMISSION_NEVER = 'never';
const FORM_BUTTON_NEW = 'new';
const FORM_BUTTON_DELETE = 'delete';
const FORM_FORWARD_MODE_NO = 'no';
const FORM_FORWARD_MODE_AUTO = 'auto';
......@@ -66,12 +68,6 @@ const ROW_KEYS = "keys";
const KVP_IF_VALUE_EMPTY_COPY_KEY = 'if_value_empty_copy_key';
const KVP_VALUE_GIVEN = 'value_given';
// BuildForm
const SUBRECORD_COLUMN_WIDTH = 20;
const FORM_ELEMENTS_NATIVE = 'native';
const FORM_ELEMENTS_SUBRECORD = 'subrecord';
const FORM_ELEMENTS_NATIVE_SUBRECORD = 'native_subrecord';
// QFQ Error Codes
const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
......@@ -267,12 +263,24 @@ const API_ANSWER_REDIRECT_CLIENT = 'client';
const API_ANSWER_REDIRECT_NO = 'no';
const API_ANSWER_REDIRECT_URL = 'url';
// FORM
// BuildForm
const SYMBOL_NEW = 'new';
const SYMBOL_EDIT = 'edit';
const SYMBOL_DELETE = 'delete';
//CHECKBOX
const CHECKBOX_VALUE_CHECKED = 'checked';
const CHECKBOX_VALUE_UNCHECKED = 'unchecked';
const CHECKBOX_ORIENTATION = 'orientation';
//
// Subrecord
const SUBRECORD_COLUMN_WIDTH = 20;
const FORM_ELEMENTS_NATIVE = 'native';
const FORM_ELEMENTS_SUBRECORD = 'subrecord';
const FORM_ELEMENTS_NATIVE_SUBRECORD = 'native_subrecord';
const SUBRECORD_NEW = SYMBOL_NEW;
const SUBRECORD_EDIT = SYMBOL_EDIT;
const SUBRECORD_DELETE = SYMBOL_DELETE;
const SUBRECORD_PARAMETER_FORM = CLIENT_FORM;
const SUBRECORD_PARAMETER_PAGE = 'page';
const SUBRECORD_PARAMETER_DETAIL = 'detail';
\ No newline at end of file
......@@ -187,6 +187,7 @@ class Database {
/**
* Fires query $sql and fetches result als assoc array (all modes but ROW_KEYS) or as num array (mode: ROW_EKYS). Throws exception.
*
* $mode
* ROW_REGULAR: Return 2-dimensional assoc array. Every query row is one array row.
* ROW_IMPLODE_ALL: Return string. All cells of all rows imploded to one string.
......@@ -200,7 +201,7 @@ class Database {
* @param string $mode
* @param array $parameterArray
* @param string $specificMessage
* @return mixed|null
* @return mixed|null If no record found, empty string ( ROW_EXPECT_0_1, ROW_EXPECT_1) or empty array (all other modes)
* @throws \qfq\CodeException
* @throws \qfq\DbException
*/
......
......@@ -72,9 +72,9 @@ class Support {
/**
* Wraps some $inner fragment with a CSS styled $tooltipText . CSS is configured in 'Resources/Public/qfq-jqw.css'.
*
*
* Based on: http://www.w3schools.com/howto/howto_css_tooltip.asp
*
*
* @param $inner
* @param $tooltipText
* @return string
......@@ -85,16 +85,19 @@ class Support {
}
/**
* If the given $file does not start with a '/', it's relative and the Typo3 Extension path will be added.
* @param $file
* @return string
* Search for the parameter $needle in $haystack. The argumenst has to be seperated by ','.
*
* Returns the false if not found or index of found place. Be carefull: use unary operator to compare for 'false'
*
* @param $needle
* @param $haystack
* @return boolean true if found, else false
*/
public static function ifRelativePathPrependExtensionPath($file) {
public static function findInSet($needle, $haystack) {
$arr = explode(',', $haystack);
return array_search($needle, $arr) !== false;
if ($file[0] !== '/') {
if (isset($_SERVER['SCRIPT_FILENAME']))
$file = dirname($_SERVER['SCRIPT_FILENAME']) . '/typo3conf/ext/qfq/' . $file;
}
return $file;
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS `Form` (
`permitNew` ENUM('sip', 'logged_in', 'logged_out', 'always', 'never') NOT NULL DEFAULT 'sip',
`permitEdit` ENUM('sip', 'logged_in', 'logged_out', 'always', 'never') NOT NULL DEFAULT 'sip',
`render` ENUM('plain', 'table', 'bootstrap') NOT NULL DEFAULT 'plain',
`showButton` SET('new', 'delete') NOT NULL DEFAULT 'new,delete',
`multiMode` ENUM('none', 'horizontal', 'vertical') NOT NULL DEFAULT 'none',
`multiSql` TEXT NOT NULL,
`multiDetailForm` VARCHAR(255) NOT NULL DEFAULT '',
......@@ -121,46 +122,47 @@ INSERT INTO Form (name, title, noteInternal, tableName, permitNew, permitEdit, r
'Form', 'always', 'always', 'bootstrap', '', 'maxVisiblePill=3\nclass=container-fluid');
# FormEditor: FormElements
INSERT INTO FormElement (formId, name, label, mode, type, class, ord, size, maxLength, note, clientJs, value, sql1, sql2, parameter, feIdContainer)
INSERT INTO FormElement (formId, name, label, mode, type, class, ord, size, maxLength, note, clientJs, value, sql1, sql2, parameter, feIdContainer, subrecordOption)
VALUES
(1, 'basic', 'Basic', 'show', 'pill', 'container', 10, 0, 0, '', '', '', '', '', '', 0),
(1, 'permission', 'Permission', 'show', 'pill', 'container', 20, 0, 0, '', '', '', '', '', '', 0),
(1, 'various', 'Various', 'show', 'pill', 'container', 30, 0, 0, '', '', '', '', '', '', 0),
(1, 'multi', 'Multi', 'show', 'pill', 'container', 40, 0, 0, '', '', '', '', '', '', 0),
(1, 'formelement', 'Formelement', 'show', 'pill', 'container', 50, 0