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

#3899 / Copy/Paste

Manual.rst: various topics undocumented.
DatabaseUpdate.php: New table Clipboard, New FE.type='paste', New Form.forwardMode='url-sip' - will be applied for 0.18.3.
FormAction.php: New: doAllFormElementPaste(), prepareDuplicate(), checkNCopyFiles(), copyRecord()
Store.php: New member in STORE_CLIENT 'CLIENT_COOKIE_QFQ' - might be used to identify current user.
BuildFormBootstrap.php: New buildButtonCopyForm().
QuickFormQuery.php: Calculating the target page now happens after saving the current record and processing all after save actions. New: pasteClipboard()
formEditor.sql: New form 'copyForm'. New table 'Clipboard'
parent fff4393a
......@@ -848,7 +848,7 @@ Only variables that are known in a specified store can be substituted.
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| B | :ref:`STORE_BEFORE`: Record - the current record loaded in the form before any update | All columns of the current record from the current table |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| P | Parent record. E.g.: on multi forms the current record of the outer query | All columns of the MultiSQL Statement from the table for the current row |
| P | Parent record. E.g.: on multi & copy forms the current record of the outer query, | All columns of the MultiSQL Statement from the table for the current row |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| D | Default values column : The *table.column* specified *default value*. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
......@@ -2938,6 +2938,89 @@ the following (switch off all non named):
* open and close note tag: `note`, `/note`,
* close row tag: `/row` ,
Copy Form
^^^^^^^^^
Records (=master) and child records can be duplicated (=copy) by a regular `Form`, extended by `FormElemens` of type 'paste'.
A 'copy form' works either in:
* 'copy and paste now' mode: the 'select' and 'paste' `Form` is merged in one form, only one master record is possible,
* 'copy now, paste later' mode: the 'select' `Form` selects master record(s), the 'paste' Form paste's them later.
Concept
'''''''
A 'select action' (e.g. a `Form` or a button click) creates record(s) in the table `Clipboard`. Each clipboard record contains:
* master record id(s) of the record(s) to duplicate,
* the 'paste' form id (that `Form` defines, to which table the master records belongs to, as well as rules of how to
duplicate any slave records)
* user identifier (QFQ cookie) to separate clipboard records of different users.
The 'select action' is responsible to delete old clipboard records of the current user, before new clipboard records are
created.
The 'paste form' iterates over all master record id(s) in the `Clipboard` table. For each master record id, all FormElements
of type `paste` a fired (incl. the creating slave records).
E.g. if there is a basket with different items and you want to duplicate the whole basket including new items, create a
form with the following parameter
* Form
* Name: `copyBasket`
* Table: `Clipboard`
* Show Button: only `close` and `save`
* FormElement 1
* Name: `idSrc`
* Lable: `Source Form`
* Class: `native`
* Type: `select`
* sql1: `{{! SELECT id, title FROM Basket }}`
* FormElement 2
* Name: `myNewName`
* Class: `native`
* Type: `tex`t
* FormElement 3
* Name: `clearClipboard`
* Class: `action`
* Type: `beforeSave`
* Parameter:
* `sqlValidate={{!SELECT f.id FROM Form AS f WHERE f.name LIKE '{{myName:FE:alnumx}}' LIMIT 1}}`
* `expectRecords = 0`
* `messageFail = There is already a form with this name`
* `sqlAfter={{DELETE FROM Clipboard WHERE cookie='{{cookieQfq:C0:alnumx}}' }}`
* FormElement 4
* Name: `updateClipboardRecord`
* Class: `action`
* Type: `afterSave`
* Parameter: `sqlAfter={{UPDATE Clipboard SET cookie='{{cookieQfq:C0:alnumx}}', formIdPaste={{formId:S0}} /* PasteForm */ WHERE id={{id:R}} LIMIT 1 }}`
* FormElement 5
* Name: `basketId`
* Class: `action`
* Type: `paste`
* sql1: `{{!SELECT {{id:P}} AS id, '{{myNewName:FE:allbut}}' AS name}}`
* Parameter: `recordDestinationTable=Basket`
* FormElement 6
* Name: `itemId`
* Class: `action`
* Type: `paste`
* sql1: `{{!SELECT i.id AS id, {{basketId:P}} AS basketId FROM Item AS i WHERE i.basketId={{id:P}} }}`
* Parameter: `recordDestinationTable=Item`
Best practice
-------------
......
......@@ -153,6 +153,36 @@ class BuildFormBootstrap extends AbstractBuildForm {
return Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $element);
}
/**
* Creates a button to open 'CopyForm' with the current form as source.
*
* @return string - the rendered button
*/
private function buildButtonCopyForm() {
// Show copy icon only on form 'form' and only if there is a form loaded (id>0)
if ($this->formSpec[COLUMN_ID] != 1) {
return '';
}
// current loaded form.
$formId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);
$queryStringArray = [
'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
'form' => 'copyForm',
'r' => 0,
'idSrc' => $formId,
];
$queryString = Support::arrayToQueryString($queryStringArray);
$sip = $this->store->getSipInstance();
$url = $sip->queryStringToSip($queryString);
$toolTip = "Duplicate form" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
$status = ($formId == 0) ? 'disabled' : '';
return $this->buildButtonAnchor($url, 'form-view-' . $this->formSpec[F_ID], '', $toolTip, GLYPH_ICON_DUPLICATE, $status, 'btn btn-default navbar-btn');
}
/**
* Creates a link to open current form loaded in FormEditor
*
......@@ -221,6 +251,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
$buttonEditForm = $this->buildViewForm() .
$this->buildShowEditFormElementCheckbox() .
$this->buildButtonCopyForm() .
$this->buildButtonAnchor($url, 'form-edit-button', '', $toolTip, GLYPH_ICON_TOOL, '', 'btn btn-default navbar-btn');
}
......
......@@ -138,7 +138,7 @@ const ERROR_LOG_NOT_WRITABLE = 1045;
const ERROR_UNNOWN_STORE = 1046;
const ERROR_GET_STORE_ZERO = 1047;
const ERROR_SET_STORE_ZERO = 1048;
const ERROR_QFQ_SESSION_MISSING = 1049;
const ERROR_INVALID_OR_MISSING_PARAMETER = 1050;
const ERROR_UNKNOWN_SQL_LOG_MODE = 1051;
const ERROR_FORM_NOT_FOUND = 1052;
......@@ -189,6 +189,7 @@ const ERROR_IO_INVALID_LINK = 1308;
const ERROR_IO_DIR_EXIST_AS_FILE = 1309;
const ERROR_IO_CHDIR = 1310;
const ERROR_IO_CREATE_FILE = 1311;
const ERROR_IO_COPY_FILE = 1312;
//Report
const ERROR_UNKNOWN_LINK_QUALIFIER = 1400;
......@@ -306,6 +307,9 @@ const CLIENT_REQUEST_URI = 'REQUEST_URI';
const CLIENT_SCRIPT_NAME = 'SCRIPT_NAME';
const CLIENT_PHP_SELF = 'PHP_SELF';
// $_COOKIE
const CLIENT_COOKIE_QFQ = 'cookieQfq';
// T3 Bodytext Keywords
const TYPO3_FORM = CLIENT_FORM;
const TYPO3_RECORD_ID = CLIENT_RECORD_ID;
......@@ -625,6 +629,7 @@ const GLYPH_ICON_TOOL = 'glyphicon-wrench';
const GLYPH_ICON_CHECK = 'glyphicon-ok';
const GLYPH_ICON_CLOSE = 'glyphicon-remove';
const GLYPH_ICON_TASKS = 'glyphicon-tasks';
const GLYPH_ICON_DUPLICATE = 'glyphicon-duplicate';
const GLYPH_ICON_VIEW = 'glyphicon-eye-open';
const GLYPH_ICON_FILE = 'glyphicon-file';
......@@ -653,7 +658,11 @@ const F_FORWARD_PAGE = 'forwardPage';
const F_FORWARD_MODE_CLIENT = API_ANSWER_REDIRECT_CLIENT;
const F_FORWARD_MODE_NO = API_ANSWER_REDIRECT_NO;
const F_FORWARD_MODE_PAGE = 'page';
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_SIP = 'url-sip';
// client', 'no', 'url', 'url-skip-history'
const F_FE_DATA_PATTERN_ERROR = 'data-pattern-error';
const F_FE_DATA_REQUIRED_ERROR = 'data-required-error';
......@@ -821,6 +830,8 @@ const FE_INPUT_EXTRA_BUTTON_INFO = 'extraButtonInfo';
const FE_TMP_EXTRA_BUTTON_HTML = '_extraButtonHtml'; // will be filled on the fly during building extrabutton
const FE_CHECKBOX_CHECKED = 'checked';
const FE_CHECKBOX_UNCHECKED = 'unchecked';
const FE_RECORD_DESTINATION_TABLE = 'recordDestinationTable';
const FE_RECORD_SOURCE_TABLE = 'recordSourceTable';
const FE_FLAG_ROW_OPEN_TAG = '_flagRowOpenTag'; // will be automatically computed during Formload: true | false
const FE_FLAG_ROW_CLOSE_TAG = '_flagRowCloseTag'; // will be automatically computed during Formload: true | false
......@@ -851,7 +862,6 @@ const FE_TYPE_EXTRA = 'extra';
const FE_TYPE_SUBRECORD = 'subrecord';
const FE_TYPE_NOTE = 'note';
const FE_TYPE_SENDMAIL = 'sendMail';
const FE_TYPE_BEFORE_LOAD = 'beforeLoad';
const FE_TYPE_BEFORE_SAVE = 'beforeSave';
const FE_TYPE_BEFORE_INSERT = 'beforeInsert';
......@@ -862,6 +872,8 @@ const FE_TYPE_AFTER_SAVE = 'afterSave';
const FE_TYPE_AFTER_INSERT = 'afterInsert';
const FE_TYPE_AFTER_UPDATE = 'afterUpdate';
const FE_TYPE_AFTER_DELETE = 'afterDelete';
const FE_TYPE_SENDMAIL = 'sendMail';
const FE_TYPE_PASTE = 'paste';
const FE_TYPE_TEMPLATE_GROUP = 'templateGroup';
......@@ -924,6 +936,8 @@ const DB_NUM_ROWS = 'numRows';
const DB_AFFECTED_ROWS = 'affectedRows';
const DB_INSERT_ID = 'insertId';
const COLUMN_ID = 'id';
const COLUMN_FIELD = 'Field';
const COLUMN_CREATED = 'created';
const COLUMN_MODIFIED = 'modified';
......
......@@ -166,9 +166,18 @@ class QuickFormQuery {
*/
public function getForwardMode() {
$forwardPage = $this->eval->parse($this->formSpec[F_FORWARD_PAGE]);
if ($this->formSpec[F_FORWARD_MODE] == F_FORWARD_MODE_URL_SIP) {
$forwardPage = store::getSipInstance()->queryStringToSip($forwardPage, RETURN_URL);
// F_FORWARD_MODE_URL_SIP is not defined in API PROTOCOL. At the moment it's only used for 'copyForm'.
// 'copyForm' behaves better if the page is not in history.
// An option for better implenting would be to separate SKIP History from ForwardMode. For API, it can be combined again.
$this->formSpec[F_FORWARD_MODE] = F_FORWARD_MODE_URL_SKIP_HISTORY;
}
return ([
API_REDIRECT => $this->formSpec[F_FORWARD_MODE],
API_REDIRECT_URL => $this->formSpec[F_FORWARD_PAGE]
API_REDIRECT_URL => $forwardPage,
]);
}
......@@ -291,7 +300,6 @@ class QuickFormQuery {
break;
case FORM_UPDATE:
$formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
// data['form-update']=....
$data = $build->process($formMode);
......@@ -331,21 +339,27 @@ class QuickFormQuery {
$this->fillStoreWithRecord($this->formSpec[F_TABLE_NAME], $rc, STORE_RECORD);
}
// $htmlElementNameIdZero = false;
// Action: Paste
$this->pasteClipboard($this->formSpec[F_ID], $formAction);
// Action: Sendmail
$formAction->elements($rc, $this->feSpecAction, FE_TYPE_SENDMAIL);
// $htmlElementNameIdZero = false;
$getJson = true;
// Retrieve current STORE_SIP.
$sipArray = $this->store->getStore(STORE_SIP);
if ($sipArray[SIP_RECORD_ID] == 0) {
if ($this->formSpec[F_FORWARD_MODE] !== F_FORWARD_MODE_PAGE) {
if ($this->formSpec[F_FORWARD_MODE] !== F_FORWARD_MODE_URL &&
$this->formSpec[F_FORWARD_MODE] !== F_FORWARD_MODE_URL_SKIP_HISTORY &&
$this->formSpec[F_FORWARD_MODE] !== F_FORWARD_MODE_URL_SIP
) {
$this->formSpec = $this->buildNSetReloadUrl($this->formSpec, $rc);
}
$getJson = false;
}
// Action: Sendmail
$formAction->elements($rc, $this->feSpecAction, FE_TYPE_SENDMAIL);
if ($getJson) {
// Retrieve FE Values as JSON
// $data['form-update']=...
......@@ -362,9 +376,37 @@ class QuickFormQuery {
// $data['element-update']=...
$data = $this->groupElementUpdateEntries($data);
}
return $data;
}
/**
* Iterate over all Clipboard source records and fire for each all FE.type=paste records.
*
* @param int $formId
* @param FormAction $formAction
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
private function pasteClipboard($formId, FormAction $formAction) {
$cookieQfq = $this->store->getVar(CLIENT_COOKIE_QFQ, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
if ($cookieQfq === false || $cookieQfq == '') {
throw new UserFormException('Qfq Session missing', ERROR_QFQ_SESSION_MISSING);
}
# select clipboard records
$sql = "SELECT c.idSrc as id, c.xId FROM Clipboard AS c WHERE c.cookie='$cookieQfq' AND c.formIdPaste=$formId ORDER BY c.id";
$arrClipboard = $this->db->sql($sql);
// Process clipboard records.
foreach ($arrClipboard AS $srcIdRecord) {
$formAction->doAllFormElementPaste($this->feSpecAction, $this->formSpec[F_TABLE_NAME], $this->formSpec[F_TABLE_NAME], "", $srcIdRecord);
}
} # doClipboard()
/**
* Set F_FORWARD_MODE to F_FORWARD_MODE_PAGE and builds a redirection URL to the current page with the already
* used parameters. Do this by building a new SIP with the new recordId.
......@@ -438,12 +480,18 @@ class QuickFormQuery {
$form = $this->modeCleanFormConfig($mode, $form);
// Save specific elements to be expanded later.
$parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE]);
$form[F_FORWARD_PAGE] = '';
$formSpec = $this->eval->parseArray($form);
HelperFormElement::explodeParameter($formSpec, F_PARAMETER);
$formSpec = $this->syncSystemFormConfig($formSpec);
$formSpec = $this->initForm($formSpec);
$formSpec = array_merge($formSpec, $parseLater);
// Set F_FINAL_DELETE_FORM
$formSpec[F_FINAL_DELETE_FORM] = ($formSpec[F_EXTRA_DELETE_FORM] != '') ? $formSpec[F_EXTRA_DELETE_FORM] : $formSpec[F_NAME];
......
......@@ -483,5 +483,4 @@ class Save {
return false;
}
}
\ No newline at end of file
......@@ -48,6 +48,11 @@ $UPDATE_ARRAY = array(
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM( 'client', 'no', 'url', 'url-skip-history' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'client'",
],
'0.18.3' => [
"CREATE TABLE IF NOT EXISTS `Clipboard` (`id` INT(11) NOT NULL AUTO_INCREMENT, `cookie` VARCHAR(255) NOT NULL DEFAULT '', `formIdPaste` INT(11) NOT NULL DEFAULT '0', `idSrc` INT(11) NOT NULL DEFAULT '0', `xId` INT(11) NOT NULL DEFAULT '0', `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`)) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;",
"ALTER TABLE `FormElement` CHANGE `type` `type` ENUM( 'checkbox', 'date', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text', 'editor', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', 'fieldset', 'pill', 'templateGroup', 'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad', 'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail', 'paste' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'text';",
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM( 'client', 'no', 'url', 'url-skip-history', 'url-sip' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'client';",
],
);
......
......@@ -377,4 +377,189 @@ class FormAction {
return $fe;
}
/**
* Will be called for each master record (clipboard).
* Process all FE.type='paste' for the given master record in clipboard.
* Will store the clipboard in STORE_PARENT.
*
* @param array $feSpecAction - all FE.class='action' - just process 'paste'
* @param string $recordSourceTable - table name from where to copy the source records
* @param string $recordDestinationTable - table name where the records will be duplicated to.
* @param string $sub - on the highest level an empty string. It's a filter, value comes from FE.name, to specify sub-sub copy rules.
* @param array $clipboard
* @throws CodeException
* @throws UserFormException
*/
public function doAllFormElementPaste(array $feSpecAction, $recordSourceTable, $recordDestinationTable, $sub, array $clipboard) {
# process all paste records
foreach ($feSpecAction as $formElement) {
// Set the clipboard as the parent record. Update always the latest created Ids
$this->store->setStore($clipboard, STORE_PARENT_RECORD, true);
// Only process FE elements of types listed in $feTypeList. Skip all other.
if (false === Support::findInSet($formElement[FE_TYPE], FE_TYPE_PASTE) || $formElement[FE_LABEL] != $sub) {
continue;
}
// Preparation for Log, Debug
$this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
$formElement = HelperFormElement::initActionFormElement($formElement);
if (!empty($formElement[FE_RECORD_DESTINATION_TABLE])) {
$recordDestinationTable = $formElement[FE_RECORD_DESTINATION_TABLE];
$recordSourceTable = (empty($formElement[FE_RECORD_SOURCE_TABLE])) ? $recordDestinationTable : $formElement[FE_RECORD_SOURCE_TABLE];
}
$newValues = $this->evaluate->parse($formElement[FE_SQL1]);
# Dupliziere den Record. RC ist die ID des neu erzeugten Records.
$lastInsertId = $this->prepareDuplicate($feSpecAction, $newValues, $recordSourceTable, $recordDestinationTable, $sub, $clipboard, $formElement[FE_NAME]);
# Lege die Record ID im Array ab, damit spaetere 'paste' Records diese entsprechend einsetzen koennen.
# Nur falls ein Name angegeben ist und dieser !='id' ist.
if ($formElement[FE_NAME] !== '' && $formElement[FE_NAME] != COLUMN_ID) {
$clipboard[$formElement[FE_NAME]] = $lastInsertId;
}
}
} # doAllFormElementPaste()
/**
*
*
* @param array $feSpecAction - all FE.class='action' - just process 'paste'
* @param array $updateRecords - array of records: 'id' is the source.id, all other fields will replace source columns.
* @param $recordSourceTable - table name from where to copy the source records
* @param $recordDestinationTable - table name where the records will be duplicated to.
* @param string $sub - on the highest level an empty string. It's a filter, value comes from FE.name, to specify sub-sub copy rules.
* @param array $clipboard -
* @param string $field - name of a column where to save the lastInsertId.
* @return int - lastInsertId
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
private function prepareDuplicate(array $feSpecAction, array $updateRecords, $recordSourceTable, $recordDestinationTable, $sub, array $clipboard, $field) {
// Sometimes there is no query at all.
if (count($updateRecords) == 0) {
return (0);
}
// Iterate (for the given Paste FE) all updateRecords: duplicate each.
$lastInsertId = 0;
foreach ($updateRecords as $newColumns) {
// will be used in sub paste's
// $clipboard["_src_id"] = $newColumns[COLUMN_ID];
$rowSrc = $this->db->sql("SELECT * FROM $recordSourceTable WHERE id=?", ROW_EXPECT_1, [$newColumns[COLUMN_ID]]);
$this->checkNCopyFiles($rowSrc, $newColumns);
foreach ($newColumns as $key => $val) {
$rowSrc[$key] = $val;
}
$lastInsertId = $this->copyRecord($rowSrc, $recordDestinationTable);
$clipboard[$field] = $lastInsertId;
// Set the clipboard as the primary record as long as secondaries are created.
$this->store->setStore($clipboard, STORE_PARENT_RECORD, true);
# Do subqueries
if ($sub == "") {
$this->doAllFormElementPaste($feSpecAction, $recordSourceTable, $recordDestinationTable, $field, $clipboard);
}
}
return $lastInsertId;
} // prepareDuplicate()
/**
* @param array $rowSrc
* @param array $rowDest
* @throws UserFormException
*/
private function checkNCopyFiles(array $rowSrc, array $rowDest) {
foreach ($rowSrc as $key => $val) {
// Skip non 'special file column'.
if (false === strpos($key, COLUMN_PATH_FILE_NAME)) {
continue;
}
// If a/b) the target is empty, c) src & dest is equal, d) src is not a file: there is nothing to copy.
if (empty($rowDest[$key]) || ($val === $rowDest[$key]) || !is_file($val)) {
continue;
}
Support::mkDirParent($rowDest[$key]);
if (!copy($val, $rowDest[$key])) {
throw new UserFormException("Error copy file from [$val] to [" . $rowDest[$key] . "]", ERROR_IO_COPY_FILE);
}
}
}
/**
* Copy $row to $destable.
* Copy only values which have a column in $destTable.
* If there is nothing to copy - Do nothing.
* Columns with name 'id', 'modified' or 'created' are skipped.
*
* @param array $row
* @param string $destTable
* @return int - lastInsertId
* @throws CodeException
* @throws DbException
*/
function copyRecord(array $row, $destTable) {
$keys = array();
$values = array();
$placeholder = array();
$columns = $this->db->sql("SHOW FIELDS FROM " . $destTable);
// Process all columns of destTable
foreach ($columns as $col) {
$key = $col[COLUMN_FIELD];
$val = $row[$key];
switch ($key) {
case COLUMN_ID:
continue 2;
case COLUMN_MODIFIED:
case COLUMN_CREATED:
$keys[] = $key;
$placeholder[] = 'NOW()';
continue 2;
}
if (isset($row[$key])) {
$keys[] = $key;
$values[] = $val;
$placeholder[] = '?';
}
}
// If there is nothing to write: return
if (count($values) == 0) {
return (0);
}
$keyString = implode(',', $keys);
$valueString = implode(',', $placeholder);
$sql = "INSERT INTO $destTable ($keyString) VALUES ($valueString)";
return $this->db->sql($sql, ROW_REGULAR, $values);
} # copyRecord()
}
......@@ -359,6 +359,10 @@ class Store {
$arr = array_merge($arr, $_POST);
}
if(isset($_COOKIE[SESSION_NAME])){
$arr[CLIENT_COOKIE_QFQ] = $_COOKIE[SESSION_NAME];
}
// It's important to merge the SERVER array last: those entries shall overwrite client values.
if (isset($_SERVER)) {
$server = Sanitize::htmlentitiesArr($_SERVER); // $_SERVER values might be compromised.
......
This diff is collapsed.
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