Commit b4347ce9 authored by Carsten  Rose's avatar Carsten Rose

Merge branch 'master' into s9535-verticalText

parents 9f077d91 cdc0df99
Pipeline #3136 passed with stages
in 1 minute and 56 seconds
......@@ -36,10 +36,37 @@ Features
Bug Fixes
^^^^^^^^^
Date: 09.01.2020
Notes
^^^^^
* Deprecated: Form.parameter.mode. Use Form.parameter.formModeGlobal
Features
^^^^^^^^
* #9805 / Form.parameter.activateFirstRequiredTab.
* #9858 / Form.parameter: replace 'mode' by 'formModeGlobal'
* #9860 / SQL function qmore(): change text '...' to '[...]'.
* Update Developer doc for record locking.
* Mockup for error handling.
Bug Fixes
^^^^^^^^^
* #7925 / Error in split PDF file during upload. Fix the cwd error in Logger.
* #9789 / Record lock release to early on 'leave page'. QfqJS: Moved release lock to before unload.
* #9861 / Fix problem with broken sql.log filename.
* #8851 / Revert implementation: LogMode 'modify' vs. 'modifyAll'.
* #9859 / Database Update: check for 'Update specialColumnName needed' breaks new QFQ install.
* #9813 / During QFQ database update, skip errors like 'Error 1060 - Duplicate Column'.
* Manual.rst: Fix various broken table layouts.
Version 19.12.0
---------------
Date: <date>
Date: 17.12.2019
Notes
^^^^^
......@@ -72,7 +99,6 @@ Bug Fixes
* #7974 / TinyMCE: ReadOnly.
* #9424 / modeSql: skip if it starts with '#'.
* #9531 / File Upload Required.
* #9674 / Select Required Dynamic Update.
* #9678 / textarea now trigger DynamicUpdate.
* #9679 / FormModeGlobal: add STORE_USER - system wide readonly.
......@@ -83,6 +109,7 @@ Bug Fixes
* #9733 / Identiy different tabs. Record lock for same tab will always be granted.
* #9734 / Fix 'dirty lock release' - leaving a dirty form without closing, leaves a stale lock record. Added a releaselock() before window.unload. Dirty remove on goBack.
* #9735 / File Delete: no dirty trigger.
* Download / PDF merge: skip leading errors, interpret only 'Could not merge encrypted files'.
* DragAndDrop broken: after refactoring Support.php, the dragAndDrop was broken - missed init of '$store'.
......
......@@ -15,11 +15,16 @@ Neue Versionsnummer
2) Die aktuellen Commits anschauen und wichtige Topics uebernehmen (git log > ~/qfq.log, alles bis zum letzten TAG anschauen):
# complicated: git log | grep -v -e '^commit ' -e '^Author: ' -e '^Date: ' -e '^Merge: ' > /tmp/out; pluma /tmp/out
# Zeigt **alle Commits** an, die seit dem aendern von NewVersion gemacht wurden! Das sollten alle Commits seit der letzten Version sein.
* git log --pretty=%s --after="`stat -c %y doc/NewVersion.md`"
* **All commits since last tag**:
git log $(git describe --tags --abbrev=0)..HEAD --oneline | cut -c9- > /tmp/out; pluma /tmp/out
* All commits since tag 'v19.12.0'
git log v19.12.0..HEAD --oneline
* complicated:
git log | grep -v -e '^commit ' -e '^Author: ' -e '^Date: ' -e '^Merge: ' > /tmp/out; pluma /tmp/out
* **Anpassen**: qfq/extension/Documentation/Release.rst
* Release.rst **verteilen**: make copyReleaseNotes
......@@ -54,12 +59,12 @@ Neue Versionsnummer
* Update the version number in this document (topic 6)
* Commit & Push new version changes to master branch:
New version 19.12.0
New version 20.1.1
6) **New Tag**:
git tag v19.12.0
git push -u origin v19.12.0
git tag v20.1.1
git push -u origin v20.1.1
7) Tickets:
* Schliessen und der QFQ Version zuweisen.
......
......@@ -1319,7 +1319,7 @@ Only in FormElement:
| **auto** | Form | | Only supported for FormElements. Most suitable checktype is dynamically evaluated based |
| | | | on native column definition, the FormElement type, and other info. See below for details. |
+------------------+------+-------+-------------------------------------------------------------------------------------------+
| **email** | Form | Query | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\.[a-zA-Z]{2,} |
| **email** | Form | Query | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\.[a-zA-Z]{2,} |
+------------------+------+-------+-------------------------------------------------------------------------------------------+
| **pattern** | Form | | Compares the value against a regexp. |
+------------------+------+-------+-------------------------------------------------------------------------------------------+
......@@ -1350,15 +1350,15 @@ The following `escape` & `action` types are available:
+=======+==================================================================================================================================+
| c | Config - the escape type configured in `configuration`_. |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| C | Colon ':' will be escaped against \\: |
| C | Colon ':' will be escaped against \\: |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| d | Double ticks " will be escaped against \\\". |
| d | Double ticks " will be escaped against \\\". |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| l | LDAP search filter values: `ldap-escape() <http://php.net/manual/en/function.ldap-escape.php>`_ (LDAP_ESCAPE_FILTER). |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| L | LDAP DN values. `ldap-escape() <http://php.net/manual/en/function.ldap-escape.php>`_ (LDAP_ESCAPE_DN). |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| s | Single ticks ' will be escaped against \\\'. |
| s | Single ticks ' will be escaped against \\\'. |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
| S | Stop replace. If the replaced value contains nested variables, they won't be replaced. |
+-------+----------------------------------------------------------------------------------------------------------------------------------+
......@@ -2559,7 +2559,7 @@ Parameter
| mode | string | The value `readonly` will activate a global readonly mode of the form - the user can't change any data. |
| | | See :ref:`form-mode-global` |
+-----------------------------+--------+----------------------------------------------------------------------------------------------------------+
| activeFirstRequiredTab | digit | 0: off, 1: (default) - with formModeGlobal=requiredOffButMark bring pill to front on save. |
| activateFirstRequiredTab | digit | 0: off, 1: (default) - with formModeGlobal=requiredOffButMark bring pill to front on save. |
| | | See :ref:`form-mode-global` |
+-----------------------------+--------+----------------------------------------------------------------------------------------------------------+
| enterAsSubmit | digit | 0: off, 1: Pressing *enter* in a form means *save* and *close*. Takes default from configuration_. |
......@@ -2732,10 +2732,8 @@ Mode
* On lost focus.
* When the user saves the record.
* After saving the record, by default the first pill with a missing input comes to front. This behaviour can be
switch on/off with `form.parameter.requiredOffButMark=0|1`
* After saving the record, by default the first pill with a missing input comes to front.
* This behaviour can be switch on/off with `form.parameter.activateFirstRequiredTab=0`
Extra
;;;;;
......@@ -3156,7 +3154,7 @@ See also at specific *FormElement* definitions.
+------------------------+--------+ |
| max | s/d/n | *Always use the international format 'yyyy-mm-dd[ hh:mm[:ss]]* |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| processReadOnly | [n] | [0|1] By default FE's with type='readonly' are not processed during 'save'. |
| processReadOnly | string | [0|1] By default FE's with type='readonly' are not processed during 'save'. |
| | | This option forces to process them during 'save' as well. |
+------------------------+--------+----------------------------------------------------------------------------------------------------------+
| retype, | string | See `input-text`_ |
......@@ -6234,11 +6232,11 @@ Link Examples
+-----------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| SELECT "p:form_person|C:green" AS _link | <a href="?form_person"><img alttext="Check" src="typo3conf/ext/qfq/Resources/Public/icons/checked-green.gif"></a> |
+-----------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| SELECT "U:form=Person&r=123|x|D" as _link | <a href="typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234"><span class="glyphicon glyphicon-trash" ></span>"></a> |
| SELECT "U:form=Person&r=123|x|D" as _link | <a href="typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234"><span class="glyphicon glyphicon-trash" ></span>"></a> |
+-----------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| SELECT "U:form=Person&r=123|x|t:Delete" as _link | <a href="typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234">Delete</a> |
| SELECT "U:form=Person&r=123|x|t:Delete" as _link | <a href="typo3conf/ext/qfq/Classes/Api/delete.php?s=badcaffee1234">Delete</a> |
+-----------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| SELECT "s:1|d:full.pdf|M:pdf|p:id=det1&r=12|p:id=det2|F:cv.pdf| | <a href="typo3conf/ext/qfq/Classes/Api/download.php?s=badcaffee1234">Download</a> |
| SELECT "s:1|d:full.pdf|M:pdf|p:id=det1&r=12|p:id=det2|F:cv.pdf| | <a href="typo3conf/ext/qfq/Classes/Api/download.php?s=badcaffee1234">Download</a> |
| t:Download|a:Create complete PDF - please wait" as _link | |
+-----------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| SELECT "y:iatae3Ieem0jeet|t:Password|o:Clipboard|b" AS _link | <button class="btn btn-info" onClick="new QfqNS.Clipboard({text: 'iatae3Ieem0jeet'});" title='Copy to clipboard'>Password</button> |
......
......@@ -22,7 +22,7 @@
Release
=======
Version 19.x.x
Version 20.x.x
--------------
Date: <date>
......@@ -36,6 +36,47 @@ Features
Bug Fixes
^^^^^^^^^
Version 20.1.1
--------------
Date: 13.01.2020
Bug Fixes
^^^^^^^^^
* #7705 / Fix problem with wrong value after save and form update.
* #8587 / A form triggers a save only, if there are real table columns.
Version 20.1.0
--------------
Date: 09.01.2020
Notes
^^^^^
* Deprecated: Form.parameter.mode. Use Form.parameter.formModeGlobal
Features
^^^^^^^^
* #9805 / Form.parameter.activateFirstRequiredTab.
* #9858 / Form.parameter: replace 'mode' by 'formModeGlobal'
* #9860 / SQL function qmore(): change text '...' to '[...]'.
* Update Developer doc for record locking.
* Mockup for error handling.
Bug Fixes
^^^^^^^^^
* #7925 / Error in split PDF file during upload. Fix the cwd error in Logger.
* #9789 / Record lock release to early on 'leave page'. QfqJS: Moved release lock to before unload.
* #9861 / Fix problem with broken sql.log filename.
* #8851 / Revert implementation: LogMode 'modify' vs. 'modifyAll'.
* #9859 / Database Update: check for 'Update specialColumnName needed' breaks new QFQ install.
* #9813 / During QFQ database update, skip errors like 'Error 1060 - Duplicate Column'.
* Manual.rst: Fix various broken table layouts.
Version 19.12.0
---------------
......@@ -72,7 +113,6 @@ Bug Fixes
* #7974 / TinyMCE: ReadOnly.
* #9424 / modeSql: skip if it starts with '#'.
* #9531 / File Upload Required.
* #9674 / Select Required Dynamic Update.
* #9678 / textarea now trigger DynamicUpdate.
* #9679 / FormModeGlobal: add STORE_USER - system wide readonly.
......
......@@ -21,8 +21,8 @@
; you can use in 'conf.py'
project = QFQ - Quick Form Query
version = 19.12
release = 19.12.0
version = 20.1
release = 20.1.1
t3author = Carsten Rose
copyright = since 2017 by the author
......
......@@ -9,6 +9,7 @@
namespace IMATHUZH\Qfq\Core;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Form\Checkbox;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
......@@ -21,7 +22,6 @@ use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Report\Report;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Form\Checkbox;
/**
* Class AbstractBuildForm
......@@ -763,6 +763,10 @@ abstract class AbstractBuildForm {
continue; // skip this FE
}
// #7705. Dirty fix: 'form_save and fresh created tg element can't be updated, cause the HTML id is unknown: skip those.
if ($mode == FORM_SAVE && false != stripos($fe[FE_NAME], '%d')) {
continue; // skip this FE
}
$flagOutput = ($fe[FE_TYPE] !== FE_TYPE_EXTRA); // type='extra' will not displayed and not transmitted to the form.
$debugStack = array();
......@@ -2302,8 +2306,8 @@ abstract class AbstractBuildForm {
];
// Inherit current F_MODE
if ($this->formSpec[F_MODE] != '') {
$queryStringArray[F_MODE_GLOBAL] = $this->formSpec[F_MODE];
if ($this->formSpec[F_MODE_GLOBAL] != '') {
$queryStringArray[F_MODE_GLOBAL] = $this->formSpec[F_MODE_GLOBAL];
}
// In case the subrecord FE is set to 'readonly': subforms will be called with formModeGlobal=readonly
......@@ -3667,9 +3671,7 @@ EOT;
* '{{!SELECT ...' statement, that one will be fired. In case of an non-primary FE, the result array are the
* values for the copies of the specific FE.
*
* Additional the maximum count of all select rows will be determined and returned.
*
* @return int max number of records in FormElement[FE_VALUE] over all FormElements.
* @return int Count of records in FormElement[FE_VALUE] over all FormElements.
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
......
......@@ -586,6 +586,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
public function getFormTag() {
......
......@@ -783,7 +783,6 @@ const RANDOM_LENGTH = 32;
// SQL logging Modes
const SQL_LOG_MODE_ALL = 'all';
const SQL_LOG_MODE_MODIFY = 'modify';
const SQL_LOG_MODE_MODIFY_ALL = 'modifyAll';
const SQL_LOG_MODE_NONE = 'none';
const SQL_LOG_MODE_ERROR = 'error';
......@@ -1011,7 +1010,6 @@ const F_TYPEAHEAD_LDAP_SEARCH = 'typeAheadLdapSearch';
const F_TYPEAHEAD_LDAP_SEARCH_PREFETCH = 'typeAheadLdapSearchPrefetch';
const F_TYPEAHEAD_LDAP_SEARCH_PER_TOKEN = 'typeAheadLdapSearchPerToken';
const F_MODE = 'mode';
const F_MODE_READONLY = 'readonly';
const F_MODE_REQUIRED_OFF = 'requiredOff';
const F_MODE_REQUIRED_OFF_BUT_MARK = 'requiredOffButMark';
......@@ -1046,7 +1044,7 @@ const F_NEW_BUTTON_CLASS = SYSTEM_NEW_BUTTON_CLASS;
const F_NEW_BUTTON_GLYPH_ICON = SYSTEM_NEW_BUTTON_GLYPH_ICON;
const F_ENTER_AS_SUBMIT = SYSTEM_ENTER_AS_SUBMIT;
const F_ACTIVATE_FIRST_REQUIRED_TAB = 'activeFirstRequiredTab';
const F_ACTIVATE_FIRST_REQUIRED_TAB = 'activateFirstRequiredTab';
const F_DRAG_AND_DROP_ORDER_SQL = 'dragAndDropOrderSql';
const F_ORDER_INTERVAL = 'orderInterval';
......@@ -1372,6 +1370,7 @@ const QUERY_TYPE_SELECT = 'type: select,show,describe,explain';
const QUERY_TYPE_INSERT = 'type: insert';
const QUERY_TYPE_UPDATE = 'type: update,replace,delete';
const QUERY_TYPE_CONTROL = 'type: set';
const QUERY_TYPE_FAILED = 'type: query failed';
//Regexp
//const REGEXP_DATE_INT = '^\d{4}-\d{2}-\d{2}$';
......
......@@ -50,7 +50,7 @@ class Database {
/**
* @var array
*/
private $sqlLogModePrio = [SQL_LOG_MODE_NONE => 1, SQL_LOG_MODE_ERROR => 2, SQL_LOG_MODE_MODIFY => 3, SQL_LOG_MODE_MODIFY_ALL => 4, SQL_LOG_MODE_ALL => 5];
private $sqlLogModePrio = [SQL_LOG_MODE_NONE => 1, SQL_LOG_MODE_ERROR => 2, SQL_LOG_MODE_MODIFY => 3, SQL_LOG_MODE_ALL => 4];
private $dbName = '';
......@@ -74,7 +74,7 @@ class Database {
$this->store = Store::getInstance();
$storeSystem = $this->store->getStore(STORE_SYSTEM);
$this->sqlLog = $storeSystem[SYSTEM_SITE_PATH] . '/' . $storeSystem[SYSTEM_SQL_LOG];
$this->sqlLog = $storeSystem[SYSTEM_SQL_LOG];
$dbInit = $storeSystem[SYSTEM_DB_INIT];
$config = $this->getConnectionDetails($dbIndex, $storeSystem);
......@@ -181,6 +181,7 @@ class Database {
* @param string $specificMessage
* @param array $keys
* @param array $stat DB_NUM_ROWS | DB_INSERT_ID | DB_AFFECTED_ROWS
* @param array $skipErrno
*
* @return array|int
* SELECT | SHOW | DESCRIBE | EXPLAIN: see $mode
......@@ -190,7 +191,7 @@ class Database {
* @throws \DbException
* @throws \UserFormException
*/
public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '', array &$keys = array(), array &$stat = array()) {
public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '', array &$keys = array(), array &$stat = array(), array $skipErrno = array()) {
$queryType = '';
$result = array();
$this->closeMysqliStmt();
......@@ -205,7 +206,7 @@ class Database {
$specificMessage .= " ";
}
$count = $this->prepareExecute($sql, $parameterArray, $queryType, $stat, $specificMessage);
$count = $this->prepareExecute($sql, $parameterArray, $queryType, $stat, $specificMessage, $skipErrno);
if ($count === false) {
throw new \DbException($specificMessage . "No idea why this error happens - please take some time and check the problem.", ERROR_DB_GENERIC_CHECK);
......@@ -339,23 +340,27 @@ class Database {
* Returns the number of selected rows (SELECT, SHOW, ..) or the affected rows (UPDATE, INSERT). $stat contains
* appropriate num_rows, insert_id or rows_affected.
*
* In case of an error, throw an exception.
* mysqli error code listed in $skipErrno[] do not throw an error.
*
* @param string $sql SQL statement with prepared statement variable.
* @param array $parameterArray parameter array for prepared statement execution.
* @param string $queryType returns QUERY_TYPE_SELECT | QUERY_TYPE_UPDATE | QUERY_TYPE_INSERT, depending on
* the query.
* @param array $stat DB_NUM_ROWS | DB_INSERT_ID | DB_AFFECTED_ROWS
*
* @param string $specificMessage
* @param array $skipErrno
*
* @return int|mixed
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
*/
private function prepareExecute($sql, array $parameterArray, &$queryType, array &$stat, $specificMessage = '') {
private function prepareExecute($sql, array $parameterArray, &$queryType, array &$stat, $specificMessage = '', array $skipErrno = array()) {
$sqlLogMode = $this->isSqlModify($sql) ? SQL_LOG_MODE_MODIFY : SQL_LOG_MODE_ALL;
$errno = 0;
// Only log a modify type statement here if sqlLogMode is (at least) modifyAll
// If sqlLogMode is modify, log the statement after it has been executed and we know if there are affected rows.
$sqlLogMode = $this->isSqlModify($sql) ? SQL_LOG_MODE_MODIFY_ALL : SQL_LOG_MODE_ALL;
$result = 0;
$stat = array();
$errorMsg[ERROR_MESSAGE_TO_USER] = empty($specificMessage) ? 'SQL error' : $specificMessage;
......@@ -372,34 +377,52 @@ class Database {
$this->dbLog($sqlLogMode, $sql, $parameterArray);
if (false === ($this->mysqli_stmt = $this->mysqli->prepare($sql))) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error;
throw new \DbException(json_encode($errorMsg), ERROR_DB_PREPARE);
if ($skipErrno === array() && false === array_search($this->mysqli->errno, $skipErrno)) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error;
throw new \DbException(json_encode($errorMsg), ERROR_DB_PREPARE);
} else {
$errno = $this->mysqli->errno;
}
}
if (count($parameterArray) > 0) {
if (false === $this->prepareBindParam($parameterArray)) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error;
if ($skipErrno !== array() && false === array_search($this->mysqli->errno, $skipErrno)) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error;
throw new \DbException(json_encode($errorMsg), ERROR_DB_BIND);
throw new \DbException(json_encode($errorMsg), ERROR_DB_BIND);
} else {
$errno = $this->mysqli->errno;
}
}
}
if (false === $this->mysqli_stmt->execute()) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error;
if ($skipErrno !== array() && false === array_search($this->mysqli->errno, $skipErrno)) {
$this->dbLog(SQL_LOG_MODE_ERROR, $sql, $parameterArray);
$errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $this->getSqlHint($sql, $this->mysqli->error);
$errorMsg[ERROR_MESSAGE_OS] = '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error;
throw new \DbException(json_encode($errorMsg), ERROR_DB_EXECUTE);
throw new \DbException(json_encode($errorMsg), ERROR_DB_EXECUTE);
} else {
$errno = $this->mysqli->errno;
}
}
$msg = '';
$count = 0;
$command = strtoupper(explode(' ', $sql, 2)[0]);
if ($errno === 0) {
$command = strtoupper(explode(' ', $sql, 2)[0]);
} else {
$command = 'FAILED';
}
switch ($command) {
case 'SELECT':
case 'SHOW':
......@@ -444,6 +467,12 @@ class Database {
$count = $stat[DB_AFFECTED_ROWS];
$msg = '';
break;
case 'FAILED':
$queryType = QUERY_TYPE_FAILED;
$stat[DB_AFFECTED_ROWS] = 0;
$count = -1;
$msg = '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error;
break;
default:
// Unknown command: treat it as a control command
......@@ -458,18 +487,7 @@ class Database {
$this->store->setVar(SYSTEM_SQL_COUNT, $count, STORE_SYSTEM);
}
// Logfile
$pageContentSqlLogMode = $this->store->getVar(SYSTEM_SQL_LOG_MODE, STORE_SYSTEM);
if ($pageContentSqlLogMode == SQL_LOG_MODE_MODIFY && $sqlLogMode == SQL_LOG_MODE_MODIFY_ALL) {
// sqlLogMode modify: need to log query and query result (if count > 0)
if ($count > 0) {
$this->dbLog(SQL_LOG_MODE_MODIFY, $sql, $parameterArray);
$this->dbLog(SQL_LOG_MODE_MODIFY, $msg);
}
} else {
// Query result
$this->dbLog($sqlLogMode, $msg);
}
$this->dbLog($sqlLogMode, $msg);
return $count;
}
......
......@@ -137,10 +137,6 @@ class DatabaseUpdate {
$versionInfo = $this->getDatabaseVersion();
$old = $versionInfo[QFQ_VERSION_KEY] ?? false;
if (version_compare($old, '19.9.0') === -1) {
$this->updateSpecialColumns();
}
if ($dbUpdate === SYSTEM_DB_UPDATE_ALWAYS || ($dbUpdate === SYSTEM_DB_UPDATE_AUTO && $new != $old)) {
$newFunctionHash = $this->updateSqlFunctions($versionInfo[QFQ_VERSION_KEY_FUNCTION_HASH] ?? '');
......@@ -164,6 +160,11 @@ class DatabaseUpdate {
// A complete new installation get's some extra tables
$this->db->playSqlFile(__DIR__ . '/../../Sql/customTable.sql');
}
if (version_compare($old, '19.9.0') === -1) {
$this->updateSpecialColumns();
}
}
/**
......@@ -373,6 +374,9 @@ class DatabaseUpdate {
* @throws \UserFormException
*/
private function dbUpdateStatements($old, $new) {
$dummy = array();
if ($new == '' || $old === false || $old === null) {
return;
}
......@@ -391,7 +395,9 @@ class DatabaseUpdate {
if ($apply) {
// Play Statements
foreach ($sqlStatements as $sql) {
$this->db->sql($sql, ROW_REGULAR, array(), "Apply updates to QFQ database. Installed version: $old. New QFQ version: $new");
$this->db->sql($sql, ROW_REGULAR, array(),
"Apply updates to QFQ database. Installed version: $old. New QFQ version: $new",
$dummy, $dummy, [1060] /* duplicate column name */);
}
// Remember already applied updates - in case something breaks and the update has to be repeated.
$this->setDatabaseVersion($new);
......
......@@ -855,8 +855,8 @@ class Support {
$formElement[FE_MODE] = $formElement[FE_MODE_SQL];
}
if (isset($formSpec[F_MODE])) {
$formElement[FE_MODE] = self::applyFormModeToFormElement($formElement[FE_MODE], $formSpec[F_MODE]);
if (isset($formSpec[F_MODE_GLOBAL])) {
$formElement[FE_MODE] = self::applyFormModeToFormElement($formElement[FE_MODE], $formSpec[F_MODE_GLOBAL]);
}
// set typeAheadPedantic
......
......@@ -448,7 +448,7 @@ class QuickFormQuery {
$recordDirty = array();
$rcLockFound = $dirty->getCheckDirty($this->formSpec[F_TABLE_NAME], $recordId, $recordDirty, $msg);
if (($rcLockFound == LOCK_FOUND_CONFLICT || $rcLockFound == LOCK_FOUND_OWNER) && $recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE) {
$this->formSpec[F_MODE] = F_MODE_READONLY;
$this->formSpec[F_MODE_GLOBAL] = F_MODE_READONLY;
}
}
......@@ -1435,7 +1435,6 @@ class QuickFormQuery {
Support::setIfNotSet($formSpec, F_SUBMIT_BUTTON_TEXT, '');
Support::setIfNotSet($formSpec, F_BUTTON_ON_CHANGE_CLASS, '');
Support::setIfNotSet($formSpec, F_LDAP_USE_BIND_CREDENTIALS, '');
Support::setIfNotSet($formSpec, F_MODE, '');
Support::setIfNotSet($formSpec, F_DB_INDEX, $this->store->getVar(F_DB_INDEX, STORE_SYSTEM));
Support::setIfNotSet($formSpec, F_ENTER_AS_SUBMIT, $this->store->getVar(SYSTEM_ENTER_AS_SUBMIT, STORE_SYSTEM));
Support::setIfNotSet($formSpec, F_SESSION_TIMEOUT_SECONDS, $this->store->getVar(SYSTEM_SESSION_TIMEOUT_SECONDS, STORE_SYSTEM));
......@@ -1453,10 +1452,18 @@ class QuickFormQuery {
// }
// }
//
$formSpec[F_MODE] = Support::getFormModeGlobal($formSpec[F_MODE]);
// Check for deprecated legacy code
if (isset($formSpec['mode'])) {
throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Outdated form definition',
ERROR_MESSAGE_TO_DEVELOPER => "form.parameter.mode is deprecated. Please use form.parameter.formModeGlobal instead."
]));
}
// Unify F_MODE_GLOBAL
$formSpec[F_MODE_GLOBAL] = Support::getFormModeGlobal($formSpec[F_MODE_GLOBAL] ?? '');
if ($formSpec[F_MODE] == F_MODE_READONLY) {
if ($formSpec[F_MODE_GLOBAL] == F_MODE_READONLY) {
$formSpec[F_SHOW_BUTTON] = FORM_BUTTON_CLOSE;
$formSpec[F_SUBMIT_BUTTON_TEXT] = '';
}
......
......@@ -171,7 +171,9 @@ class Save {
break;
}
$feName = $formElement[FE_NAME];
if (!isset($formValues[$feName]) && $this->isMemberOfTemplateGroup($formElement)) {
// #7705. Skip FE, which are not already expanded. Detect them by '%' (== '%d')
if (!isset($formValues[$feName]) && false === stripos($feName, '%d') && $this->isMemberOfTemplateGroup($formElement)) {
$formValues[$feName] = $formElement[FE_VALUE];
}
}
......@@ -232,7 +234,7 @@ class Save {
*
* @param $recordId
*
* @return int record id (in case of insert, it's different from $recordId)
* @return int record id (in case of insert, it's different from $recordId)
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
......@@ -241,6 +243,7 @@ class Save {
private function elements($recordId) {
$columnCreated = false;
$columnModified = false;
$realColumnFound = false;
$newValues = array();
......@@ -258,6 +261,7 @@ class Save {
// Skip Upload Elements: those will be processed later.
if ($this->isColumnUploadField($column)) {
$realColumnFound = true;
continue;
}
......@@ -283,29 +287,31 @@ class Save {
Support::setIfNotSet($formValues, $column);
}
$newValues[$column] = $formValues[$column];
$realColumnFound = true;
}
if ($columnModified && !empty($newValues) && !isset($newValues[COLUMN_MODIFIED])) {
$newValues[COLUMN_MODIFIED] = date('YmdHis');
}
// Only save record if real columns exist.
if ($realColumnFound) {
if ($recordId == 0) {
if ($columnCreated && !isset($newValues[COLUMN_CREATED])) {
$newValues[COLUMN_CREATED] = date('YmdHis');
if ($columnModified && !isset($newValues[COLUMN_MODIFIED])) {
$newValues[COLUMN_MODIFIED] = date('YmdHis');
}
$rc = $this->insertRecord($this->formSpec[F_TABLE_NAME], $newValues);
} else {
if (!empty($newValues)) {
if ($recordId == 0) {
if ($columnCreated && !isset($newValues[COLUMN_CREATED])) {
$newValues[COLUMN_CREATED] = date('YmdHis');
}
$recordId = $this->insertRecord($this->formSpec[F_TABLE_NAME], $newValues);
} else {
$this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]);
}
$rc = $recordId;
}
$this->nativeDoSlave($rc);
$this->nativeDoSlave($recordId);
return $rc;
return $recordId;
}
/**
......
......@@ -235,7 +235,7 @@ class FillStoreForm {
$clientFieldName = ($formMode == FORM_REST) ? $formElement[FE_NAME] : HelperFormElement::buildFormElementName($formElement, $fakeRecordId);