Commit 2e7b75b6 authored by Carsten  Rose's avatar Carsten Rose
Browse files

'Upload advanced mode' implementiert. V2 (kein FormElement Action 'afterSave' mehr noetig)

Default fuer Store Prioritaet hat sich geaendert: alt='FSRD', neu='FSRVD' - damit wird ist es ueberfluessig den V Store anzugeben.
Variable '_filename' umbenannt in 'filename' und verschoben von STORE_FORM nach STORE_VARS. Damit ist es ueberfluessig eine Sanatize Klasse anzugeben.
STORE_VAR hat zwei neue Variablen: 'filename', 'fileDestination'.
Bei Form-Action Elemente gibt es zwei neue Typen: 'sqlBefore' und 'sqlAfter'

Index.rst: Dokumentation auf V2 angepasst. Doku fuer V1 hat es nie gegeben.
FormAction.php: Moved function initActionFormElement to HelperFormElement::initActionFormElement(), Implement sqlBefore & sqlAfter for Action Elemente.
HelperFormElement.php: new class initActionFormElement(), initUploadFormElement().
FillStoreForm.php, AbstractBuildForm.php, Evaluate.php: Implemented the $skip parameter to suppress unwanted variable expansion during form load.
Constants.php: New STORE_USE_DEFAULT, VAR_FILE_DESTINATION, VAR_FILENAME,FE_SQL_AFTER, FE_SQL_BEOFRE, FE_TYPE_UPLOAD.
Evaluate.php: moved 'decryptCurlyBraces()' up, in order to  create better error messages.
Save.php: new doUploadSlave(), implement 'Upload advanced mode'.
parent 3b40767e
......@@ -48,7 +48,7 @@ QFQ Keywords (Bodytext)
| | * by SIP: **form = {{form}}** |
| | * by SQL: **form = {{SELECT c.form FROM conference AS c WHERE c.id={{a:C}} }}** |
+-------------------+---------------------------------------------------------------------------------+
| r | recordId. The form will load the record with the specified id |
| r | <record id> The form will load the record with the specified id |
| | * Variants: **r = 123**, by SQL: **r = {{SELECT ...}}** |
| | * If not specified, the default is '0' |
+-------------------+---------------------------------------------------------------------------------+
......@@ -259,7 +259,7 @@ Only variables that are known in a specified store can be substituted.
| | SYSTEM_SQL_RAW ... SYSTEM_FORM_ELEMENT_COLUMN, c) Any custom fields: CONTACT, HELP, .. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
* Default *<prio>*: *FSRD* - Form / SIP / Record / Table definition.
* Default *<prio>*: *FSRVD* - Form / SIP / Record / Vars / Table definition.
* Hint: Preferable, parameter should be submitted by SIP, not by Client (=URL).
* Warning: Data submitted via 'Client' can be easily spoofed and altered.
......@@ -276,6 +276,7 @@ Predefined variable names
Store: *FORM* - F
^^^^^^^^^^^^^^^^^
* Sanatized: *yes*
* Represents the values in the form, typically before saving them.
* Used for:
......@@ -288,16 +289,13 @@ Store: *FORM* - F
+=================================+============================================================================================================================================+
| <FormElement name> | Name of native *FormElement*. To get, exactly and only, the specified *FormElement* (for 'p_id'): *{{p_id:F}}* |
+---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| _filename__<FE_NAME> | Original filename of uploaded file from FormElement <FE_NAME> |
+---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| _deleted__<FE_NAME>='1' | If there was already a file uploaded and the user has clicked the trash, than this field becomes '1' |
+---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
.. _STORE_SIP:
Store: *SIP* - S
^^^^^^^^^^^^^^^^
* Sanatized: *no*
* Filled automatically by creating links. E.g.:
* in `Report` by using `_page?` or `_link` (with active 's')
......@@ -324,6 +322,7 @@ Store: *SIP* - S
Store: *RECORD* - R
^^^^^^^^^^^^^^^^^^^
* Sanatized: *no*
* Current record loaded in Form.
* If r=0, alle values are empty.
......@@ -338,6 +337,7 @@ Store: *RECORD* - R
Store: *BEFORE* - B
^^^^^^^^^^^^^^^^^^^
* Sanatized: *no*
* Current record loaded in Form without any modification.
* If r=0, alle values are empty.
......@@ -354,6 +354,8 @@ This store is handy to compare new and old values of a form.
Store: *CLIENT* - C
^^^^^^^^^^^^^^^^^^^
* Sanatized: *yes*
+-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
| Name | Explanation |
+=========================+==========================================================================================================================================+
......@@ -383,6 +385,8 @@ Store: *CLIENT* - C
Store: *TYPO3* (Bodytext) - T
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Sanatized: *no*
+-------------------------+-------------------------------------------------------------------+----------+
| Name | Explanation | Note |
+=========================+===================================================================+==========+
......@@ -415,6 +419,8 @@ Store: *TYPO3* (Bodytext) - T
Store: *VARS* - V
^^^^^^^^^^^^^^^^^
* Sanatized: *no*
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| Name | Explanation |
+=========================+============================================================================================================================================+
......@@ -422,6 +428,10 @@ Store: *VARS* - V
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| slaveId | see *FormElement* `action` |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| filename | Original filename of an uploaded file via an 'upload'-FormElement. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| fileDestinaton | Destination (path & filename) for an uploaded file. Defined in an 'upload'-FormElement.parameter. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
.. _STORE_SYSTEM:
......@@ -429,6 +439,8 @@ Store: *VARS* - V
Store: *SYSTEM* - Y
^^^^^^^^^^^^^^^^^^^
* Sanatized: *no*
+-------------------------+--------------------------------------------------------------------------+
| Name | Explanation |
+=========================+==========================================================================+
......@@ -912,7 +924,18 @@ Effect matrix
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| accept | | | | | | | | | | | | | - 3 | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| slaveId | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| fileDestination | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| sqlBefore | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| sqlInsert | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| sqlDelete | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
| sqlAfter | | | | | | | | | | | | | - | |
+------------------+----------+---------+-------------+----------+--------+-------+------+----------+-------+--------+-----------+---------+--------+--------+
* 1: A line break created every <size> elements. Easy way to make checkboxes or radio vertical instead of horizontal.
* 2: Any number >1 makes the 'select' input 'multiple' ready.
......@@ -1195,45 +1218,119 @@ An upload element is based on a 'file browse'-button and a 'trash'-button (=dele
The 'file browse'-button is displayed, if there is no file uploaded already.
The 'trash'-button is displayed, if there is a file uploaded already.
The user can than select a file from the local filesystem.
After clicking on the browse brutton , the user can select a file from the local filesystem.
After choosing the file, the upload starts immediately, shown by a turning wheel. When the server received the whole file
and accepts the file, the 'file browse'-button dissappears and the filename is shown, followed by a 'trash'-button.
Either the user is satisfied now or the user can delete the uploaded file (and maybe upload another one).
Until this point, the file is cached on the server but not copied to the final destination. The user have to save the
Until this point, the file is cached on the server but not copied to the `fileDestination`. The user have to save the
current record, either to finalize the upload or to delete a previous uploaded file.
The FormElement behaves like a 'native FormElement' (showing controls/text on the form) as well as an 'action FormElement'
by fireing queries and doing some additional actions during form save. Inside the *Form editor* it's shown as a 'native FormElement'.
* *FormElement*. *parameter*:
* *fileDestination*: Destination where to copy the file. A good practice is to specify a relative `fileDestination` -
such an installation (filesystem and database) are moveable.
* If the original filename should be part of `fileDestination`, the variable *{{_filename_<FE_NAME>}}* can be used. Example ::
* If the original filename should be part of `fileDestination`, the variable *{{filename}}* (STORE_VARS) can be used. Example ::
fileDestination={{SELECT 'fileadmin/user/pictures/', p.name, '-{{_filename_<FE_NAME>}}' FROM Person AS p WHERE p.id={{r}} }}
fileDestination={{SELECT 'fileadmin/user/pictures/', p.name, '-{{filename}}' FROM Person AS p WHERE p.id={{id:R0}} }}
* *<FE_NAME>* is the name of the upload FormElement.
* If a file already exist under `fileDestination`, an error message is shown and 'save' is aborted.
The user has no possibility to overwrite the already existing file. If the whole workflow is correct, this situation
should no arise. Check also *fileReplace* below.
* The original filename will be sanatized: only alnum characters are allowed. German 'umlaut' will be replaced by
'ae', 'ue', 'oe'. All non valid characters will be replaced by '-'.
* If a file already exist under `fileDestination`, an error message is shown and 'save' is aborted. The user has no
possibility to overwrite the already existing file. If the whole workflow is correct, this situation should no
arise. Check also *fileReplace* below.
* All necessary subdirectories in `fileDestination` are automatically created.
* Using the current record id in the `fileDestination`: Using {{r}} is problematic for new records: that one is still '0'
at the time of saving. Use `{{id:R0}}` instead.
* Using the current record id in the `fileDestination`: Using {{r}} is problematic for a 'new' primary record: that
one is still '0' at the time of saving. Use `{{id:R0}}` instead.
* *slaveId*, *sqlBefore*, *sqlInsert*, *sqlUpdate*, *sqlDelete*, *sqlUpdate*: Only used in :ref:`Upload advanced mode`.
* *fileReplace*=*always*: If `fileDestination` exist - replace it by the new one.
* *fileReplace=always*: If `fileDestination` exist - replace it by the new one.
Deleting a record and the referenced file
'''''''''''''''''''''''''''''''''''''''''
If the user deletes a record which contains reference(s) to files, such files are deleted too.
If the user deletes a record (e.g. pressing the delete button on a form) which contains reference(s) to files, such files
are deleted too. Slave records, which might be also deleted through a 'delete'-form, are *not* checked for file references
and therefore such files are not deleted on the filesystem.
Only columns where the columname contains `pathFileName` are checked for file references. Therefore, always choose a
columnanem which contains `pathFileName`.
columnanme which contains `pathFileName`.
If there are other records, which references the same file, such files are not deleted.
It's a very basic check: just the current column of the current table is compared. In general it's not a good idea to
have mutliple references to a single file. Therefore this check is just a fallback.
.. _Upload simple mode:
Upload simple mode
;;;;;;;;;;;;;;;;;;
Requires: *'upload'-FormElement.name = 'column name'* of an column in the primary table.
After moving the file to `fileDestination`, the current record/column will be updated to `fileDestination`.
The database definition of the named column has to be a string variant (varchar, text but not numeric or else).
On form load, the column value will be displayed as path/filename.
Deleting an uploaded file in the form (by clicking on the trash near beside) will delete
the file on the filesystem as well. The column will be updated to an empty string.
This happens automatically without any further definiton in the 'upload'-FormElement.
Multiple 'upload'-FormElements per form are possible. Each of it needs an own table column.
.. _Upload advanced mode:
Upload advanced mode
;;;;;;;;;;;;;;;;;;;;
Requires: *'upload'-FormElement.name* is unknown as a column in the primary table.
This mode will serve further database structure scenarios.
A typical name for such an 'upload'-FormElement might start with 'my', e.g. 'myUpload1'.
* `FormElement.value`: The path/filename, shown during 'form load' to indicate a previous uploaded file, has to be queried
with this field. E.g.::
{{SELECT pathFilenamePicture FROM Note WHERE id={{slaveId}} }}
* `FormElement.parameter`:
* *fileDestination*: determine the path/filename. E.g.::
fileDestination=fileadmin/person/{{name:R0}}_{{id:R}}/uploads/picture_{{filename}}
* *slaveId*: Defines the target record where to retrieve and store the path/filename of the uploaded file.
Check also :ref:`slave-id`. E.g.::
slaveId={{SELECT id FROM Note WHERE pId={{id:R0}} AND type='picture' LIMIT 1}}
* *sqlBefore*: fired during a form save, before the following queries are fired.
* *sqlInsert*: fired if `slaveId=0` and an upload exist (user has choosen a file)::
sqlInsert={{INSERT INTO Note (pId, type, pathFileName) VALUE ({{id:R0}}, 'image', '{{fileDestination}}') }}
* *sqlUpdate*: fired if `slaveId>0` and an upload exist (user has choosen a file). E.g.::
sqlUpdate={{UPDATE Note SET pathFileName = '{{fileDestination}}' WHERE id={{slaveId}} LIMIT 1}}
* *sqlDelete*: fired if `slaveId>0` and no upload exist (user has not choosen a file). E.g.::
sqlDelete={{DELETE FROM Note WHERE id={{slaveId:V}} LIMIT 1}}
* *sqlAfter*: fired after all previous queries have been fired. Might update the new created id to a primary record. E.g.::
sqlUpdate={{UPDATE Person SET noteIdPicture = {{slaveId}} WHERE id={{id:R0}} LIMIT 1 }}
If there are other records, which references the same file, such files are not deleted (but the record is deleted).
It's a very basic check: just the current column of the current table is compared.
.. _class-action:
......@@ -1267,8 +1364,8 @@ Types:
* beforeDelete
* afterDelete
Validate
''''''''
sqlValidate
'''''''''''
Perform checks by fireing a SQL query and expecting a predefined number of selected records.
......@@ -1292,16 +1389,14 @@ Validate
* `messageFail` - Message to show. E.g.: `messageFail=There is already a person called {{firstname:F:all}} {{name:F:all}}`
sqlInsert / sqlUpdate / sqlDelete
'''''''''''''''''''''''''''''''''
* Save values of a form to different record(s), optionally on different table(s).
* Typically useful on 'afterSave' - be careful when using it earlier, e.g. beforeLoad.
.. _slave-id:
slaveId
'''''''
*FormElement*.’‘’parameter’‘’:
* `requiredList` - List of `native`-*FormElement*: only if all of those elements are filled, the current
`action`-*FormElement* will be processed.
* `slaveId`:
* Auto fill: name the action `action`-*FormElement* equal to an existing column (table from the current form definition).
......@@ -1311,18 +1406,29 @@ sqlInsert / sqlUpdate / sqlDelete
* Explicit definition: `slaveId=123` or `slaveId={{SELECT id ...}}`
* `sqlInsert`: fired if `slaveId=0` or `slaveId=''`.
* `sqlUpdate`: fired if `slaveId>0`:
* `sqlDelete`: always fired (after sqlInsert or sqlUpdate) - the definition, when this query is fired, might change in
the future.
Note:
* `{{slaveId:V}}` can be used in any query as the current slaveId. It's *important* to Specify Store V!
* `{{slaveId}}` can be used in any query as the current slaveId.
* If the `action`-*FormElement* name exist as a column in the master record: Update that column *automatically* with the
recent slaveId (after an INSERT the last_insert_id() acts as the new `slaveId`).
sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
* Save values of a form to different record(s), optionally on different table(s).
* Typically useful on 'afterSave' - be careful when using it earlier, e.g. beforeLoad.
*FormElement*.’‘’parameter’‘’:
* `requiredList` - List of `native`-*FormElement*: only if all of those elements are filled, the current
`action`-*FormElement* will be processed.
* `sqlBefore`: always fired (before any sqlInsert or sqlUpdate)
* `sqlInsert`: fired if `slaveId=0` or `slaveId=''`.
* `sqlUpdate`: fired if `slaveId>0`:
* `sqlDelete`: always fired (after sqlInsert or sqlUpdate) - the definition, when this query is fired, might change in the future.
* `sqlAfter`: always fired (after sqlInsert, sqlUpdate or sqlDelete)
Example
'''''''
......@@ -1922,8 +2028,8 @@ The colum name is composed of the string *page* and a trailing character to spec
+---------------+-----------------------------------------------+-------------------------------------+----------------------------------------------+
|_pagec |Internal link without a grafic, with question |*Please confirm!* |p:<pageId>[&param] |
+---------------+-----------------------------------------------+-------------------------------------+----------------------------------------------+
|_paged |Internal link with delete icon (trash) |*Delete record ?* |U:form=<formname>&r=<recordid> *or* |
| | | |U:table=<tablename>&r=<recordid> |
|_paged |Internal link with delete icon (trash) |*Delete record ?* |U:form=<formname>&r=<record id> *or* |
| | | |U:table=<tablename>&r=<record id> |
+---------------+-----------------------------------------------+-------------------------------------+----------------------------------------------+
|_pagee |Internal link with edit icon (pencil) |empty |p:<pageId>[&param] |
+---------------+-----------------------------------------------+-------------------------------------+----------------------------------------------+
......@@ -1973,8 +2079,8 @@ These column offers a link, with a confirmation question, to delete one record (
::
SELECT "U:table=<tablename>&r=<recordId>|q:<question>|..." AS _paged
SELECT "U:form=<formname>&r=<recordId>|q:<question>|..." AS _paged
SELECT "U:table=<tablename>&r=<record id>|q:<question>|..." AS _paged
SELECT "U:form=<formname>&r=<record id>|q:<question>|..." AS _paged
..
......
......@@ -342,6 +342,9 @@ abstract class AbstractBuildForm {
$modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, $storeUse = STORE_USE_DEFAULT, $mode = FORM_LOAD) {
$html = '';
$flagOutput = false;
// The following 'FormElement.parameter' will never be used during load (fe.type='upload').
$skip = [FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, F_FE_PARAMETER];
// get current data record
if ($recordId > 0 && $this->store->getVar('id', STORE_RECORD) === false) {
......@@ -368,8 +371,16 @@ abstract class AbstractBuildForm {
// Preparation for Log, Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM);
// for Upload FormElements, it's necessary to precalculate an optional given 'slaveId'.
if ($fe[FE_TYPE] === FE_TYPE_UPLOAD) {
$slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]);
if (is_numeric($slaveId)) {
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR);
}
}
// evaluate current FormElement
$formElement = $this->evaluate->parseArray($fe, $debugStack);
$formElement = $this->evaluate->parseArray($fe, $skip, $debugStack);
// Some Defaults
$formElement = Support::setFeDefaults($formElement);
......
......@@ -233,7 +233,7 @@ const STORE_EMPTY = "E"; // value: '', might helpfull if variable is not defined
const STORE_SYSTEM = "Y"; // various system values like db connection credentials
const STORE_EXTRA = 'X'; // Persistent Store: contains arrays! Not Usefull for user. Used by system.
const STORE_USE_DEFAULT = "FSRD";
const STORE_USE_DEFAULT = "FSRVD";
//
// Store: Definitions / Members
//
......@@ -251,8 +251,6 @@ const CLIENT_UPLOAD_FE_NAME = 'name';
const CLIENT_SIP_FOR_FORM = '_sipForForm';
const CLIENT_FE_NAME = '_feName';
const CLIENT_UPLOAD_FILENAME = '_filename_'; // will be extended by 'formElement[FE_name]'. E.g. '_filename_pathFileName1'
const CLIENT_FILE_DELETED = '_deleted_'; // will be extended by 'formElement[FE_name]'. E.g. '_deleted_pathFileName1'=0|1
// ALL $_SERVER variables: http://php.net/manual/en/reserved.variables.server.php
// The following exist and might be the most used ones.
......@@ -353,7 +351,12 @@ const SIP_URLPARAM = 'urlparam';
const SIP_MAKE_URLPARAM_UNIQ = '_makeUrlParamUniq'; // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
// FURTHER: all extracted params from 'urlparam
const ACTION_KEYWORD_SLAVE_ID = 'slaveId';
const VAR_RANDOM = 'random';
const VAR_FILE_DESTINATION = 'fileDestination';
const VAR_SLAVE_ID = ACTION_KEYWORD_SLAVE_ID;
const VAR_FILENAME = 'filename'; // Original filename of an uploaded file.
//const RECORD_ID_NEW = -1;
......@@ -514,6 +517,8 @@ const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of
const FE_MESSAGE_FAIL = 'messageFail'; // Action: Message to display, if FE_SQL_VALIDATE fails.
const FE_REQUIRED_LIST = 'requiredList'; // Optional list of FormElements which have to be non empty to make this 'action'-FormElement active.
const FE_SLAVE_ID = 'slaveId'; // Action; Value or Query to compute id of slave record.
const FE_SQL_AFTER = 'sqlAfter'; // Action: Always fired
const FE_SQL_BEFORE = 'sqlBefore'; // Action: Always fired
const FE_SQL_UPDATE = 'sqlUpdate'; // Action: Update Statement for slave record
const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave record.
const FE_SQL_DELETE = 'sqlDelete'; // Action: Delete Statement to delete unused slave record.
......@@ -536,6 +541,7 @@ const FE_RETYPE_SOURCE_NAME = '_retypeSourceName'; // QFQ internal reference to
const RETYPE_FE_NAME_EXTENSION = 'RETYPE';
// FormElement Types
const FE_TYPE_UPLOAD = 'upload';
const FE_TYPE_EXTRA = 'extra';
const FE_TYPE_SENDMAIL = 'sendMail';
const FE_TYPE_BEFORE_LOAD = 'beforeLoad';
......@@ -549,8 +555,6 @@ const FE_TYPE_AFTER_INSERT = 'afterInsert';
const FE_TYPE_AFTER_UPDATE = 'afterUpdate';
const FE_TYPE_AFTER_DELETE = 'afterDelete';
const ACTION_KEYWORD_SLAVE_ID = 'slaveId';
// SUPPORT
const PARAM_T3_ALL = 't3 all';
const PARAM_T3_NO_ID = "t3 no id";
......
......@@ -49,14 +49,22 @@ class Evaluate {
* Evaluate a whole array or a array of arrays.
*
* @param $tokenArray
* @param array $skip Optional Array with keynames, which will not be evaluated.
* @param array $debugStack
* @return mixed
* @throws UserFormException
*/
public function parseArray($tokenArray, &$debugStack = array()) {
public function parseArray($tokenArray, array $skip = array(), &$debugStack = array()) {
$arr = array();
foreach ($tokenArray as $key => $value) {
if (array_search($key, $skip) !== false) {
continue;
}
if (is_array($value)) {
$arr[] = $this->parseArray($value);
$arr[] = $this->parseArray($value, $skip);
} else {
$arr[$key] = $this->parse($value, 0, $debugStack);
}
......@@ -67,6 +75,7 @@ class Evaluate {
/**
* Recursive evaluation of 'line'. Constant string, Variables or SQL Query or all of them. All queries will be fired.
* In case of an 'INSERT' statement, return the last_insert_id().
*
* Token to replace have to be enclosed by '{{' and '}}'
*
......@@ -136,13 +145,15 @@ class Evaluate {
$posFirstClose = strpos($result, $this->endDelimiter);
}
$result = Support::decryptDoubleCurlyBraces($result);
if ($flagTokenReplaced === true) {
$debugLocal[] = $debugIndent . "FINAL: " . is_array($result) ? "array(" . count($result) . ")" : "$result";
$debugStack = $debugLocal;
}
return Support::decryptDoubleCurlyBraces($result);
return $result;
}
/**
......
......@@ -304,6 +304,7 @@ class QuickFormQuery {
// If an old record exist: load it. Necessary to delete uploaded files which should be overwritten.
$this->fillStoreWithRecord($this->formSpec[F_TABLE_NAME], $recordId, STORE_RECORD);
// SAVE
$save = new Save($this->formSpec, $this->feSpecAction, $this->feSpecNative);
$rc = $save->process();
......
......@@ -48,6 +48,7 @@ class Save {
/**
* Starts save process. On succcess, returns forwardmode/page.
*
* @return int
* @throws CodeException
* @throws DbException
* @throws UserFormException
......@@ -226,17 +227,27 @@ class Save {
continue;
}
$formElement = HelperFormElement::initUploadFormElement($formElement);
// Preparation for Log, Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
$column = $formElement['name'];
$pathFileName = $this->doUpload($formElement, $formValues[$column], $sip);
// Only update (save) the new pathFileName to the primaryRecord if the corresponding column exist.
if ($pathFileName !== false && isset($primaryRecord[$column])) {
$newValues[$column] = $pathFileName;
// Upload Type: Simple or Advanced
if (isset($primaryRecord[$column])) {
// 'Simple Upload': no special action needed, just process the current (maybe modifired) value.
if ($pathFileName !== false) {
$newValues[$column] = $pathFileName;
}
} else {
// 'Advanced Upload'
$this->doUploadSlave($formElement, $recordId, $pathFileName);
}
}
// Only used in 'Simple Upload'
if (count($newValues) > 0) {
$this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId);
}
......@@ -248,9 +259,9 @@ class Save {
*
* Check also: doc/CODING.md
*
* @param $formElement
* @param $sipUpload
* @return string|false New filename or false on error
* @param array $formElement FormElement 'upload'
* @param string $sipUpload SIP
* @return string|false New pathFilename or false on error
* @throws CodeException
* @throws UserFormException
* @internal param $recordId
......@@ -272,7 +283,6 @@ class Save {
// Delete existing old file.
if (isset($statusUpload[FILES_FLAG_DELETE]) && $statusUpload[FILES_FLAG_DELETE] == '1') {
$this->store->setVar(CLIENT_FILE_DELETED . $formElement[FE_NAME], '1', STORE_FORM);
$arr = $sip->getVarsFromSip($sipUpload);
$oldFile = $arr[EXISTING_PATH_FILE_NAME];
if (file_exists($oldFile)) {
......@@ -314,11 +324,14 @@ class Save {
if (isset($formElement[FE_FILE_DESTINATION])) {
// Provide variable '_filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
// Provide variable 'filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
$origFilename = Sanitize::safeFilename($statusUpload[FILES_NAME]);
$this->store->setVar(CLIENT_UPLOAD_FILENAME . $formElement[FE_NAME], $origFilename, STORE_FORM);
$this->store->setVar(VAR_FILENAME, $origFilename, STORE_VAR);
$pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
// Saved in store for later use during 'Advanced Upload'-post processing
$this->store->setVar(VAR_FILE_DESTINATION, $pathFileName, STORE_VAR);
}
if ($pathFileName === '') {
......@@ -345,6 +358,58 @@ class Save {
return $pathFileName;
}
/**
* 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 doUploadSlave(array $fe, $recordId, $pathFileName) {
$sql = '';
$flagUpdateSlaveId = false;
// Get the slaveId
$slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]);
if ($slaveId === '' || $slaveId === false) {
$slaveId = 0;
}
// Store the slaveId: it's used and replaced in the update statement.
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);
// If given: fire a sqlAfter query
$this->evaluate->parse($fe[FE_SQL_BEFORE]);
if ($slaveId == 0 && $pathFileName != '') {
$sql = $fe[FE_SQL_INSERT];
$flagUpdateSlaveId = true;
}
if ($slaveId > 0 && $pathFileName != '') {
$sql = $fe[FE_SQL_UPDATE];
}
if ($slaveId > 0 && $pathFileName == '') {
$sql = $fe[FE_SQL_DELETE];
}
$rc = $this->evaluate->parse($sql);
if ($flagUpdateSlaveId) {
// Store the slaveId: it's used and replaced in the update statement.
$this->store->setVar(VAR_SLAVE_ID, $rc, STORE_VAR, true);
$slaveId = $rc;
}
// If given: fire a sqlAfter query
$this->evaluate->parse($fe[FE_SQL_AFTER]);
return $slaveId;
}
/**
* Get the complete FormElement for $name
*
......
......@@ -72,7 +72,7 @@ class FormAction {
// Iterate over all Action FormElements
foreach ($feSpecAction as $fe) {