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
* When qfq starts,
* (Form) Looking for a formname at:
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
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.
......@@ -36,11 +36,19 @@ LOAD
* All parameters from active SIP: [$this->store->getStore(STORE_SIP)]
* 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
available in $this->feNative and $this->feAction.
* In QuickFormQuery.php the whole Form will be copied to `$this->formSpec` and depending on further processing, the
elements are available in `$this->feNative` and `$this->feAction`.
* The Form specificaton (table form) will be evaluated direct after loading.
* 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
----
......@@ -51,6 +59,18 @@ SAVE
* Client will handle the response of save.php.
* 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
------
* Via wrapper api/delete.php
......
......@@ -1470,7 +1470,7 @@ abstract class AbstractBuildForm {
private function prepareSubrecod(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) {
if (!isset($primaryRecord['id'])) {
$rcText = 'Please save record first.';
$rcText = 'Please save and close record and reopen it.';
return false;
}
......
......@@ -331,10 +331,13 @@ const SIP_MODE_ANSWER = '_modeAnswer'; // Mode how delete() will answer to clien
const SIP_FORM = CLIENT_FORM;
const SIP_TABLE = 'table'; // delete a record from 'table'
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 VAR_RANDOM = 'random';
//const RECORD_ID_NEW = -1;
// TOKEN evaluate
const TOKEN_ESCAPE_SINGLE_TICK = 's';
const TOKEN_ESCAPE_DOUBLE_TICK = 'd';
......
......@@ -226,12 +226,13 @@ class QuickFormQuery {
}
$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);
}
$formAction = new FormAction($this->formSpec, $this->db, $this->phpUnit);
$this->store->fillStoreTableDefaultColumnType($this->formSpec[F_TABLE_NAME]);
switch ($this->formSpec['render']) {
......@@ -248,10 +249,10 @@ class QuickFormQuery {
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) {
case FORM_LOAD:
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);
$data = $build->process($mode);
......@@ -284,7 +285,7 @@ class QuickFormQuery {
$sipArray = $this->store->getStore(STORE_SIP);
if ($sipArray[SIP_RECORD_ID] == 0) {
// After insert: a new SIP for the new record id is required.
$this->newRecordCreateSip($sipArray, $rc);
$this->newRecordUpdateSip($rc);
$htmlElementNameIdZero = true;
}
......@@ -536,37 +537,15 @@ class QuickFormQuery {
* @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);
private function newRecordUpdateSip($recordId) {
// Update current SIP store with new RecordID
$sipArray = $this->store->getStore(STORE_SIP);
$sipArray[SIP_RECORD_ID] = $recordId;
$this->store->setVarArray($sipArray, STORE_SIP, true);
// 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);
// Update SIP urlparam
store::getSipInstance()->updateSipToSession($sipArray);
// Overwrite SIP Store
$tmpParam[SIP_SIP] = $sip;
$this->store->setVarArray($tmpParam, STORE_SIP, true);
}
/**
......@@ -672,4 +651,41 @@ class QuickFormQuery {
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 {
}
}
// 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.
foreach ($this->feSpecNative AS $formElement) {
......@@ -118,7 +123,7 @@ class FillStoreForm {
$formElement = $this->evaluate->parseArray($formElement, $debugStack);
// 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
$formElement = Support::setFeDefaults($formElement);
......
......@@ -61,7 +61,7 @@ class Session {
if ($feUidLoggedIn !== $feUserUidSession) {
// destroy existing session store
Session::clear();
Session::clearAll();
// save new feUserUid, feUserName
Session::set(SESSION_FE_USER_UID, $feUidLoggedIn);
......@@ -98,7 +98,7 @@ class Session {
/**
*
*/
public static function clear() {
public static function clearAll() {
if (self::$phpUnit) {
self::$sessionLocal = array();
......@@ -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
* @return Session class
......
......@@ -67,11 +67,8 @@ class Sip {
// Split parameter between Script, Client and SIP
$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[]
$sipParamString = OnArray::toString($sipArray);
$sipParamString = $this->buildParamStringFromArray($sipArray);
$sessionParamSip = Session::get($sipParamString);
if ($sessionParamSip === false) {
......@@ -184,6 +181,32 @@ class Sip {
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
*
......@@ -201,6 +224,25 @@ class Sip {
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]
*
......
......@@ -551,6 +551,10 @@ class Store {
}
$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
$tmpUrlparam = OnArray::toString($tmpParam);
......
......@@ -62,7 +62,7 @@ class SessionTest extends \PHPUnit_Framework_TestCase {
// write/read data1
Session::set('var1', 'data1');
Session::clear();
Session::clearAll();
$val = Session::get('var1');
$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