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 ...@@ -19,9 +19,6 @@ Features not implemented yet
---------------------------- ----------------------------
* Multi Forms * Multi Forms
* FormElement:
* type=action (especially not *addNupdate*)
QFQ content element QFQ content element
------------------- -------------------
...@@ -135,12 +132,16 @@ Most fields of a form specification might contain: ...@@ -135,12 +132,16 @@ Most fields of a form specification might contain:
* A variable (or SQL) statement is surrounded by curly braces: * A variable (or SQL) statement is surrounded by curly braces:
*{{VarName[:<store / prio>[:<sanitize class>]]}}* *{{VarName[:<store / prio>[:<sanitize class>[:<escape>]]]}}*
* Example: * Example:
*{{r}}* *{{r}}*
*{{index:FS}}*
*{{name:FS:alnumx:s}}*
*{{SELECT name FROM person WHERE id=1234}}* *{{SELECT name FROM person WHERE id=1234}}*
*{{SELECT name FROM person WHERE id={{r}} }}* *{{SELECT name FROM person WHERE id={{r}} }}*
...@@ -152,9 +153,9 @@ Most fields of a form specification might contain: ...@@ -152,9 +153,9 @@ Most fields of a form specification might contain:
* *{{ SELECT "Hello World" }}* acts as *{{SELECT "Hello World"}}* * *{{ SELECT "Hello World" }}* acts as *{{SELECT "Hello World"}}*
* *{{ varname }}* acts as *{{varname}}* * *{{ 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, * 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. 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 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>. * If no value is found, the value is an <empty string>.
...@@ -162,7 +163,20 @@ URL Parameter ...@@ -162,7 +163,20 @@ URL Parameter
------------- -------------
* URL (=GET) Parameter can be used in *forms* and *reports* as variables. * 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 Sanitize class
-------------- --------------
...@@ -318,6 +332,8 @@ Store: *VARS* - V ...@@ -318,6 +332,8 @@ Store: *VARS* - V
+====================+============================================================================================================================================+ +====================+============================================================================================================================================+
| random | random string with length of 32 chars, alphanum | | random | random string with length of 32 chars, alphanum |
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +--------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| slaveId | see FormElement `action` |
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
SQL SQL
--- ---
...@@ -905,51 +921,68 @@ current record either to finalize the upload or to delete a previous uploaded fi ...@@ -905,51 +921,68 @@ current record either to finalize the upload or to delete a previous uploaded fi
Class: Action Class: Action
------------- -------------
Type: beforeLoad Type: before... | after...
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
* Former: formallow Types:
* Function: a) fire SQL, b) allow / deny access
* respects 'processRow'
Type: afterLoad * beforeLoad
^^^^^^^^^^^^^^^ * afterLoad
* beforeSave
* afterSave
* beforeInsert
* afterInsert
* beforeUpdate
* afterUpdate
* beforeDelete
* afterDelete
* Probably not implemented: no usecase. * Check data
* Function: fire SQL
* respects 'processRow'
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 * OK: the expected number of records has been selected. Continue processing the next FormElement.
* Function: a) fire SQL, b) allow / deny access * Fail: the expected number of records has not been selected (less or more): Display an error message and abort the
* respects 'processRow' current form load or form save.
Type: afterSave FormElement.’‘’parameter’‘’:
^^^^^^^^^^^^^^^
* Maybe successor of *addnupdate* * ‘’‘requiredList‘’‘ - List of `native`-FormElements: only if all of those elements are filled, the current
* Function: fire SQL `action`-FormElement will be processed.
* respects 'processRow' * ‘’‘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 * ‘’‘expectRecords‘’‘ - number of records. E.g.: `expectRecords=0`
* respects 'processRow' * ‘’‘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 * Save values of a form to different record(s), optionally on different table(s).
* respects 'processRow' * 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 Type: sendmail
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
...@@ -2295,8 +2328,8 @@ QFQ content record:: ...@@ -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 Requirement: new records should automatically get the highest number plus 10 for their 'ord' value. Existing records
should not be altered. should not be altered.
...@@ -2329,3 +2362,16 @@ Version 2 ...@@ -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. 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}}`. * 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'); ...@@ -20,25 +20,21 @@ require_once(__DIR__ . '/../Evaluate.php');
class formAction { class formAction {
// private $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form // 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 = ''; private $primaryTableName = '';
/** /**
* @var Database * @var Database
*/ */
private $db = null; private $db = null;
/** /**
* @var Store * @var Store
*/ */
private $store = null; private $store = null;
/**
* @var Evaluate instantiated class
*/
protected $evaluate = null;
/** /**
* @param array $formSpec * @param array $formSpec
* @param Database $db * @param Database $db
...@@ -46,7 +42,7 @@ class formAction { ...@@ -46,7 +42,7 @@ class formAction {
*/ */
public function __construct(array $formSpec, Database $db, $phpUnit = false) { public function __construct(array $formSpec, Database $db, $phpUnit = false) {
$this->formSpec = $formSpec; $this->formSpec = $formSpec;
$this->primaryTableName = Support::setIfNotSet($formSpec,F_TABLE_NAME); $this->primaryTableName = Support::setIfNotSet($formSpec, F_TABLE_NAME);
$this->db = $db; $this->db = $db;
...@@ -56,24 +52,6 @@ class formAction { ...@@ -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 integer $recordId
* @param array $feSpecAction * @param array $feSpecAction
...@@ -101,10 +79,14 @@ class formAction { ...@@ -101,10 +79,14 @@ class formAction {
} }
if ($fe[FE_TYPE] !== FE_TYPE_BEFORE_LOAD && $fe[FE_TYPE] !== FE_TYPE_AFTER_LOAD) { 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); $this->fillStoreRecord($this->primaryTableName, $recordId);
} }
if (!$this->checkRequiredList($fe)) {
continue;
}
// Preparation for Log, Debug // Preparation for Log, Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM);
...@@ -115,44 +97,49 @@ class formAction { ...@@ -115,44 +97,49 @@ class formAction {
} }
/** /**
* @param array $fe * @param $table
* @return int * @param $recordId
* @throws CodeException * @throws CodeException
* @throws DbException
* @throws UserFormException * @throws UserFormException
*/ */
private function doSlave(array $fe, $recordId) { private function fillStoreRecord($table, $recordId) {
$flagUpdateMasterRecord = false;
// Get the slaveId
$tmp = Support::setIfNotSet($fe, FE_SLAVE_ID);
$slaveId = $this->evaluate->parse($tmp);
if ($slaveId === '' && $fe[FE_NAME] !== '') { if (!is_string($table) || $table === '') {
// if the current action element has the same name as a real master record column: take that value as an id throw new UserFormException("");
$slaveId = $this->store->getVar($fe[FE_NAME], STORE_RECORD);
if ($slaveId !== false) {
$flagUpdateMasterRecord = true;
} }
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);
} }
if ($slaveId === '' || $slaveId === false) {
$slaveId = 0;
} }
// 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) { if (!isset($fe[FE_REQUIRED_LIST]) || $fe[FE_REQUIRED_LIST] === '') {
$slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]); return true;
} else {
$this->evaluate->parse($fe[FE_SQL_UPDATE]);
} }
if ($flagUpdateMasterRecord) { $arr = explode(',', $fe[FE_REQUIRED_LIST]);
$this->db->sql("UPDATE " . $this->primaryTableName . " SET " . $fe[FE_NAME] . " = $slaveId WHERE id = ? LIMIT 1", ROW_REGULAR, [ $recordId ] ); 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 { ...@@ -194,6 +181,49 @@ class formAction {
throw new UserFormException($msg, ERROR_REPORT_FAILED_ACTION); 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` ( ...@@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS `Form` (
`permitNew` ENUM('sip', 'logged_in', 'logged_out', 'always', 'never') NOT NULL DEFAULT 'sip', `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', `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 '', `requiredParameter` VARCHAR(255) NOT NULL DEFAULT '',
`showButton` SET('new', 'delete', 'close', 'save') NOT NULL DEFAULT 'new,delete,close,save', `showButton` SET('new', 'delete', 'close', 'save') NOT NULL DEFAULT 'new,delete,close,save',
`multiMode` ENUM('none', 'horizontal', 'vertical') NOT NULL DEFAULT 'none', `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