Commit 606a6b2a authored by Carsten  Rose's avatar Carsten Rose
Browse files

formDirty: version not tested, definitely broken

parent feabaa01
...@@ -12,6 +12,11 @@ Alice -> dirty: action=lock ...@@ -12,6 +12,11 @@ Alice -> dirty: action=lock
activate dirty #FFBBBB activate dirty #FFBBBB
dirty -> Alice: status=success, lock_timeout=<secs> dirty -> Alice: status=success, lock_timeout=<secs>
... ...
Alice -> Alice: edit (more changes)
note over Alice: Client will extend lock automatically,\nif there are changes during last timeout period
Alice -> dirty: action=extend
dirty -> Alice: status=success
...
Alice -> save: POST form Alice -> save: POST form
save -> dirty: lock valid? save -> dirty: lock valid?
dirty -> save: yes dirty -> save: yes
...@@ -39,17 +44,50 @@ note right: User Bob can edit and save ...@@ -39,17 +44,50 @@ note right: User Bob can edit and save
Bob -> save: Post form Bob -> save: Post form
save -> dirty: lock valid? save -> dirty: lock valid?
dirty -> save: no dirty -> save: no
save -> save: save form save -> save: store 'tokenForce=<uniqid>' in lock
save -> dirty: action=release save -> Bob: status=**conflict_allow_force**,\nmessage=Record locked by user Alice,\ntokenForce=<uniqid>
dirty->dirty: lock for Alice can't be released by Bob Bob -> Bob: UI question 'continue?'
save -> Bob: status=succes,redirect=client
alt Bob: force save
Bob -> save: Post form, GET Parameter 'tokenForce=<uniqid>'
save -> save: uniqId valid?
alt yes
save -> save: save form
save -> Bob: status=**succes**,redirect=client
else no
save -> Bob: status=**conflict_allow_force**,\nmessage=Record locked by user Alice,\ntokenForce=<uniqid>
save -> save: store 'tokenForce=<uniqid>' in lock
note over save: old <uniqid> will be overwritten
end
else Bob: cancel save
Bob -> Bob: none
end
note over save: no release: Bob does not own the lock
... ...
Alice -> save: POST form Alice -> save: POST form
save -> dirty: lock valid? save -> dirty: lock valid? yes!
dirty -> save: yes dirty -> dirty: record modified since lock?
save -> save: save form alt yes
save -> dirty: action=release save -> Alice: status=**conflict_allow_force**,\nmessage=Record has been modified,\ntokenForce=<uniqid>
deactivate dirty ...
save -> Alice: status=succes,redirect=client Alice -> save: POST form, GET Parameter 'tokenForce=<uniqid>'
save -> save: uniqId valid?
alt yes
save -> save: save form
save -> dirty: action=release
save -> Alice: status=**succes**,redirect=client
else no
save -> Alice: status=**conflict_allow_force**,\nmessage=Record locked,\ntokenForce=<uniqid>
save -> save: store 'tokenForce=<uniqid>' in lock
note over save: old <uniqid> will be overwritten
end
else
save -> save: save form
save -> dirty: action=release
deactivate dirty
save -> Alice: status=succes,redirect=client
end
@enduml @enduml
\ No newline at end of file
...@@ -51,7 +51,7 @@ Alice -> save: POST form ...@@ -51,7 +51,7 @@ Alice -> save: POST form
save -> dirty: lock valid? save -> dirty: lock valid?
dirty -> save: no dirty -> save: no
save -> Alice: status=**conflict**,redirect=no save -> Alice: status=**conflict**,redirect=no
note left: Save Button becomes disabled note over Alice: Save Button becomes disabled
... ...
Bob -> save: POST form Bob -> save: POST form
save -> dirty: lock valid? save -> dirty: lock valid?
...@@ -60,6 +60,8 @@ save -> save: save form ...@@ -60,6 +60,8 @@ save -> save: save form
save -> dirty: action=release save -> dirty: action=release
deactivate dirty deactivate dirty
save -> Bob: status=**succes**,redirect=client save -> Bob: status=**succes**,redirect=client
...
note over Alice: If Alice want's 'edit & save'\nAlice has to reload page\nto enable save button again.
@enduml @enduml
\ No newline at end of file
...@@ -257,7 +257,6 @@ const ERROR_SUBSTITUTE_FOUND = 2100; ...@@ -257,7 +257,6 @@ const ERROR_SUBSTITUTE_FOUND = 2100;
// Dirty // Dirty
const ERROR_MISSING_FORM_IN_SIP = 2200; const ERROR_MISSING_FORM_IN_SIP = 2200;
const ERROR_DELETE_DIRTY_RECORD = 2201; const ERROR_DELETE_DIRTY_RECORD = 2201;
// //
// Store Names: Identifier // Store Names: Identifier
// //
...@@ -688,9 +687,10 @@ const F_FORWARD_MODE_NO = API_ANSWER_REDIRECT_NO; ...@@ -688,9 +687,10 @@ const F_FORWARD_MODE_NO = API_ANSWER_REDIRECT_NO;
const F_FORWARD_MODE_URL = API_ANSWER_REDIRECT_URL; const F_FORWARD_MODE_URL = API_ANSWER_REDIRECT_URL;
const F_FORWARD_MODE_URL_SKIP_HISTORY = API_ANSWER_REDIRECT_URL_SKIP_HISTORY; const F_FORWARD_MODE_URL_SKIP_HISTORY = API_ANSWER_REDIRECT_URL_SKIP_HISTORY;
const F_FORWARD_MODE_URL_SIP = 'url-sip'; const F_FORWARD_MODE_URL_SIP = 'url-sip';
// client', 'no', 'url', 'url-skip-history' // client', 'no', 'url', 'url-skip-history'
const F_RECORD_LOCK_TIMEOUT_SECONDS = 'recordLockTimeoutSeconds';
const F_FE_DATA_PATTERN_ERROR = 'data-pattern-error'; const F_FE_DATA_PATTERN_ERROR = 'data-pattern-error';
const F_FE_DATA_REQUIRED_ERROR = 'data-required-error'; const F_FE_DATA_REQUIRED_ERROR = 'data-required-error';
const F_FE_DATA_MATCH_ERROR = 'data-match-error'; const F_FE_DATA_MATCH_ERROR = 'data-match-error';
...@@ -1101,4 +1101,8 @@ const DIRTY_MODE_ADVISORY = 'advisory'; ...@@ -1101,4 +1101,8 @@ const DIRTY_MODE_ADVISORY = 'advisory';
const DIRTY_MODE_NONE = 'none'; const DIRTY_MODE_NONE = 'none';
const DIRTY_QFQ_USER_SESSION_COOKIE = 'qfqUserSessionCookie'; const DIRTY_QFQ_USER_SESSION_COOKIE = 'qfqUserSessionCookie';
const DIRTY_FE_USER= 'feUser'; const DIRTY_FE_USER= 'feUser';
const DIRTY_REMOTE_ADDRESS = 'remoteAddress'; const DIRTY_REMOTE_ADDRESS = 'remoteAddress';
\ No newline at end of file const DIRTY_API_ACTION = 'action'; // Name of parameter in API call of dirty.php?action=...&s=...
const DIRTY_API_ACTION_LOCK = 'lock';
const DIRTY_API_ACTION_RELEASE = 'release';
const DIRTY_API_ACTION_EXTEND = 'extend';
...@@ -756,6 +756,8 @@ class QuickFormQuery { ...@@ -756,6 +756,8 @@ class QuickFormQuery {
F_NEW_BUTTON_CLASS, F_NEW_BUTTON_CLASS,
F_NEW_BUTTON_GLYPH_ICON, F_NEW_BUTTON_GLYPH_ICON,
F_RECORD_LOCK_TIMEOUT_SECONDS,
]; ];
// By definition: existing vars which are empty, means: EMPTY - do not use any default! // By definition: existing vars which are empty, means: EMPTY - do not use any default!
......
...@@ -55,6 +55,7 @@ $UPDATE_ARRAY = array( ...@@ -55,6 +55,7 @@ $UPDATE_ARRAY = array(
'0.19.0' => [ '0.19.0' => [
"ALTER TABLE `Form` ADD `dirtyMode` ENUM( 'exclusive', 'advisory', 'none' ) NOT NULL DEFAULT 'exclusive' AFTER `requiredParameter`", "ALTER TABLE `Form` ADD `dirtyMode` ENUM( 'exclusive', 'advisory', 'none' ) NOT NULL DEFAULT 'exclusive' AFTER `requiredParameter`",
"ALTER TABLE `Form` ADD `recordLockTimeoutSeconds` INT NOT NULL DEFAULT '0' AFTER `parameter`"
], ],
); );
......
...@@ -10,6 +10,7 @@ namespace qfq; ...@@ -10,6 +10,7 @@ namespace qfq;
use qfq; use qfq;
require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../store/Session.php'); require_once(__DIR__ . '/../store/Session.php');
require_once(__DIR__ . '/../Constants.php'); require_once(__DIR__ . '/../Constants.php');
require_once(__DIR__ . '/../database/Database.php'); require_once(__DIR__ . '/../database/Database.php');
...@@ -18,6 +19,11 @@ require_once(__DIR__ . '/../../qfq/store/Client.php'); ...@@ -18,6 +19,11 @@ require_once(__DIR__ . '/../../qfq/store/Client.php');
class Dirty { class Dirty {
/**
* @var Store
*/
private $store = null;
/** /**
* @var Database instantiated class * @var Database instantiated class
*/ */
...@@ -41,7 +47,7 @@ class Dirty { ...@@ -41,7 +47,7 @@ class Dirty {
$this->session = Session::getInstance($phpUnit); $this->session = Session::getInstance($phpUnit);
$this->client = Client::getParam(); $this->client = Client::getParam();
$this->db = new Database(); $this->db = new Database();
$this->store = Store::getInstance('', $phpUnit);
} }
/** /**
...@@ -55,20 +61,26 @@ class Dirty { ...@@ -55,20 +61,26 @@ class Dirty {
$sipClass = new Sip(); $sipClass = new Sip();
$sipVars = $sipClass->getVarsFromSip($this->client[SIP_SIP]); $sipVars = $sipClass->getVarsFromSip($this->client[SIP_SIP]);
$action = $this->client[DIRTY_API_ACTION];
return $this->acquireDirty($sipVars); return $this->processDirty($sipVars, $action);
} }
/** /**
* Tries to get a 'DirtyRecord'. Returns an array about success or failure. * Depending of $action:
* DIRTY_API_ACTION_LOCK: tries to get a 'DirtyRecord'.
* DIRTY_API_ACTION_RELEASE: release a 'DirtyRecord'.
* DIRTY_API_ACTION_EXTEND: extend a 'DirtyRecord'
* Returns always an array about success or failure.
* *
* @param array $sipVars * @param array $sipVars
* @param string $action DIRTY_API_ACTION_LOCK | DIRTY_API_ACTION_RELEASE | DIRTY_API_ACTION_EXTEND
* @return array * @return array
* @throws CodeException * @throws CodeException
* @throws DbException * @throws DbException
*/ */
private function acquireDirty(array $sipVars) { private function processDirty(array $sipVars, $action) {
$answer = array(); $answer = array();
...@@ -78,24 +90,99 @@ class Dirty { ...@@ -78,24 +90,99 @@ class Dirty {
$recordId = empty($sipVars[SIP_RECORD_ID]) ? 0 : $sipVars[SIP_RECORD_ID]; $recordId = empty($sipVars[SIP_RECORD_ID]) ? 0 : $sipVars[SIP_RECORD_ID];
// For r=0 (new) , 'dirty' will always succeed. // For r=0 (new) , 'dirty' will always succeed (for all $actions modes).
if ($recordId == 0) { if ($recordId == 0) {
return [API_STATUS => 'success', API_MESSAGE => '']; return [API_STATUS => 'success', API_MESSAGE => ''];
} }
// Get tableName // Get tableName. Take care to check timeout defaults manually.
$tableVars = $this->db->sql("SELECT tableName, dirtyMode FROM Form AS f WHERE f.name=?", ROW_EXPECT_1, [$sipVars[SIP_FORM]], "Form not found: '" . $sipVars[SIP_FORM] . "'"); $tableVars = $this->db->sql("SELECT tableName, dirtyMode, recordLockTimeoutSeconds FROM Form AS f WHERE f.name=?", ROW_EXPECT_1, [$sipVars[SIP_FORM]], "Form not found: '" . $sipVars[SIP_FORM] . "'");
$tableName = $tableVars[F_TABLE_NAME]; $tableName = $tableVars[F_TABLE_NAME];
$formDirtyMode = $tableVars[F_DIRTY_MODE]; $formDirtyMode = $tableVars[F_DIRTY_MODE];
if ($tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS] == '0') {
$recordLockTimeoutSeconds = $this->store->getVar(SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS, STORE_SYSTEM);
} else {
$recordLockTimeoutSeconds = $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS];
}
$feUser = $this->session->get(SESSION_FE_USER); $feUser = $this->session->get(SESSION_FE_USER);
// Look for already existing dirty record. // Look for already existing dirty record.
$recordDirty = $this->getRecordDirty($tableName, $recordId); $recordDirty = $this->getRecordDirty($tableName, $recordId);
switch ($action) {
case DIRTY_API_ACTION_LOCK:
$answer = $this->lockAcquire($recordDirty, $recordId, $tableName, $formDirtyMode, $feUser, $recordLockTimeoutSeconds);
break;
case DIRTY_API_ACTION_RELEASE:
$answer = $this->lockRelease($recordDirty);
break;
case DIRTY_API_ACTION_EXTEND:
$answer = $this->lockExtend($recordDirty, $recordLockTimeoutSeconds, $formDirtyMode);
break;
default:
break;
}
return $answer;
}
private function lockExtend($recordDirty, $recordLockTimeoutSeconds, $formDirtyMode) {
if (count($recordDirty) == 0) {
// No dirty record found: this is not ok
$answer = [API_STATUS => API_ANSWER_STATUS_ERROR, API_MESSAGE => 'No lock found to extend'];
} else {
if ($this->client[CLIENT_COOKIE_QFQ] == $recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE]) {
// Write 'dirty' record
$expire = ($recordLockTimeoutSeconds == 0) ? "'9999-12-31 23:59:59'" : "DATE_ADD(NOW(), INTERVAL $recordLockTimeoutSeconds SECOND)";
$rc = $this->db->sql("UPDATE Dirty SET `expire`= $expire WHERE id=? LIMIT 1", [$recordDirty[COLUMN_ID]], "Can't delete Dirty record");
if ($rc != 1) {
throw new CodeException("Failed to update a record which have been seen earlier: Dirty.id=" . $recordDirty[COLUMN_ID], ERROR_FAILED_DELETE_DIRTY_RECORD);
}
$answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
} else {
$answer = $this->conflict($recordDirty, $formDirtyMode);
}
}
return $answer;
}
/**
* @param array $recordDirty
* @return array
* @throws CodeException
* @throws DbException
*/
private function lockRelease(array $recordDirty) {
if (count($recordDirty) == 0) {
$answer = [API_STATUS => API_ANSWER_STATUS_ERROR, API_MESSAGE => 'No lock found to release'];
} else {
$this->deleteDirtyRecord($recordDirty[COLUMN_ID]);
$answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
}
return $answer;
}
/**
* @param array $recordDirty
* @param int $recordId
* @param string $tableName
* @param string $formDirtyMode
* @param string $feUser
* @param int $recordLockTimeoutSeconds
* @return array
*/
private function lockAcquire(array $recordDirty, $recordId, $tableName, $formDirtyMode, $feUser, $recordLockTimeoutSeconds) {
if (count($recordDirty) == 0) { if (count($recordDirty) == 0) {
// No dirty record found. // No dirty record found.
$answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableName, $formDirtyMode, $feUser); $answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableName, $formDirtyMode, $feUser, $recordLockTimeoutSeconds);
} else { } else {
$answer = $this->conflict($recordDirty, $formDirtyMode); $answer = $this->conflict($recordDirty, $formDirtyMode);
} }
...@@ -103,6 +190,7 @@ class Dirty { ...@@ -103,6 +190,7 @@ class Dirty {
return $answer; return $answer;
} }
/** /**
* Load (if exist) a DirtyRecord (lock). * Load (if exist) a DirtyRecord (lock).
* *
...@@ -122,6 +210,8 @@ class Dirty { ...@@ -122,6 +210,8 @@ class Dirty {
} }
/** /**
* Checks different conflict stati. Should only be called in case of an conflict.
*
* @param array $recordDirty * @param array $recordDirty
* @param $currentFormDirtyMode * @param $currentFormDirtyMode
* @return array * @return array
...@@ -136,7 +226,7 @@ class Dirty { ...@@ -136,7 +226,7 @@ class Dirty {
$status = API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE; $status = API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE;
} else { } else {
// username if logged in
if (empty($recordDirty[DIRTY_FE_USER])) { if (empty($recordDirty[DIRTY_FE_USER])) {
$msgUser = "another user"; $msgUser = "another user";
} else { } else {
...@@ -149,7 +239,8 @@ class Dirty { ...@@ -149,7 +239,8 @@ class Dirty {
if ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE || $currentFormDirtyMode == DIRTY_MODE_EXCLUSIVE) { if ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE || $currentFormDirtyMode == DIRTY_MODE_EXCLUSIVE) {
$status = API_ANSWER_STATUS_CONFLICT; $status = API_ANSWER_STATUS_CONFLICT;
} else { } else {
$status = API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE; // If mode is '
$status = API_ANSWER_STATUS_SUCCESS;
} }
} }
...@@ -159,16 +250,17 @@ class Dirty { ...@@ -159,16 +250,17 @@ class Dirty {
/** /**
* Write a 'Dirty'-Record. * Write a 'Dirty'-Record.
* *
* @param $s * @param string $s
* @param $recordId * @param int $recordId
* @param $tableName * @param string $tableName
* @param $formDirtyMode * @param string $formDirtyMode
* @param $feUser * @param string $feUser
* @param int $recordLockTimeoutSeconds
* @return array * @return array
* @throws CodeException * @throws CodeException
* @throws DbException * @throws DbException
*/ */
private function writeDirty($s, $recordId, $tableName, $formDirtyMode, $feUser) { private function writeDirty($s, $recordId, $tableName, $formDirtyMode, $feUser, $recordLockTimeoutSeconds) {
$record = $this->db->sql("SELECT * FROM $tableName WHERE id=?", ROW_EXPECT_1, [$recordId], "Record to tag 'dirty' not found."); $record = $this->db->sql("SELECT * FROM $tableName WHERE id=?", ROW_EXPECT_1, [$recordId], "Record to tag 'dirty' not found.");
...@@ -176,8 +268,10 @@ class Dirty { ...@@ -176,8 +268,10 @@ class Dirty {
$recordModified = empty($record[COLUMN_MODIFIED]) ? 0 : $record[COLUMN_MODIFIED]; $recordModified = empty($record[COLUMN_MODIFIED]) ? 0 : $record[COLUMN_MODIFIED];
// Write 'dirty' record // Write 'dirty' record
$this->db->sql("INSERT INTO Dirty (`sip`, `tableName`, `recordId`, `recordModified`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " . $expire = ($recordLockTimeoutSeconds == 0) ? "9999-12-31 23:59:59" : "DATE_ADD(NOW(), INTERVAL $recordLockTimeoutSeconds SECOND)";
"VALUES ( ?,?,?,?,?,?,?,?,? )", ROW_REGULAR, [$s, $tableName, $recordId, $recordModified, $feUser, $this->client[CLIENT_COOKIE_QFQ], $formDirtyMode, $this->client[CLIENT_REMOTE_ADDRESS], date('YmdHis')]);
$this->db->sql("INSERT INTO Dirty (`sip`, `tableName`, `recordId`, `expire`, `recordModified`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " .
"VALUES ( ?,?,?,?,?,?,?,?,?,? )", ROW_REGULAR, [$s, $tableName, $recordId, $expire, $recordModified, $feUser, $this->client[CLIENT_COOKIE_QFQ], $formDirtyMode, $this->client[CLIENT_REMOTE_ADDRESS], date('YmdHis')]);
return [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => '']; return [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
......
...@@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS `Form` ( ...@@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS `Form` (
`escapeTypeDefault` VARCHAR(32) NOT NULL DEFAULT 'c', `escapeTypeDefault` VARCHAR(32) NOT NULL DEFAULT 'c',
`render` ENUM('bootstrap', 'table', 'plain') NOT NULL DEFAULT 'bootstrap', `render` ENUM('bootstrap', 'table', 'plain') NOT NULL DEFAULT 'bootstrap',
`requiredParameter` VARCHAR(255) NOT NULL DEFAULT '', `requiredParameter` VARCHAR(255) NOT NULL DEFAULT '',
`dirtyMode` ENUM('exclusive', 'advisory', 'none') NOT NULL DEFAULT 'exclusive', `dirtyMode` ENUM('exclusive', 'advisory', 'none') NOT NULL DEFAULT 'exclusive',
`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',
`multiSql` TEXT NOT NULL, `multiSql` TEXT NOT NULL,
...@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS `Form` ( ...@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS `Form` (
`bsNoteColumns` VARCHAR(255) NOT NULL DEFAULT '', `bsNoteColumns` VARCHAR(255) NOT NULL DEFAULT '',
`parameter` TEXT NOT NULL, `parameter` TEXT NOT NULL,
`recordLockTimeoutSeconds` INT(11) NOT NULL DEFAULT 0,
`deleted` ENUM('yes', 'no') NOT NULL DEFAULT 'no', `deleted` ENUM('yes', 'no') NOT NULL DEFAULT 'no',
`modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
...@@ -135,6 +136,7 @@ CREATE TABLE IF NOT EXISTS `Dirty` ( ...@@ -135,6 +136,7 @@ CREATE TABLE IF NOT EXISTS `Dirty` (
`sip` VARCHAR(255) NOT NULL, `sip` VARCHAR(255) NOT NULL,
`tableName` VARCHAR(255) NOT NULL, `tableName` VARCHAR(255) NOT NULL,
`recordId` INT(11) NOT NULL, `recordId` INT(11) NOT NULL,
`expire` DATETIME NOT NULL,
`recordModified` DATETIME NOT NULL, `recordModified` DATETIME NOT NULL,
`feUser` VARCHAR(255) NOT NULL, `feUser` VARCHAR(255) NOT NULL,
`qfqUserSessionCookie` VARCHAR(255) NOT NULL, `qfqUserSessionCookie` VARCHAR(255) NOT NULL,
...@@ -198,6 +200,7 @@ VALUES ...@@ -198,6 +200,7 @@ VALUES
'itemList=c:config,s:single,d:double,l:ldap search,L:ldap value,m:mysql realEscapeString,-:none\nbuttonClass=btn-default', 2, '', '', '', 'specialchar', 'no', ''), 'itemList=c:config,s:single,d:double,l:ldap search,L:ldap value,m:mysql realEscapeString,-:none\nbuttonClass=btn-default', 2, '', '', '', 'specialchar', 'no', ''),
(1, 'dirtyMode', 'Record Locking', 'show', 'radio', 'all', 'native', 240, 0, 10, '<a href="{{DOCUMENTATION_QFQ:Y}}#locking-record">Info</a>', '', '', '', (1, 'dirtyMode', 'Record Locking', 'show', 'radio', 'all', 'native', 240, 0, 10, '<a href="{{DOCUMENTATION_QFQ:Y}}#locking-record">Info</a>', '', '', '',
'buttonClass=btn-default', 2, '', '', '', 'specialchar', 'no', ''), 'buttonClass=btn-default', 2, '', '', '', 'specialchar', 'no', ''),
(1, 'recordLockTimeoutSeconds', 'Lock timeout (seconds)', 'show', 'text', 'all', 'native', 245, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-requiredParameter">Info</a>', '', '', '', '', 2, '', '', '', 'specialchar', 'no', ''),
(1, 'showButton', 'Show button', 'show', 'checkbox', 'all', 'native', 250, 0, 5, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-showButton">Info</a>', '', '', '', 'checkBoxMode = multi\norientation=vertical', 2, '', '', '', 'specialchar', 'no', ''), (1, 'showButton', 'Show button', 'show', 'checkbox', 'all', 'native', 250, 0, 5, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-showButton">Info</a>', '', '', '', 'checkBoxMode = multi\norientation=vertical', 2, '', '', '', 'specialchar', 'no', ''),
(1, 'forwardMode', 'Forward', 'show', 'radio', 'all', 'native', 300, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-forward">Info</a>', '', '', '', 'buttonClass=btn-default', 3, '', '', '', 'specialchar', 'yes', ''), (1, 'forwardMode', 'Forward', 'show', 'radio', 'all', 'native', 300, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-forward">Info</a>', '', '', '', 'buttonClass=btn-default', 3, '', '', '', 'specialchar', 'yes', ''),
(1, 'forwardPage', 'Forward URL / Page', 'show', 'text', 'all', 'native', 310, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-forward">Info</a>', '', '', '', '', 3, '', (1, 'forwardPage', 'Forward URL / Page', 'show', 'text', 'all', 'native', 310, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-forward">Info</a>', '', '', '', '', 3, '',
......
...@@ -12,14 +12,14 @@ CREATE TABLE IF NOT EXISTS `Form` ( ...@@ -12,14 +12,14 @@ CREATE TABLE IF NOT EXISTS `Form` (
`escapeTypeDefault` VARCHAR(32) NOT NULL DEFAULT 'c', `escapeTypeDefault` VARCHAR(32) NOT NULL DEFAULT 'c',
`render` ENUM('plain', 'table', 'bootstrap') NOT NULL DEFAULT 'plain', `render` ENUM('plain', 'table', 'bootstrap') NOT NULL DEFAULT 'plain',
`requiredParameter` VARCHAR(255) NOT NULL DEFAULT '', `requiredParameter` VARCHAR(255) NOT NULL DEFAULT '',
`dirtyMode` ENUM('exclusive', 'advisory', 'none') NOT NULL DEFAULT 'exclusive', `dirtyMode` ENUM('exclusive', 'advisory', 'none') NOT NULL DEFAULT 'exclusive',
`showButton` SET('new', 'delete') NOT NULL DEFAULT 'new,delete', `showButton` SET('new', 'delete') NOT NULL DEFAULT 'new,delete',
`multiMode` ENUM('none', 'horizontal', 'vertical') NOT NULL DEFAULT 'none', `multiMode` ENUM('none', 'horizontal', 'vertical') NOT NULL DEFAULT 'none',
`multiSql` TEXT NOT NULL, `multiSql` TEXT NOT NULL,
`multiDetailForm` VARCHAR(255) NOT NULL DEFAULT '', `multiDetailForm` VARCHAR(255) NOT NULL DEFAULT '',
`multiDetailFormParameter` VARCHAR(255) NOT NULL DEFAULT '', `multiDetailFormParameter` VARCHAR(255) NOT NULL DEFAULT '',
`forwardMode` ENUM('client', 'no', 'url', 'url-skip-history') NOT NULL DEFAULT 'client', `forwardMode` ENUM('client', 'no', 'url', 'url-skip-history') NOT NULL DEFAULT 'client',
`forwardPage` VARCHAR(255) NOT NULL DEFAULT '', `forwardPage` VARCHAR(255) NOT NULL DEFAULT '',
`bsLabelColumns` VARCHAR(255) NOT NULL DEFAULT '', `bsLabelColumns` VARCHAR(255) NOT NULL DEFAULT '',
...@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS `Form` ( ...@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS `Form` (
`bsNoteColumns` VARCHAR(255) NOT NULL DEFAULT '', `bsNoteColumns` VARCHAR(255) NOT NULL DEFAULT '',
`parameter` TEXT NOT NULL, `parameter` TEXT NOT NULL,
`recordLockTimeoutSeconds` INT(11) NOT NULL DEFAULT 0,
`deleted` ENUM('yes', 'no') NOT NULL DEFAULT 'no', `deleted` ENUM('yes', 'no') NOT NULL DEFAULT 'no',
`modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
...@@ -134,6 +135,7 @@ CREATE TABLE IF NOT EXISTS `Dirty` ( ...@@ -134,6 +135,7 @@ CREATE TABLE IF NOT EXISTS `Dirty` (
`sip` VARCHAR(255) NOT NULL, `sip` VARCHAR(255) NOT NULL,
`tableName` VARCHAR(255) NOT NULL, `tableName` VARCHAR(255) NOT NULL,
`recordId` INT(11) NOT NULL, `recordId` INT(11) NOT NULL,
`expire` DATETIME NOT NULL,
`recordModified` DATETIME NOT NULL, `recordModified` DATETIME NOT NULL,
`feUser` VARCHAR(255) NOT NULL, `feUser` VARCHAR(255) NOT NULL,
`qfqUserSessionCookie` VARCHAR(255) NOT NULL, `qfqUserSessionCookie` VARCHAR(255) NOT NULL,
......
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