Commit 65b7f84a authored by Carsten  Rose's avatar Carsten Rose
Browse files

UsersManual/index.rst: New option <escape> in variables described. Updated:...

UsersManual/index.rst: New option <escape> in variables described. Updated: before..., after..., slaveId
FormAction.php: Implement 'requiredList' for 'action'-FormElements.
formEditor.sql: new default for render ... bootstrap.
parent ee6ad621
......@@ -19,9 +19,6 @@ Features not implemented yet
----------------------------
* Multi Forms
* FormElement:
* type=action (especially not *addNupdate*)
QFQ content element
-------------------
......@@ -135,12 +132,16 @@ Most fields of a form specification might contain:
* A variable (or SQL) statement is surrounded by curly braces:
*{{VarName[:<store / prio>[:<sanitize class>]]}}*
*{{VarName[:<store / prio>[:<sanitize class>[:<escape>]]]}}*
* Example:
*{{r}}*
*{{index:FS}}*
*{{name:FS:alnumx:s}}*
*{{SELECT name FROM person WHERE id=1234}}*
*{{SELECT name FROM person WHERE id={{r}} }}*
......@@ -152,9 +153,9 @@ Most fields of a form specification might contain:
* *{{ SELECT "Hello World" }}* acts as *{{SELECT "Hello World"}}*
* *{{ varname }}* acts as *{{varname}}*
* There are several stores, from where to retrieve the value. If a value is not found in one store, the next store is searched,
until a value is found or there are no more stores available.
* sdkf
* If anywhere along the line an empty string is found, this **is** a value: therefore, the search will stop.
* If no value is found, the value is an <empty string>.
......@@ -162,7 +163,20 @@ URL Parameter
-------------
* URL (=GET) Parameter can be used in *forms* and *reports* as variables.
* If a value violates a parameter sanitize class, an exception is thrown.
* If a value violates a parameter sanitize class, the value becomes an empty string.
Escape
------
* Variables used in SQL Statements might cause trouble, if they contain single or double ticks.
* Escaping of single or double is defined by the parameter <escape> (fourth parameter):
* 's' - single ticks will be escaped.
* 'd' - double ticks will be escaped.
* It's not possible to escape single and double ticks at the same time.
* Which of them to escape (single or double) depends on the surrounding SQL query.
* Escaping is only necessary inside of SQL queries.
Sanitize class
--------------
......@@ -318,6 +332,8 @@ Store: *VARS* - V
+====================+============================================================================================================================================+
| random | random string with length of 32 chars, alphanum |
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| slaveId | see FormElement `action` |
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
SQL
---
......@@ -905,51 +921,68 @@ current record either to finalize the upload or to delete a previous uploaded fi
Class: Action
-------------
Type: beforeLoad
^^^^^^^^^^^^^^^^
Type: before... | after...
^^^^^^^^^^^^^^^^^^^^^^^^^^
* Former: formallow
* Function: a) fire SQL, b) allow / deny access
* respects 'processRow'
Types:
Type: afterLoad
^^^^^^^^^^^^^^^
* beforeLoad
* afterLoad
* beforeSave
* afterSave
* beforeInsert
* afterInsert
* beforeUpdate
* afterUpdate
* beforeDelete
* afterDelete
* Probably not implemented: no usecase.
* Function: fire SQL
* respects 'processRow'
* Check data
Type: beforeSave
^^^^^^^^^^^^^^^^
Perform checks by fireing s SQL query and expecting a predefined number of selected records. Depending on the
'action'-type, the check is perform on form load, or form save.
* Former: lookup
* Function: a) fire SQL, b) allow / deny access
* respects 'processRow'
* OK: the expected number of records has been selected. Continue processing the next FormElement.
* Fail: the expected number of records has not been selected (less or more): Display an error message and abort the
current form load or form save.
Type: afterSave
^^^^^^^^^^^^^^^
FormElement.’‘’parameter’‘’:
* Maybe successor of *addnupdate*
* Function: fire SQL
* respects 'processRow'
* ‘’‘requiredList‘’‘ - List of `native`-FormElements: only if all of those elements are filled, the current
`action`-FormElement will be processed.
* ‘’‘sqlValidate’‘’ - query. E.g.: `sqlValidate={{!SELECT id FROM Person AS p WHERE p.name LIKE {{name:F}} AND p.firstname LIKE {{firstname:F}} }}`
Type: beforeInsert / afterInsert
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Pay attention to '{{!...' after the equal sign.
* Function: a) fire SQL, b) (before) allow / deny access
* respects 'processRow'
* ‘’‘expectRecords‘’‘ - number of records. E.g.: `expectRecords=0`
* ‘’‘messageFail‘’‘ - Message to show. E.g.: `messageFail=There is already a person called {{firstname:F}} {{name:F}}`
Type: beforeUpdate / afterUpdate
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Insert / Update / Delete records
* Function: a) fire SQL, b) (before) allow / deny access
* respects 'processRow'
* Save values of a form to different record(s), optionally on different table(s).
* Typically usefull on 'afterSave'.
FormElement.’‘’parameter’‘’:
* ‘’‘requiredList‘’‘ - List of `native`-FormElements: only if all of those elements are filled, the current
`action`-FormElement will be processed.
* ‘’‘slaveId‘’‘:
* A value of `0` means the following `sqlInsert` will be fired.
* A value > `0` means the following `sqlUpdate` will be fired and the `slaveId` specifies which one.
* If there is no `slaveId` defined and if there is a column name with the same name as the current `action`-FormElement:
take the value from that column as `slaveId`.
* Access the `slaveId` by using the variable `{{slaveId:V}}` inside of `sqlUpdate` or `sqlInsert`.
* ‘’‘sqlUpdate‘’‘ - query. E.g.: `sqlUpdate={{UPDATE Address SET street = '{{street:F:all}}' WHERE id={{slaveId:V}} LIMIT 1}}`
* ‘’‘sqlInsert‘’‘ - query. E.g.: `sqlInsert={{INSERT INTO Address (pId, street) VALUES ( {{id:R}}, '{{street:F:all}}' WHERE id={{slaveId:V}} }}`
* ‘’‘sqlDelete‘’‘ - query: NOT IMPLEMENTED. Will be usefull for MultiForms.
* If the `action`-FormElement name exist as a column in the master record: Update that column with the recent slaveId
(after an INSERT the last_insert_id() acts as the new `slaveId`).
Type: beforeDelete / afterDelete
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Function: a) fire SQL, b) (before) allow / deny access
* respects 'processRow'
Type: sendmail
^^^^^^^^^^^^^^
......@@ -2295,8 +2328,8 @@ QFQ content record::
}
Secondary records: compute next free 'ord' automatically
--------------------------------------------------------
Form: compute next free 'ord' automatically
-------------------------------------------
Requirement: new records should automatically get the highest number plus 10 for their 'ord' value. Existing records
should not be altered.
......@@ -2329,3 +2362,16 @@ Version 2
Compute the next 'ord' as default value direct inside the secondary form. No change is needed for the primary form.
* Secondary form, `ord` formelement, field `value`: set `{{SELECT IF({{ord:R0}}=0, MAX(IFNULL(fe.ord,0))+10,{{ord:R0}}) FROM (SELECT 1) AS a LEFT JOIN FormElement AS fe ON fe.formId={{formId:S0}} GROUP BY fe.formId}}`.
Form: Person Wizard
-------------------
Requirement: A form that displays the column 'firstname' from table 'Person' and 'email' from table 'Address'. If the
records not exist, the form should create it.
* Form
* Name: wizard
* Titile: Person Wizard
* Table: Person
* Render: bootsrap
\ No newline at end of file
......@@ -20,25 +20,21 @@ require_once(__DIR__ . '/../Evaluate.php');
class formAction {
// private $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form
private $formSpec = array(); // copy of the loaded form
/**
* @var Evaluate instantiated class
*/
protected $evaluate = null; // copy of the loaded form
private $formSpec = array();
private $primaryTableName = '';
/**
* @var Database
*/
private $db = null;
/**
* @var Store
*/
private $store = null;
/**
* @var Evaluate instantiated class
*/
protected $evaluate = null;
/**
* @param array $formSpec
* @param Database $db
......@@ -46,7 +42,7 @@ class formAction {
*/
public function __construct(array $formSpec, Database $db, $phpUnit = false) {
$this->formSpec = $formSpec;
$this->primaryTableName = Support::setIfNotSet($formSpec,F_TABLE_NAME);
$this->primaryTableName = Support::setIfNotSet($formSpec, F_TABLE_NAME);
$this->db = $db;
......@@ -56,24 +52,6 @@ class formAction {
}
/**
* @param $table
* @param $recordId
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
private function fillStoreRecord($table, $recordId) {
if (!is_string($table) || $table === '') {
throw new UserFormException("");
}
if ($recordId !== false && $recordId > 0) {
$record = $this->db->sql("SELECT * FROM $table WHERE id = ?", ROW_EXPECT_1, [$recordId]);
$this->store->setVarArray($record, STORE_RECORD, true);
}
}
/**
* @param integer $recordId
* @param array $feSpecAction
......@@ -101,10 +79,14 @@ class formAction {
}
if ($fe[FE_TYPE] !== FE_TYPE_BEFORE_LOAD && $fe[FE_TYPE] !== FE_TYPE_AFTER_LOAD) {
// Always work on recent data: previous actions might modify the data
// Always work on recent data: previous actions might have modified the data.
$this->fillStoreRecord($this->primaryTableName, $recordId);
}
if (!$this->checkRequiredList($fe)) {
continue;
}
// Preparation for Log, Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM);
......@@ -115,44 +97,49 @@ class formAction {
}
/**
* @param array $fe
* @return int
* @param $table
* @param $recordId
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
private function doSlave(array $fe, $recordId) {
$flagUpdateMasterRecord = false;
// Get the slaveId
$tmp = Support::setIfNotSet($fe, FE_SLAVE_ID);
$slaveId = $this->evaluate->parse($tmp);
private function fillStoreRecord($table, $recordId) {
if ($slaveId === '' && $fe[FE_NAME] !== '') {
// if the current action element has the same name as a real master record column: take that value as an id
$slaveId = $this->store->getVar($fe[FE_NAME], STORE_RECORD);
if ($slaveId !== false) {
$flagUpdateMasterRecord = true;
}
if (!is_string($table) || $table === '') {
throw new UserFormException("");
}
if ($slaveId === '' || $slaveId === false) {
$slaveId = 0;
if ($recordId !== false && $recordId > 0) {
$record = $this->db->sql("SELECT * FROM $table WHERE id = ?", ROW_EXPECT_1, [$recordId]);
$this->store->setVarArray($record, STORE_RECORD, true);
}
}
// put the slaveId to the store: it's used and replaced in the update statement.
$this->store->setVar(ACTION_KEYWORD_SLAVE_ID, $slaveId, STORE_VAR, true);
/**
* Process all FormElements given in the `requiredList` identified be their name.
* If none is empty in STORE_FORM return true, else false.
* If none FormElement is specified, return true.
*
* @param array $fe
* @return bool true if none FE is specified or all specified are non empty.
*/
private function checkRequiredList(array $fe) {
if ($slaveId == 0) {
$slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]);
} else {
$this->evaluate->parse($fe[FE_SQL_UPDATE]);
if (!isset($fe[FE_REQUIRED_LIST]) || $fe[FE_REQUIRED_LIST] === '') {
return true;
}
if ($flagUpdateMasterRecord) {
$this->db->sql("UPDATE " . $this->primaryTableName . " SET " . $fe[FE_NAME] . " = $slaveId WHERE id = ? LIMIT 1", ROW_REGULAR, [ $recordId ] );
$arr = explode(',', $fe[FE_REQUIRED_LIST]);
foreach ($arr as $key) {
$key = trim($key);
$val = $this->store->getVar($key, STORE_FORM);
if ($val === false || $val === '' || $val === '0') {
return false;
}
}
return $slaveId;
return true;
}
/**
......@@ -194,6 +181,49 @@ class formAction {
throw new UserFormException($msg, ERROR_REPORT_FAILED_ACTION);
}
/**
* Create the slave record. First try to evaluate a slaveId. Depending if the slaveId > 0 choose `sqlUpdate` or `sqlInsert`
*
* @param array $fe
* @return int
* @throws CodeException
* @throws UserFormException
*/
private function doSlave(array $fe, $recordId) {
$flagUpdateMasterRecord = false;
// Get the slaveId
$tmp = Support::setIfNotSet($fe, FE_SLAVE_ID);
$slaveId = $this->evaluate->parse($tmp);
if ($slaveId === '' && $fe[FE_NAME] !== '') {
// if the current action element has the same name as a real master record column: take that value as an id
$slaveId = $this->store->getVar($fe[FE_NAME], STORE_RECORD);
}
if ($slaveId === '' || $slaveId === false) {
$slaveId = 0;
}
// Store the slaveId: it's used and replaced in the update statement.
$this->store->setVar(ACTION_KEYWORD_SLAVE_ID, $slaveId, STORE_VAR, true);
// Fire slave query
if ($slaveId == 0) {
$slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]);
} else {
$this->evaluate->parse($fe[FE_SQL_UPDATE]);
}
// Check if there is a column with the same name as the 'action'-FormElement.
if (false !== $this->store->getVar($fe[FE_NAME], STORE_RECORD)) {
// After an insert or update, update the (new) slave id to the master record.
$this->db->sql("UPDATE " . $this->primaryTableName . " SET " . $fe[FE_NAME] . " = $slaveId WHERE id = ? LIMIT 1", ROW_REGULAR, [$recordId]);
}
return $slaveId;
}
}
//
///********************************************************
......
......@@ -9,7 +9,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',
`render` ENUM('plain', 'table', 'bootstrap') NOT NULL DEFAULT 'bootstrap',
`requiredParameter` VARCHAR(255) NOT NULL DEFAULT '',
`showButton` SET('new', 'delete', 'close', 'save') NOT NULL DEFAULT 'new,delete,close,save',
`multiMode` ENUM('none', 'horizontal', 'vertical') NOT NULL DEFAULT 'none',
......
Supports Markdown
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