Commit de377b07 authored by Carsten  Rose's avatar Carsten Rose
Browse files

#2067

CODING.md: update docs for situation 'new record already saved'.
FillStoreForm.php: In case of form load with r=0, the submitted form element names are does not contain the effective record id - workaround implemented.
Session.php: refactored clear(), create unsetItem().
Sip.php: new buildParamStringFromArray(), updateSipToSession().
Store.php: For r=0 SIPs always add a uniqe parameter.
AbstractBuildForm.php: modified message for subrecords on new record.
QuickFormQuery.php: create unique SIP on form load.
parent 0b89f3b4
...@@ -23,7 +23,7 @@ LOAD ...@@ -23,7 +23,7 @@ LOAD
* When qfq starts, * When qfq starts,
* (Form) Looking for a formname at: * (Form) Looking for a formname at:
1. Typo3 Bodytext Element, 1. Typo3 Bodytext Element,
2. For the 'SIP' ($_GET['s']) 2. For the 'SIP' ($_GET['s'] => $S_SESSION['qfq'][$_GET['s']]="form=person&r=123")
3. $_GET variables 'form' and 'r' (=recordId) - the parameter 'form' has to be allowed in 'Permit URL Parameter' of 3. $_GET variables 'form' and 'r' (=recordId) - the parameter 'form' has to be allowed in 'Permit URL Parameter' of
the specified form. This means: load the form to check, if it is allowed to load the form!? the specified form. This means: load the form to check, if it is allowed to load the form!?
* If a formname is found, the search stops and the specified form will be processed. * If a formname is found, the search stops and the specified form will be processed.
...@@ -36,12 +36,20 @@ LOAD ...@@ -36,12 +36,20 @@ LOAD
* All parameters from active SIP: [$this->store->getStore(STORE_SIP)] * All parameters from active SIP: [$this->store->getStore(STORE_SIP)]
* Check Contstants.php for known Store members * Check Contstants.php for known Store members
* In QuickFormQuery.php the whole Form will be copied to $this->formSpec and depending on further processing, the elements are * In QuickFormQuery.php the whole Form will be copied to `$this->formSpec` and depending on further processing, the
available in $this->feNative and $this->feAction. elements are available in `$this->feNative` and `$this->feAction`.
* The Form specificaton (table form) will be evaluated direct after loading. * The Form specificaton (table form) will be evaluated direct after loading.
* The FormElement specification will be evaluated later on in BuildForm*.php * The FormElement specification will be evaluated later on in BuildForm*.php
* If a form is called without a SIP (form.permitNew='always'), than a SIP is created on the fly (as a
parameter in the form).
* Depending on `r=0` or `r>0` a form submit will do an MySQL `insert` or `update` later during save.
* For new records (r=0), clicking on 'save' without closing the form is a tricky situation. Additionally the user might have open multiple
tabs (same form, all r=0) and after saving the record (wihtout closing the form) the user expects that it's ok to edit
the record again and again. Unfortunately, the initial created SIP (before 'form load') is not uniqe anymore (multiple
tabs might contain a saved 'new record'). To guarantee correct saving of r=0 records, a unique on the fly generated SIP
is creatd during form load - individually per browser tab.
SAVE SAVE
---- ----
* Via wrapper api/save.php * Via wrapper api/save.php
...@@ -51,6 +59,18 @@ SAVE ...@@ -51,6 +59,18 @@ SAVE
* Client will handle the response of save.php. * Client will handle the response of save.php.
* Optional redirection initiated by client. * Optional redirection initiated by client.
New records
...........
* r=0 (missing 'r' means r=0)
* After saving the SIP content will be updated with the new record.
Remember that the SIP in the URL is *not* the SIP used in the form to identify the form/record. The form use a
individual 'new record' SIP.
Existing records
................
* r>0 ('r' have to exist)
DELETE DELETE
------ ------
* Via wrapper api/delete.php * Via wrapper api/delete.php
......
...@@ -1470,7 +1470,7 @@ abstract class AbstractBuildForm { ...@@ -1470,7 +1470,7 @@ abstract class AbstractBuildForm {
private function prepareSubrecod(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) { private function prepareSubrecod(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) {
if (!isset($primaryRecord['id'])) { if (!isset($primaryRecord['id'])) {
$rcText = 'Please save record first.'; $rcText = 'Please save and close record and reopen it.';
return false; return false;
} }
......
...@@ -331,10 +331,13 @@ const SIP_MODE_ANSWER = '_modeAnswer'; // Mode how delete() will answer to clien ...@@ -331,10 +331,13 @@ const SIP_MODE_ANSWER = '_modeAnswer'; // Mode how delete() will answer to clien
const SIP_FORM = CLIENT_FORM; const SIP_FORM = CLIENT_FORM;
const SIP_TABLE = 'table'; // delete a record from 'table' const SIP_TABLE = 'table'; // delete a record from 'table'
const SIP_URLPARAM = 'urlparam'; 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 // FURTHER: all extracted params from 'urlparam
const VAR_RANDOM = 'random'; const VAR_RANDOM = 'random';
//const RECORD_ID_NEW = -1;
// TOKEN evaluate // TOKEN evaluate
const TOKEN_ESCAPE_SINGLE_TICK = 's'; const TOKEN_ESCAPE_SINGLE_TICK = 's';
const TOKEN_ESCAPE_DOUBLE_TICK = 'd'; const TOKEN_ESCAPE_DOUBLE_TICK = 'd';
......
...@@ -226,12 +226,13 @@ class QuickFormQuery { ...@@ -226,12 +226,13 @@ class QuickFormQuery {
} }
$sipFound = $this->validateForm($foundInStore); $sipFound = $this->validateForm($foundInStore);
if (!$sipFound) { $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT);
// For 'new' record always create a new TAB-uniq (for this current form, nowhere else used) SIP.
// With such a TAB-uniq SIP, multiple TABs and following repeated NEWs are easily implemented.
if (!$sipFound || ($mode == FORM_LOAD && $recordId == 0)) {
$this->store->createSipAfterFormLoad($formName); $this->store->createSipAfterFormLoad($formName);
} }
$formAction = new FormAction($this->formSpec, $this->db, $this->phpUnit);
$this->store->fillStoreTableDefaultColumnType($this->formSpec[F_TABLE_NAME]); $this->store->fillStoreTableDefaultColumnType($this->formSpec[F_TABLE_NAME]);
switch ($this->formSpec['render']) { switch ($this->formSpec['render']) {
...@@ -248,10 +249,10 @@ class QuickFormQuery { ...@@ -248,10 +249,10 @@ class QuickFormQuery {
throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN); throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
} }
$formAction = new FormAction($this->formSpec, $this->db, $this->phpUnit);
switch ($mode) { switch ($mode) {
case FORM_LOAD: case FORM_LOAD:
case FORM_UPDATE: case FORM_UPDATE:
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT);
$formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD); $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
$data = $build->process($mode); $data = $build->process($mode);
...@@ -284,7 +285,7 @@ class QuickFormQuery { ...@@ -284,7 +285,7 @@ class QuickFormQuery {
$sipArray = $this->store->getStore(STORE_SIP); $sipArray = $this->store->getStore(STORE_SIP);
if ($sipArray[SIP_RECORD_ID] == 0) { if ($sipArray[SIP_RECORD_ID] == 0) {
// After insert: a new SIP for the new record id is required. // After insert: a new SIP for the new record id is required.
$this->newRecordCreateSip($sipArray, $rc); $this->newRecordUpdateSip($rc);
$htmlElementNameIdZero = true; $htmlElementNameIdZero = true;
} }
...@@ -536,37 +537,15 @@ class QuickFormQuery { ...@@ -536,37 +537,15 @@ class QuickFormQuery {
* @param $sipArray * @param $sipArray
* @param $recordId * @param $recordId
*/ */
private function newRecordCreateSip($sipArray, $recordId) { private function newRecordUpdateSip($recordId) {
// Update current SIP store with new RecordID
$tmpParam = array(); $sipArray = $this->store->getStore(STORE_SIP);
$sipArray[SIP_RECORD_ID] = $recordId;
foreach ($sipArray as $key => $value) { $this->store->setVarArray($sipArray, STORE_SIP, true);
switch ($key) {
case SIP_SIP:
case SIP_URLPARAM:
case SIP_TABLE:
continue;
case SIP_RECORD_ID:
$tmpParam[SIP_RECORD_ID] = $recordId;
break;
default:
// further vars stored in old SIP (form, maybe default values)
$tmpParam[$key] = $value;
break;
}
}
// Construct fake urlparam // Update SIP urlparam
$tmpUrlparam = OnArray::toString($tmpParam); store::getSipInstance()->updateSipToSession($sipArray);
// Create a SIP which has never been passed by URL - further processing might expect this to exist.
$sip = store::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
$this->store->setVar(CLIENT_SIP, $sip, STORE_CLIENT);
// Overwrite SIP Store
$tmpParam[SIP_SIP] = $sip;
$this->store->setVarArray($tmpParam, STORE_SIP, true);
} }
/** /**
...@@ -672,4 +651,41 @@ class QuickFormQuery { ...@@ -672,4 +651,41 @@ class QuickFormQuery {
return $result; return $result;
} }
/**
* @param $sipArray
* @param $recordId
*/
private function newRecordCreateSip($sipArray, $recordId) {
$tmpParam = array();
foreach ($sipArray as $key => $value) {
switch ($key) {
case SIP_SIP:
case SIP_URLPARAM:
case SIP_TABLE:
continue;
case SIP_RECORD_ID:
$tmpParam[SIP_RECORD_ID] = $recordId;
break;
default:
// further vars stored in old SIP (form, maybe default values)
$tmpParam[$key] = $value;
break;
}
}
// Construct fake urlparam
$tmpUrlparam = OnArray::toString($tmpParam);
// Create a SIP which has never been passed by URL - further processing might expect this to exist.
$sip = store::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
$this->store->setVar(CLIENT_SIP, $sip, STORE_CLIENT);
// Overwrite SIP Store
$tmpParam[SIP_SIP] = $sip;
$this->store->setVarArray($tmpParam, STORE_SIP, true);
}
} }
\ No newline at end of file
...@@ -104,6 +104,11 @@ class FillStoreForm { ...@@ -104,6 +104,11 @@ class FillStoreForm {
} }
} }
// Check if there is a 'new record already saved' situation:
// yes: the names of the input fields are submitted with '<fieldname>:0' instead of '<fieldname>:<id>'
// no: regular situation, take real 'recordid'
$fakeRecordId = isset($sipValues[SIP_MAKE_URLPARAM_UNIQ]) ? 0 : $sipValues[SIP_RECORD_ID];
// Iterate over all formelements. Sanatize values. Built an assoc array $newValues. // Iterate over all formelements. Sanatize values. Built an assoc array $newValues.
foreach ($this->feSpecNative AS $formElement) { foreach ($this->feSpecNative AS $formElement) {
...@@ -118,7 +123,7 @@ class FillStoreForm { ...@@ -118,7 +123,7 @@ class FillStoreForm {
$formElement = $this->evaluate->parseArray($formElement, $debugStack); $formElement = $this->evaluate->parseArray($formElement, $debugStack);
// Get related formElement. Construct the field name used in the form. // Get related formElement. Construct the field name used in the form.
$clientFieldName = HelperFormElement::buildFormElementName($formElement['name'], $sipValues[SIP_RECORD_ID]); $clientFieldName = HelperFormElement::buildFormElementName($formElement['name'], $fakeRecordId);
// Some Defaults // Some Defaults
$formElement = Support::setFeDefaults($formElement); $formElement = Support::setFeDefaults($formElement);
......
...@@ -61,7 +61,7 @@ class Session { ...@@ -61,7 +61,7 @@ class Session {
if ($feUidLoggedIn !== $feUserUidSession) { if ($feUidLoggedIn !== $feUserUidSession) {
// destroy existing session store // destroy existing session store
Session::clear(); Session::clearAll();
// save new feUserUid, feUserName // save new feUserUid, feUserName
Session::set(SESSION_FE_USER_UID, $feUidLoggedIn); Session::set(SESSION_FE_USER_UID, $feUidLoggedIn);
...@@ -98,7 +98,7 @@ class Session { ...@@ -98,7 +98,7 @@ class Session {
/** /**
* *
*/ */
public static function clear() { public static function clearAll() {
if (self::$phpUnit) { if (self::$phpUnit) {
self::$sessionLocal = array(); self::$sessionLocal = array();
...@@ -120,6 +120,19 @@ class Session { ...@@ -120,6 +120,19 @@ class Session {
} }
} }
/**
* Unset the given $key
*
* @param $key
*/
public static function unsetItem($key) {
if (isset($_SESSION[SESSION_NAME][$key])) {
unset($_SESSION[SESSION_NAME][$key]);
}
}
/** /**
* @param bool|false $phpUnit * @param bool|false $phpUnit
* @return Session class * @return Session class
......
...@@ -67,11 +67,8 @@ class Sip { ...@@ -67,11 +67,8 @@ class Sip {
// Split parameter between Script, Client and SIP // Split parameter between Script, Client and SIP
$script = $this->splitParamClientSip($paramArray, $clientArray, $sipArray); $script = $this->splitParamClientSip($paramArray, $clientArray, $sipArray);
// sort array to guarantee identical respresentation in $_SESSION. Param 'a, r, b, ...' should be saved as 'a, b, r, ..'
OnArray::sortKey($sipArray);
// Generate keyname for $_SESSION[] // Generate keyname for $_SESSION[]
$sipParamString = OnArray::toString($sipArray); $sipParamString = $this->buildParamStringFromArray($sipArray);
$sessionParamSip = Session::get($sipParamString); $sessionParamSip = Session::get($sipParamString);
if ($sessionParamSip === false) { if ($sessionParamSip === false) {
...@@ -184,6 +181,32 @@ class Sip { ...@@ -184,6 +181,32 @@ class Sip {
return $script; return $script;
} }
/**
* @param array $sipArray
* @return string
*/
private function buildParamStringFromArray(array $sipArray) {
$tmpArray = array();
foreach ($sipArray as $key => $value) {
switch ($key) {
case SIP_SIP:
case SIP_TARGET_URL:
case SIP_MODE_ANSWER:
case SIP_TABLE:
case SIP_URLPARAM:
break;
default:
$tmpArray[$key] = $value;
break;
}
}
OnArray::sortKey($tmpArray);
return OnArray::toString($tmpArray);
}
/** /**
* Returns a new uniqid, which will be used as a SIP identifier * Returns a new uniqid, which will be used as a SIP identifier
* *
...@@ -201,6 +224,25 @@ class Sip { ...@@ -201,6 +224,25 @@ class Sip {
return uniqid(); return uniqid();
} }
/**
* Update the SIP in the Session according $sipArray.
*
* @param array $sipArray
*/
public function updateSipToSession(array $sipArray) {
$sip = $sipArray[SIP_SIP];
// Remove old entry, cause the the 'key' will change.
$sipParamStringOld = Session::get($sipArray[SIP_SIP]);
Session::unsetItem($sipParamStringOld);
// Generate keyname for $_SESSION[]
$sipParamStringNew = $this->buildParamStringFromArray($sipArray);
Session::set($sip, $sipParamStringNew);
Session::set($sipParamStringNew, $sip);
}
/** /**
* Retrieve Params stored in $_SESSION[$s] * Retrieve Params stored in $_SESSION[$s]
* *
......
...@@ -551,6 +551,10 @@ class Store { ...@@ -551,6 +551,10 @@ class Store {
} }
$tmpParam = [SIP_RECORD_ID => $recordId, SIP_FORM => $formName]; $tmpParam = [SIP_RECORD_ID => $recordId, SIP_FORM => $formName];
if ($recordId == 0) {
// SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
$tmpParam[SIP_MAKE_URLPARAM_UNIQ] = uniqid();
}
// Construct fake urlparam // Construct fake urlparam
$tmpUrlparam = OnArray::toString($tmpParam); $tmpUrlparam = OnArray::toString($tmpParam);
......
...@@ -62,7 +62,7 @@ class SessionTest extends \PHPUnit_Framework_TestCase { ...@@ -62,7 +62,7 @@ class SessionTest extends \PHPUnit_Framework_TestCase {
// write/read data1 // write/read data1
Session::set('var1', 'data1'); Session::set('var1', 'data1');
Session::clear(); Session::clearAll();
$val = Session::get('var1'); $val = Session::get('var1');
$this->assertEquals(false, $val); $this->assertEquals(false, $val);
......
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