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
......@@ -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) {
$fe = $this->initActionFormElement($fe);
$fe = HelperFormElement::initActionFormElement($fe);
// Only process FE elements of types listed in $feTypeList. Skip all other
if (false === Support::findInSet($fe[FE_TYPE], $feTypeList)) {
......@@ -112,25 +112,6 @@ class FormAction {
return $flagModified;
}
/**
* Set all necessary keys - subsequent 'isset()' are not necessary anymore.
*
* @param array $fe
* @return array
*/
private function initActionFormElement(array $fe) {
$list = [FE_TYPE, FE_SLAVE_ID, FE_SQL_VALIDATE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE, FE_EXPECT_RECORDS,
FE_REQUIRED_LIST, FE_MESSAGE_FAIL, FE_SENDMAIL_TO, FE_SENDMAIL_CC, FE_SENDMAIL_BCC, FE_SENDMAIL_FROM, FE_SENDMAIL_SUBJECT, FE_SENDMAIL_REPLY_TO,
FE_SENDMAIL_FLAG_AUTO_SUBMIT, FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID];
foreach ($list as $key) {
Support::setIfNotSet($fe, $key);
}
return $fe;
}
/**
* Copy the current primary record to STORE_RECORD
*
......@@ -266,13 +247,16 @@ class FormAction {
}
// Store the slaveId: it's used and replaced in the update statement.
$this->store->setVar(ACTION_KEYWORD_SLAVE_ID, $slaveId, STORE_VAR, true);
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);
// If given: fire a sqlBefore query
$this->evaluate->parse($fe[FE_SQL_BEFORE]);
// Fire slave query
if ($slaveId == 0) {
$slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]);
// Store the slaveId: it's used and replaced in the update statement.
$this->store->setVar(ACTION_KEYWORD_SLAVE_ID, $slaveId, STORE_VAR, true);
// Store the slaveId: might be used later
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);
} else {
$this->evaluate->parse($fe[FE_SQL_UPDATE]);
}
......@@ -286,6 +270,30 @@ class FormAction {
// If given: fire a delete query
$this->evaluate->parse($fe[FE_SQL_DELETE]);
// If given: fire a sqlAfter query
$this->evaluate->parse($fe[FE_SQL_AFTER]);
return $slaveId;
}
/**
* Set all necessary keys - subsequent 'isset()' are not necessary anymore.
*
* @param array $fe
* @return array
*/
private function initActionFormElement(array $fe) {
$list = [FE_TYPE, FE_SLAVE_ID, FE_SQL_VALIDATE, FE_SQL_BEFORE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE,
FE_SQL_AFTER, FE_EXPECT_RECORDS, FE_REQUIRED_LIST, FE_MESSAGE_FAIL, FE_SENDMAIL_TO, FE_SENDMAIL_CC,
FE_SENDMAIL_BCC, FE_SENDMAIL_FROM, FE_SENDMAIL_SUBJECT, FE_SENDMAIL_REPLY_TO, FE_SENDMAIL_FLAG_AUTO_SUBMIT,
FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID];
foreach ($list as $key) {
Support::setIfNotSet($fe, $key);
}
return $fe;
}
}
......@@ -149,4 +149,42 @@ class HelperFormElement
return $feSpecNative;
}
/**
* Set all necessary keys - subsequent 'isset()' are not necessary anymore.
*
* @param array $fe
* @return array
*/
public static function initActionFormElement(array $fe) {
$list = [FE_TYPE, FE_SLAVE_ID, FE_SQL_VALIDATE, FE_SQL_BEFORE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE,
FE_SQL_AFTER, FE_EXPECT_RECORDS, FE_REQUIRED_LIST, FE_MESSAGE_FAIL, FE_SENDMAIL_TO, FE_SENDMAIL_CC,
FE_SENDMAIL_BCC, FE_SENDMAIL_FROM, FE_SENDMAIL_SUBJECT, FE_SENDMAIL_REPLY_TO, FE_SENDMAIL_FLAG_AUTO_SUBMIT,
FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID];
foreach ($list as $key) {
Support::setIfNotSet($fe, $key);
}
return $fe;
}
/**
* Set all necessary keys - subsequent 'isset()' are not necessary anymore.
*
* @param array $fe
* @return array
*/
public static function initUploadFormElement(array $fe) {
$list = [FE_SQL_BEFORE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE, FE_SQL_AFTER];
foreach ($list as $key) {
Support::setIfNotSet($fe, $key);
}
return $fe;
}
}
\ No newline at end of file
......@@ -80,6 +80,9 @@ class FillStoreForm {
* @throws UserFormException
*/
public function process() {
// The following 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];
$html = '';
$newValues = array();
......@@ -120,7 +123,7 @@ class FillStoreForm {
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
// Evaluate current FormElement: e.g. FE_MODE_SQL
$formElement = $this->evaluate->parseArray($formElement, $debugStack);
$formElement = $this->evaluate->parseArray($formElement, $skip, $debugStack);
// Get related formElement. Construct the field name used in the form.
$clientFieldName = HelperFormElement::buildFormElementName($formElement['name'], $fakeRecordId);
......
......@@ -118,6 +118,7 @@ class Store {
CLIENT_REQUEST_URI => SANITIZE_ALLOW_ALL,
CLIENT_SCRIPT_NAME => SANITIZE_ALLOW_ALNUMX,
CLIENT_PHP_SELF => SANITIZE_ALLOW_ALNUMX,
// CLIENT_UPLOAD_FILENAME => SANITIZE_ALLOW_ALLBUT,
// SYSTEM_DBUSER => SANITIZE_ALLOW_ALNUMX,
// SYSTEM_DBSERVER => SANITIZE_ALLOW_ALNUMX,
......
Markdown is supported
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