diff --git a/Documentation-develop/CONFIG.md b/Documentation-develop/CONFIG.md
index 27f440c5bbf8d493c87546244f564348c8701404..b71f07f1b81d3839ee5bd16fde6744276de914aa 100644
--- a/Documentation-develop/CONFIG.md
+++ b/Documentation-develop/CONFIG.md
@@ -19,17 +19,109 @@ IMATHUZH\Qfq\Core\Store\Store::fillStoreSystem()
 IMATHUZH\Qfq\Core\Store\Config::getConfigArray()
 IMATHUZH\Qfq\Core\Store\Config::readConfig()
 
-To create a new QFQ Config option:
+How to create a new config option
+==================================
 
-ext_conf_template.txt:
+To create a new config option, you have to make the changes specified below in the following files.
 
-* create new entry
-* set a default value in ext_conf_template.txt
+ext_conf_template.txt
+---------------------
 
-config::setDefaults()
+The following variables must be set:
 
-* Define defaults if nothing is given
+**cat**
+(category where the new option will be located, in the extension configuration of your typo3 backend)
 
-DatabaseUpdate=>checkT3QfqConfig():
+**type** (datatype of the config option)
 
-* In case existing installations should get a new default during QFQ update. 
+possible datatypes:
+* boolean (checkbox)
+* color (colorpicker)
+* int (integer value)
+* int+ (positive integer value)
+* integer (integer value)
+* offset (offset)
+* options (option select)
+```type=options[label1=value1,label2=value2,value3];```
+* small (small text field)
+* string (text field)
+* user (user function)
+```type=user[Vendor\MyExtensionKey\ViewHelpers\MyConfigurationClass->render];```
+* wrap (wrap field)
+
+**label** (title and description of the config option, split by ":")
+
+**myVariable** (name the variable of the config option and assign a default value)
+
+**Example**
+
+```
+# cat=config/config; type=boolean; label=MyLabel:Description
+myVariable = value1
+```
+
+Constants.php
+-------------
+
+Best practice would be defining constants with the name of your variable,
+since this name should never be changed.
+
+```
+const SYSTEM_MY_VARIABLE = 'myVariable';
+const F_MY_VARIABLE = 'SYSTEM_MY_VARIABLE';
+const FE_MY_VARIABLE = 'SYSTEM_MY_VARIABLE';
+```
+
+
+Config.php
+---------------------
+
+In the function **setDefaults()** a default value should be set.
+</br>Important in case of new variables: new variables do not exist in QFQ extension config and do not get the default defined in ext_conf_template.txt
+
+```
+default = [
+    ...
+    SYSTEM_MY_VARIABLE => 'true',
+    ...
+];
+```
+
+Support.php
+----------
+
+To set the default value of a FormElement you can use the **setFeDefaults()** function.
+</br>Wich provides the default value for the FormElement using the **system store**.
+</br>The **system store** contains all the variables defined in the typo3 extension configuration.
+
+```
+self::setIfNotSet($formElement, FE_MY_VARIABLE, $store->getVar(SYSTEM_MY_VARIABLE, STORE_SYSTEM));
+```
+
+StoreTest.php
+-------------
+
+The expected default value must be specified in the **testConfigIniDefaultValues()** function so that the unit test can run without errors.
+
+```
+$expect = [
+    ...
+    SYSTEM_MY_VARIABLE => 'true',
+    ...
+];
+```
+
+How to handle variables
+--------------------------------------
+
+Here is an example on how you would go about handling variables that are defined on all levels (SYSTEM -> Form -> FormElement)
+
+```
+$myVar = $store->getVar(SYSTEM_MY_VARIABLE, STORE_SYSTEM);
+if(isset($this->formSpec[F_MY_VARIABLE])){
+    $myVar = $this->formSpec[F_MY_VARIABLE];
+}
+if(isset($this->formElement[FE_MY_VARIABLE])){
+    $myVar = $this->formElement[FE_MY_VARIABLE];
+}
+```
diff --git a/Documentation/Concept.rst b/Documentation/Concept.rst
index aabbc5431b0340bc0213dc2e2f6b91bb4f16044c..d6d7dbde60ef0247c61951b4a04e9c5a8b63c871 100644
--- a/Documentation/Concept.rst
+++ b/Documentation/Concept.rst
@@ -89,103 +89,100 @@ QFQ Keywords (Bodytext)
 
 **All of these parameters are optional.**
 
-+-------------------------+---------------------------------------------------------------------------------+
-| Name                    | Explanation                                                                     |
-+=========================+=================================================================================+
-| form                    | | Formname.                                                                     |
-|                         | | Static: **form = person**                                                     |
-|                         | | By SIP: **form = {{form:SE}}**                                                |
-|                         | | By SQL: **form = {{SELECT c.form FROM Config AS c WHERE c.id={{a:C}} }}**     |
-+-------------------------+---------------------------------------------------------------------------------+
-| r                       | | <record id>. The form will load the record with the specified id.             |
-|                         | | Static: **r = 123**                                                           |
-|                         | | By SQL: **r = {{SELECT ...}}**                                                |
-|                         | | If not specified, the SIP parameter 'r' is used.                              |
-+-------------------------+---------------------------------------------------------------------------------+
-| dbIndex                 | E.g. `dbIndex = {{indexQfq:Y}}` Select a DB index. Only necessary if a          |
-|                         | different than the standard DB should be used.                                  |
-+-------------------------+---------------------------------------------------------------------------------+
-| debugShowBodyText       | If='1' and :ref:`configuration`:*showDebugInfo: yes*, shows a                   |
-|                         | tooltip with bodytext                                                           |
-+-------------------------+---------------------------------------------------------------------------------+
-| sqlLog                  | Overwrites :ref:`configuration`: :ref:`SQL_LOG` . Only affects `Report`,        |
-|                         | not `Form`.                                                                     |
-+-------------------------+---------------------------------------------------------------------------------+
-| sqlLogMode              | Overwrites :ref:`configuration`: :ref:`SQL_LOG_MODE<SQL_LOG_MODE>` .            |
-|                         | Only affects `Report`, not `Form`.                                              |
-+-------------------------+---------------------------------------------------------------------------------+
-| render                  | See :ref:`report-render`. Overwrites :ref:`configuration`: render.              |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.fbeg            | Start token for every field (=column)                                           |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.fend            | End token for every field (=column)                                             |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.fsep            | Separator token between fields (=columns)                                       |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.fskipwrap       | Skip wrapping (via fbeg, fsep, fend) of named columns. Comma separated list of  |
-|                         | column id's (starting at 1). See also the special column name '_noWrap' to      |
-|                         | suppress wrapping.                                                              |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.shead           | Static start token for whole <level>, independent if records are selected       |
-|                         | Shown before `head`.                                                            |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.stail           | Static end token for whole <level>, independent if records are selected.        |
-|                         | Shown after `tail`.                                                             |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.head            | Dynamic start token for whole <level>. Only if at least one record is select.   |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.tail            | Dynamic end token for whole <level>. Only if at least one record is select.     |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.rbeg            | Start token for row.                                                            |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.rbgd            | Alternating (per row) token.                                                    |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.rend            | End token for row. Will be rendered **before** subsequent levels are processed  |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.renr            | End token for row. Will be rendered **after** subsequent levels are processed   |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.rsep            | Seperator token between rows                                                    |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.sql             | SQL Query                                                                       |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.twig            | Twig Template                                                                   |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.althead         | If <level>.sql has no rows selected (empty), these token will be rendered.      |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.altsql          | If <level>.sql has no rows selected (empty) or affected (delete, update, insert)|
-|                         | the <altsql> will be fired. Note: Sub queries of <level> are not fired, even if |
-|                         | <altsql> selects some rows.                                                     |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.content         | | *show* (default): content of current and sub level are directly shown.        |
-|                         | | *hide*: content of current and sub levels are **stored** and not shown.       |
-|                         | | *hideLevel*: content of current and sub levels are **stored** and only sub    |
-|                         | | levels are shown.                                                             |
-|                         | | *store*: content of current and sub levels are **stored** and shown.          |
-|                         | | To retrieve the content: `{{<level>.line.content}}`.                          |
-|                         | | See :ref:`syntax-of-report`                                                   |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.count      | Current row index. Will be replaced before the query is fired in case of        |
-| <alias>.line.count      | ``<level>``/``<alias>`` is an outer/previous level or it will be replaced after |
-|                         | a query is fired in case ``<level>``/``<alias>`` is the current level.          |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.total      | Total rows (MySQL ``num_rows`` for *SELECT* and *SHOW*, MySQL ``affected_rows`` |
-| <alias>.line.total      | for *UPDATE* and *INSERT*.                                                      |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.insertId   | Last insert id for *INSERT*.                                                    |
-| <alias>.line.insertId   |                                                                                 |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.content    | Show content of `<level>`/`<alias>` (content have to be stored via              |
-| <alias>.line.content    | <level>.content=... or <alias>.content=...).                                    |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.altCount   | Like 'line.count' but for 'alt' query.                                          |
-| <alias>.line.altCount   |                                                                                 |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.altTotal   | Like 'line.total' but for 'alt' query.                                          |
-| <alias>.line.altTotal   |                                                                                 |
-+-------------------------+---------------------------------------------------------------------------------+
-| <level>.line.altInsertId| Like 'line.insertId' but for 'alt' query.                                       |
-| <alias>.line.altInsertId|                                                                                 |
-+-------------------------+---------------------------------------------------------------------------------+
++--------------------------------+---------------------------------------------------------------------------------+
+| Name                           | Explanation                                                                     |
++================================+=================================================================================+
+| form                           | | Formname.                                                                     |
+|                                | | Static: **form = person**                                                     |
+|                                | | By SIP: **form = {{form:SE}}**                                                |
+|                                | | By SQL: **form = {{SELECT c.form FROM Config AS c WHERE c.id={{a:C}} }}**     |
++--------------------------------+---------------------------------------------------------------------------------+
+| r                              | | <record id>. The form will load the record with the specified id.             |
+|                                | | Static: **r = 123**                                                           |
+|                                | | By SQL: **r = {{SELECT ...}}**                                                |
+|                                | | If not specified, the SIP parameter 'r' is used.                              |
++--------------------------------+---------------------------------------------------------------------------------+
+| dbIndex                        | E.g. `dbIndex = {{indexQfq:Y}}` Select a DB index. Only necessary if a          |
+|                                | different than the standard DB should be used.                                  |
++--------------------------------+---------------------------------------------------------------------------------+
+| debugShowBodyText              | If='1' and :ref:`configuration`:*showDebugInfo: yes*, shows a                   |
+|                                | tooltip with bodytext                                                           |
++--------------------------------+---------------------------------------------------------------------------------+
+| sqlLog                         | Overwrites :ref:`configuration`: :ref:`SQL_LOG` . Only affects `Report`,        |
+|                                | not `Form`.                                                                     |
++--------------------------------+---------------------------------------------------------------------------------+
+| sqlLogMode                     | Overwrites :ref:`configuration`: :ref:`SQL_LOG_MODE<SQL_LOG_MODE>` .            |
+|                                | Only affects `Report`, not `Form`.                                              |
++--------------------------------+---------------------------------------------------------------------------------+
+| render                         | See :ref:`report-render`. Overwrites :ref:`configuration`: render.              |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.fbeg                   | Start token for every field (=column)                                           |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.fend                   | End token for every field (=column)                                             |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.fsep                   | Separator token between fields (=columns)                                       |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.fskipwrap              | Skip wrapping (via fbeg, fsep, fend) of named columns. Comma separated list of  |
+|                                | column id's (starting at 1). See also the :ref:`special-column-names` '_noWrap' |
+|                                | to suppress wrapping.                                                           |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.shead                  | Static start token for whole <level>, independent if records are selected       |
+|                                | Shown before `head`.                                                            |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.stail                  | Static end token for whole <level>, independent if records are selected.        |
+|                                | Shown after `tail`.                                                             |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.head                   | Dynamic start token for whole <level>. Only if at least one record is select.   |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.tail                   | Dynamic end token for whole <level>. Only if at least one record is select.     |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.rbeg                   | Start token for row.                                                            |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.rbgd                   | Alternating (per row) token.                                                    |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.rend                   | End token for row. Will be rendered **before** subsequent levels are processed  |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.renr                   | End token for row. Will be rendered **after** subsequent levels are processed   |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.rsep                   | Seperator token between rows                                                    |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.sql                    | SQL Query                                                                       |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.twig                   | Twig Template                                                                   |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.althead                | If <level>.sql has no rows selected (empty), these token will be rendered.      |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.altsql                 | If <level>.sql has no rows selected (empty) or affected (delete, update, insert)|
+|                                | the <altsql> will be fired. Note: Sub queries of <level> are not fired, even if |
+|                                | <altsql> selects some rows.                                                     |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level>.content                | | *show* (default): content of current and sub level are directly shown.        |
+|                                | | *hide*: content of current and sub levels are **stored** and not shown.       |
+|                                | | *hideLevel*: content of current and sub levels are **stored** and only sub    |
+|                                | | levels are shown.                                                             |
+|                                | | *store*: content of current and sub levels are **stored** and shown.          |
+|                                | | To retrieve the content: `{{<level>.line.content}}`.                          |
+|                                | | See :ref:`syntax-of-report`                                                   |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.count       | Current row index. Will be replaced before the query is fired in case of        |
+|                                | ``<level>``/``<alias>`` is an outer/previous level or it will be replaced after |
+|                                | a query is fired in case ``<level>``/``<alias>`` is the current level.          |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.total       | Total rows (MySQL ``num_rows`` for *SELECT* and *SHOW*, MySQL ``affected_rows`` |
+|                                | for *UPDATE* and *INSERT*.                                                      |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.insertId    | Last insert id for *INSERT*.                                                    |
+| <alias>.line.insertId          |                                                                                 |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.content     | Show content of `<level>`/`<alias>` (content have to be stored via              |
+|                                | <level>.content=... or <alias>.content=...).                                    |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.altCount    | Like 'line.count' but for 'alt' query.                                          |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.altTotal    | Like 'line.total' but for 'alt' query.                                          |
++--------------------------------+---------------------------------------------------------------------------------+
+| <level|alias>.line.altInsertId | Like 'line.insertId' but for 'alt' query.                                       |
++--------------------------------+---------------------------------------------------------------------------------+
 
 .. _`report-render`:
 
diff --git a/Documentation/Form.rst b/Documentation/Form.rst
index 23f25081d7dbe32da48a1df36b7b4a450914d4b2..677d91d975dc1ef3710fc0a92d58b9169529c179 100644
--- a/Documentation/Form.rst
+++ b/Documentation/Form.rst
@@ -126,31 +126,41 @@ FormElement processing order:
 
 .. _record_locking:
 
+
 Record locking
 --------------
 
-Forms and 'record delete'-function support basic record locking. A user opens a form: starting with the first change of content, a
-record lock will be acquired in the background. If the lock is denied (e.g. another user already owns a lock on the record) an
-alert is shown.
-By default the record lock mode is 'exclusive' and the default timeout is 15 minutes. Both can be configured per form.
-The general timeout can also be configured in :ref:`configuration` (will be copied to the form during creating the form).
-
-The lock timeout counts from the first change, not from the last modification on the form.
-
-If a timeout expires, the lock becomes invalid. During the next change in a form, the lock is acquired again.
+Support for record locking is given with mode:
 
-A lock is assigned to a record of a table. Multiple forms, with the same primary table, uses the same lock for a given record.
+* *exclusive*: user can't force a write.
 
-If a `Form` acts on further records (e.g. via FE action), those further records are not protected by this basic record locking.
+  * Including a timeout (default 15 mins recordLockTimeoutSeconds in :ref:`configuration`) for maximum lock time.
 
-If a user tries to delete a record and another user already owns a lock on that record, the delete action is denied.
+* *advisory*: user is only warned, but allowed to overwrite.
+* *none*: no bookkeeping about locks.
 
-If there are different locking modes in multiple forms, the most restricting mode applies for the current lock.
+Details:
+
+* For 'new' records (r=0) there is no locking at all.
+* The record locking protection is based on the `tablename` and the `record id`. Different `Forms`, with the same primary table,
+  will be protected by record locking.
+* Action-`FormElements` updating non primary table records are not
+  protected by 'record locking': the QFQ record locking is *NOT 100%*.
+* The 'record locking' mode will be specified per `Form`. If there are multiple Forms with different modes, and there is
+  already a lock for a `tablename` / `record id` pair, the most restrictive will be applied.
+* A user opens a form: starting with the first change of content, a record lock will be acquired in the background. If
+  the lock is denied (e.g. another user already owns a lock on the record) an alert is shown. This means: the lock timeout
+  counts from the first change, not from the last modification on the form.
+* If a timeout expires, the lock becomes invalid. During the next change in a form, the lock is acquired again.
+* A lock is assigned to a record of a table. Multiple forms, with the same primary table, uses the same lock for a given record.
+* If a user tries to delete a record and another user already owns a lock on that record, the delete action is denied.
+* If there are different locking modes in multiple forms, the most restricting mode applies for the current lock.
+* If the same user opens the same recording in different tabs or browsers, the user has the possibility to skip a lock.
 
 Exclusive
 ^^^^^^^^^
 
-An existing lock on a record forbids any write action on that record.
+An existing lock on a record forbids any write action on that record. Exception: locks owned by the same user might be overwritten.
 
 Advisory
 ^^^^^^^^
@@ -3158,30 +3168,6 @@ To automatically delete slave records, use a form and create `beforeDelete` Form
 
 You might also check the form 'form' how the slave records 'FormElement' will be deleted.
 
-.. _locking-record:
-
-Locking Record / Form
----------------------
-
-Support for record locking is given with mode:
-
-* *exclusive*: user can't force a write.
-
-  * Including a timeout (default 15 mins recordLockTimeoutSeconds in :ref:`configuration`) for maximum lock time.
-
-* *advisory*: user is only warned, but allowed to overwrite.
-* *none*: no bookkeeping about locks.
-
-For 'new' records (r=0) there is no locking at all.
-
-The record locking protection is based on the `tablename` and the `record id`. Different `Forms`, with the same primary table,
-will be protected by record locking. On the other side, action-`FormElements` updating non primary table records are not
-protected by 'record locking': the QFQ record locking is *NOT 100%*.
-
-The 'record locking' mode will be specified per `Form`. If there are multiple Forms with different modes, and there is
-already a lock for a `tablename` / `record id` pair, the most restrictive will be applied.
-
-
 Best practice
 -------------
 
diff --git a/Documentation/Release.rst b/Documentation/Release.rst
index 051c77d211869f7dfde85be9cdddbab32f85c284..0203f61e86520496b7c6ce1f6d5a35f4d3d468ba 100644
--- a/Documentation/Release.rst
+++ b/Documentation/Release.rst
@@ -262,6 +262,214 @@ Bug Fixes
 * #15523 / Search/Refactor broken for Multi-DB.
 * #15626 / Multi-DB: FormEditor save error.
 
+Version 23.10.1
+---------------
+
+Date: 22.10.2023
+
+Notes
+^^^^^
+
+Features
+^^^^^^^^
+
+* #15098 / Implemented qfqFunction in QFQ variable for usage in forms.
+* Doc: Replace many places single back tick by double back tick. Add hint for 'Row size too large'.  Added Enis & Jan
+  as Developer. Add hint use mysqldump with one row per record.
+
+Bug Fixes
+^^^^^^^^^
+
+* #17003 / inline edit - dark mode has wrong css path.
+* #17075 / Fix broken '... AS _restClient'.
+* #17091 / upload_Incorrect_integer_value_fileSize.
+* #15795 / Upload: download button not shown after pressing save.
+* RTD: Fix broken readthedocs rendering.
+
+Version 23.10.0
+---------------
+
+Date: 05.10.2023
+
+Features
+^^^^^^^^
+
+* #16350 / QFQ Table 'FormSubmiLog': update recordid after insert.
+* #16350 / sql.log: reference to FormSubmitLog entry. All SQL statements generated by one HTTP Post (Form Submit) can
+  be identified.
+* #16350 / Do not log Dirty.
+* #16350 / If the T3 instance is behind a proxy, log HTTP_X_REAL_IP instead of REMOTE_ADDR in logfiles.
+* #16584 / FormEditor Report: Default without statistics.
+* #16589 / Implemented language configuration in backend for tt-content type qfq.
+* #16798 / Report inline edit v2. Improved search inside inline edit report. Whole content will be searched. Added
+  ability to switch the editor to dark mode.
+* Doc: Refactor description of {{random:V}}.
+* Doc: Add config option 'protectedFolderCheck'.
+
+Bug Fixes
+^^^^^^^^^
+
+* #16573 / Fixed wrong built date and datetime string if default value was given.
+* #16574 / Added multiple siteConfigurations compatibility for typo3 v10 and 11.
+* #16616 / Fixed typeahead api query response problem if typeahead sql is not used.
+* #16664 / Fix multi db user error.
+* #16975 / Fix problem if a 'Form Submit' contains more than 64kB data. This can happen easily for 'fabric' elements.
+
+Version 23.6.4
+--------------
+
+Date: 26.06.2023
+
+Bug Fixes
+^^^^^^^^^
+
+* #16485 / TypeAhead: strpos() string, array given error.
+* #16488 / Missing default values break saving records. New custom FE.parameter.defaultValue.
+* #16491 / FE Typ Upload - JS failure: document.querySelector() is null.
+
+Version 23.6.3
+--------------
+
+Date: 22.06.2023
+
+Bug Fixes
+^^^^^^^^^
+
+* #16478 / Rest API: Fixed stream_get_contents failure in PHP8.1 Generic Error.
+
+Version 23.6.2
+--------------
+
+Date: 21.06.2023
+
+Bug Fixes
+^^^^^^^^^
+
+* #16475 / Spontaneous spaces in HTML emails.
+* #16476 / SQL columns Text/Blog with default empty string becomes "''"
+
+
+Version 23.6.1
+--------------
+
+Date: 16.06.2023
+
+Notes
+^^^^^
+
+* QFQ is Typo3 V11 compatible
+
+Bug Fixes
+^^^^^^^^^
+
+* #16372 / Upload Element 'Undefined index htmlDownloadButton' und 'unknown mode ID'
+* #16381 / Form title dynamic update broken.
+* #16392 / Reevaluate sanitize class for each store.
+* Fix db column enum dropdown 'data truncated'
+* DB Update 'alter index' pre check if exists
+* FE: Upload - fix undefined index.
+
+Version 23.6.0
+--------------
+
+Date: 09.06.2023
+
+Features
+^^^^^^^^
+
+* Typo3 V11 compatible
+* #9579 / Multiform with Process Row.
+* #15770 / httpOrigin: Parameter missing in QFQ Config.
+* #16285 / PHP V8 Migration.
+* #16364 / T3 >=V10 pageSlug and ForwardPage.
+* Add .phpunit.result.cache to .gitignore.
+* Add bullet-orange.gif.
+* Add index createdFeUserFormId to table FormSubmitLog.
+* Doc: Add link to icons. Update use-case self-registration and add info in case of namesake. Unify word 'spam' to 'junk'.
+  Add bootstrap links.
+* Remove Form/FormElement parameter 'feGroup'.
+* QFQ Typo3 Updatewizard: Sort output by page and tt-content ordering. Add page uid an title to be reported.
+  Inline Edit: add tt_content uid and header as tooltip.
+* Update package.json.
+* Update to PHPUnit version 9.
+
+Bug Fixes
+^^^^^^^^^
+
+* #12468 / Form: Dynamic Update Form.title after save.
+* #14636 / 'Clear Me'-cross lighter and 2 px up.
+* #15445 / Doc: JS Output Widged added.
+* #15497 / Delete link: a) broken for 'table', b) broken with 'r:3'.
+* #15527 / Form/subrecord: class 'qfq-table-50' no impact.
+* #15654 / Form FE required 'Undefined index Error'.
+* #15659 / Multi Form: Datetime Picker not aligned.
+* #15691 / T3 V10: Export PDF (PDF Generator) invalid link causes error.
+* #15726 / Formelement upload stays required when changed from required to hidden.
+* #15729 / Form: text readonly input should show newline as '<br>'.
+* #15747 / Typeahead does not work with special character. Added croatian special chars (Ć,ć,Č,č,Đ,đ,Š,š,Ž,ž) to alnumx
+  whitelist.
+* #15763 / Search/Refactor: a) search for ID, b) message if nothing is found - New highlighting for disabled content.
+  Added output for not found in search.
+* #15773 / TypeAhead missing check type warning.
+* #15813 / Pressing 'Enter' in Form asks: Do you really want to delete the record?
+* #15875 / Unecessary qfq-config save.
+* #15905 / FE type time undefined index.
+* #15913 / Refactor: Footer displayed at the wrong place.
+* #15921 / Frontend edit Report Codemirror resizeable.
+* #16004 / Template Group Fields broken.
+* #16046 / Datepicker not initialized in Template Group.
+* #16051 / Dropdown menu: Download file broken with ZIP File.
+* #16055 / Dropdown option with render mode 3 format problem.
+* #16064 / Dynamic Update Checkbox and Form Store in parameter.
+* #16073 / saveButtonActive is disabled after first save.
+* #16201 / Character Count Class Quotation Mark.
+* #16204 / TypeAhead Prefetch Expects String Array Given.
+* #16228 / extraButtonPassword unhide broken.
+* #16264 / Multi DB Broken since QFQ V23.2.0.
+* #16273 / TinyMCE image upload path incorrect for T3 v10 and upwards.
+
+Version 23.3.1
+--------------
+
+Date: 31.03.2023
+
+Bug Fixes
+^^^^^^^^^
+
+* #15920 / QFQ variable evaluation broken: wrong variable name
+
+Version 23.3.0
+--------------
+
+Date: 30.03.2023
+
+Features
+^^^^^^^^
+
+* #15491 / Search refactor redesign and T3 V10 compatiblity: underscore character, counter fixed, page alias and slug handling.
+* #15529 / Form/subrecord: Design Titel / Box.
+* #15570 / Changed DB handling in class FormAction. Fixed multi-db problem with this. Included change for check of
+  existing form editor report.
+* #15579 / Ability for searching all possible characters, includes not replaced QFQ variables.
+* #15627 / Added character count and maxLength feature for TinyMCE.
+* Add various icons to documentation.
+* Doc Form.rst: reference 'orderColumn'.
+* Doc Report.rst: fix typo, add icons, improved example for tablesorter.
+* Add indexes for table FormSubmitLog.
+* FormEditor: Show Tablename in pill 'table definition'.
+* FormEditor: FE subrecord > show container id below name.
+
+Bug Fixes
+^^^^^^^^^
+
+* #14754 / Using double quotes in tableview config caused sql error.
+* #15483 / Protected folder check fixed. Changed default of wget, preventing errors. Changed handling from protected
+  folder check, new once a day.
+* #15521 / FormEditor assigns always container, even none is selected. Change handling of form variables from type select,
+  radio and checkbox. Expected 0 from client request instead of empty string.
+* #15523 / Search/Refactor broken for Multi-DB.
+* #15626 / Multi-DB: FormEditor save error.
+
 Version 23.2.0
 --------------
 
diff --git a/Documentation/Report.rst b/Documentation/Report.rst
index a46b869a0c4d46bde7fc0d035d73a14be55e2c43..9ac07ad5b681bc94767079f34ef71e6206af5903 100644
--- a/Documentation/Report.rst
+++ b/Documentation/Report.rst
@@ -398,8 +398,8 @@ To get the same result, the following is also possible::
                                     '|p:/export',
                                     '|t:Download') AS _pdf
 
-Nesting of levels (report notation `numeric`)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Nesting of levels: `numeric`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Levels can be nested. E.g.::
 
@@ -417,8 +417,8 @@ This is equal to::
     10.5.sql = SELECT ...
     10.5.head = ...
 
-Nesting of levels (report notation `alias`)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Nesting of levels: `alias`
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Levels can be nested without levels. E.g.::
 
@@ -442,7 +442,7 @@ An alias can be used instead of levels. E.g.::
 
   myAlias {
     sql = SELECT ...
-    myAlias2 {
+    nextAlias {
         sql = SELECT ...
         head = ...
     }
@@ -460,12 +460,14 @@ Allowed characters for an alias: [a-zA-Z0-9_-].
 
 .. important::
 
-The first level determines whether report notation `numeric` or `alias` is used. Using an alias or no level triggers report notation `alias`.
-It requires the use of delimiters throughout the report. A combination with the notation '10.sql = ...' is not possible.
+The first level determines whether report notation `numeric` or `alias` is used. Using an alias or no level triggers
+report notation `alias`. It requires the use of delimiters throughout the report. A combination with the notation
+'10.sql = ...' is not possible.
 
 .. important::
 
-Report notation `alias` does not require that each level be assigned an alias. If an alias is used, it must be on the same line as the opening delimiter.
+Report notation `alias` does not require that each level be assigned an alias. If an alias is used, it must be on the
+same line as the opening delimiter.
 
 Leading / trailing spaces
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/extension/Classes/Core/BodytextParser.php b/extension/Classes/Core/BodytextParser.php
index bcaaa1e8ce98e90de0e63473048bd0aea207e448..9e4fd8d50caa681e325ec0b27eb42043dc5dcea0 100644
--- a/extension/Classes/Core/BodytextParser.php
+++ b/extension/Classes/Core/BodytextParser.php
@@ -217,14 +217,14 @@ class BodytextParser {
 
                 // This later determines the notation mode
                 // Possible values for $firstToken: '10', '10.20', '10.sql=...', '10.head=...', 'myAlias {', 'myAlias{'
-                // Values such as 'form={{form:SE}}' are disregarded
+                // Values such as 'form={{form:SE}}' are valid but not parsed as a level/alias.
                 if (empty($firstToken) && 1 !== preg_match('/^(' . TOKEN_VALID_LIST . ')\s*=/', $row)) {
                     $firstToken = (strpos($row, $nestingOpen) !== false) ? trim(substr($row, 0, strpos($row, $nestingOpen))) : $row;
                 }
 
-            // If the open delimiter is missing while using an alias, this is necessary to get the correct error message later on
-            // Starts a new line if the previous line only contained '}'
-            // It prevents that the lines '}' and 'myAlias' will be joined
+                // If the open delimiter is missing while using an alias, this is necessary to get the correct error message later on
+                // Starts a new line if the previous line only contained '}'
+                // It prevents that the lines '}' and 'myAlias' will be joined
             } elseif ($full === $nestingClose) {
                 $data[] = $full;
 
@@ -249,15 +249,15 @@ class BodytextParser {
 
         // Combines line numbers ($key) from tt-content record with content ($value) from corresponding line: [line => content]
         // E.g. [0 => 4, 1 => "[", 2 => "sql = SELECT ...", 3 => "[", 4 => "sql = SELECT ...", ...]: line 0 is empty
-        foreach($reportLines as $key => $value) {
+        foreach ($reportLines as $key => $value) {
             $reportLines[$value] = $data[$key];
         }
 
         // Removes every element that is not an SQL statement: [line => content]
         // E.g. [2 => "sql = SELECT ...", 4 => "sql = SELECT ...", ...]
-        foreach($reportLines as $key => $value) {
+        foreach ($reportLines as $key => $value) {
             if (strpos($value, '=') !== false) {
-                $arr = explode('"', $value,2);
+                $arr = explode('"', $value, 2);
                 if (strpos($arr[0], TOKEN_SQL) === false) {
                     unset($reportLines[$key]);
                 }
@@ -392,7 +392,7 @@ class BodytextParser {
             $index = substr_count($pre, NESTING_TOKEN_OPEN);
 
             // Check for report notation 'alias'
-            if($notationMode === TOKEN_NOTATION_ALIAS) {
+            if ($notationMode === TOKEN_NOTATION_ALIAS) {
                 $aliasLevel = null;
 
                 // $firstToken === $level checks if we are in the 'first loop'
@@ -430,7 +430,7 @@ class BodytextParser {
                 // Removes alias or level added by user to continue auto numbering scheme
                 // E.g. User input: {\nsql = SELECT ...\n}\nmyAlias{\nsql = SELECT ...\n}
                 // $pre = "1.sql = SELECT ...\nmyAlias" -> $pre = "1.sql = SELECT ...\n"
-                $pre = substr($pre,0, strrpos($pre, PHP_EOL) + $adjustLength);
+                $pre = substr($pre, 0, strrpos($pre, PHP_EOL) + $adjustLength);
             } else {
 
                 // Remove 'level' from last line
@@ -462,7 +462,7 @@ class BodytextParser {
                 if (strpos($valueResult, '=')) {
                     $arr = explode("=", $valueResult, 2);
                     if (strpos($arr[0], TOKEN_SQL) !== false) {
-                        $reportLines[$keyLines] = str_replace('.' . TOKEN_SQL , '', trim($arr[0]));
+                        $reportLines[$keyLines] = str_replace('.' . TOKEN_SQL, '', trim($arr[0]));
                     } else {
                         continue;
                     }
diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index a9af77a6fb3026cd0f68dc924ab60a3350b9a999..3e93c4e96b0b10206d46428fad0254eb524be50f 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -1658,7 +1658,7 @@ const COLUMN_UID = 'uid';
 const COLUMN_HEADER = 'header';
 const COLUMN_TITLE = 'title';
 const COLUMN_SUBHEADER = 'subheader';
-
+const COLUMN_EXPIRE = 'expire';
 const INDEX_PHP = 'index.php';
 
 // QuickFormQuery.php
diff --git a/extension/Classes/Core/Form/Dirty.php b/extension/Classes/Core/Form/Dirty.php
index 457190674aee58a4df82eb765fe74724fee7d4da..6c4c42dc6f0eaeac9720e34da74dda66873ea60e 100644
--- a/extension/Classes/Core/Form/Dirty.php
+++ b/extension/Classes/Core/Form/Dirty.php
@@ -10,6 +10,7 @@ namespace IMATHUZH\Qfq\Core\Form;
 
 use IMATHUZH\Qfq\Core\Database\Database;
 use IMATHUZH\Qfq\Core\Helper\OnArray;
+use IMATHUZH\Qfq\Core\Helper\Support;
 use IMATHUZH\Qfq\Core\Store\Client;
 use IMATHUZH\Qfq\Core\Store\Session;
 use IMATHUZH\Qfq\Core\Store\Sip;
@@ -67,7 +68,7 @@ class Dirty {
             $this->client[DIRTY_RECORD_HASH_MD5] = '';
         }
         $this->doDbArray($dbIndexData, $dbIndexQfq);
-
+        $this->store = Store::getInstance();
     }
 
     /**
@@ -119,7 +120,6 @@ class Dirty {
             return [API_STATUS => 'success', API_MESSAGE => ''];
         }
 
-        $this->store = Store::getInstance();
         $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM);
 
         $this->dbIndexData = empty($sipVars[PARAM_DB_INDEX_DATA]) ? $this->store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM) : $sipVars[PARAM_DB_INDEX_DATA];
@@ -180,13 +180,15 @@ class Dirty {
             if ($formDirtyMode == DIRTY_MODE_NONE) {
                 $answer = [API_STATUS => 'success', API_MESSAGE => ''];
             } else {
-                // No dirty record found.
+                // No dirty record found: create lock
                 $answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5, $tabUniqId);
             }
         } else {
             if ($tabUniqId == $recordDirty[TAB_UNIQ_ID]) {
+                // In case it's the same tab (page reload): OK
                 $answer = [API_STATUS => 'success', API_MESSAGE => ''];
             } else {
+                // Here is probably a conflict.
                 $answer = $this->conflict($recordDirty, $formDirtyMode, $primaryKey);
             }
         }
@@ -221,6 +223,7 @@ class Dirty {
     }
 
     /**
+     * Aquire lock conflict detected
      *
      * @param array $recordDirty
      * @param string $currentFormDirtyMode
@@ -233,17 +236,20 @@ class Dirty {
      */
     private function conflict(array $recordDirty, $currentFormDirtyMode, $primaryKey) {
         $status = API_ANSWER_STATUS_CONFLICT;
-        $at = "at " . $recordDirty[COLUMN_CREATED] . " from " . $recordDirty[DIRTY_REMOTE_ADDRESS];
+        $until = "until " . date_format(date_create($recordDirty[COLUMN_EXPIRE]), "d.m.Y H:i:s");
 
-        // Compare modified timestamp
+        // Compare modified timestamp: in case there is a lock conflict and current form is based on outdated data: force reload.
         if ($this->isRecordModified($recordDirty[DIRTY_TABLE_NAME], $primaryKey, $recordDirty[DIRTY_RECORD_ID], $recordDirty[DIRTY_RECORD_HASH_MD5], $dummy)) {
-            return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again. [2]'];
+            return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime (your changes are lost). Please reload the form, edit and save again.'];
         }
 
-        if ($this->client[CLIENT_COOKIE_QFQ] == $recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE]) {
-            $msg = "The record has already been locked by you (maybe in another browser tab) $at!";
-            $status = ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE) ? API_ANSWER_STATUS_CONFLICT : API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE;
+        // Conflict for same user / same QFQ Session: the user can force aquire lock.
+        // Hint: after this, the form with the first lock still thinks it has the lock - that one will get a 'record modified in the meantime' on save.
+        $userMatch = ($recordDirty[DIRTY_FE_USER] != '' && $recordDirty[DIRTY_FE_USER] == $this->store->getVar(TYPO3_FE_USER, STORE_TYPO3));
+        if ($userMatch || $this->client[CLIENT_COOKIE_QFQ] == $recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE]) {
 
+            $msg = "Record already locked (by you)";
+            $status = API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE;
         } else {
 
             if (empty($recordDirty[DIRTY_FE_USER])) {
@@ -252,7 +258,7 @@ class Dirty {
                 $msgUser = "user '" . $recordDirty[DIRTY_FE_USER] . "'";
             }
 
-            $msg = "The record has already been locked by $msgUser at $at.";
+            $msg = "Record already locked by $msgUser $until.";
 
             // Mandatory lock on Record or current Form?
             if ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE || $currentFormDirtyMode == DIRTY_MODE_EXCLUSIVE) {
@@ -292,6 +298,8 @@ class Dirty {
         # Dirty workaround: setting the 'expired timestamp' minus 1 second guarantees that the client ask for relock always if the timeout is expired.
         $expire = date('Y-m-d H:i:s', strtotime("+" . $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS] - 1 . " seconds"));
         // Write 'dirty' record
+
+        $userAgent = $this->store->getVar(CLIENT_HTTP_USER_AGENT, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
         $this->dbArray[$this->dbIndexQfq]->sql("INSERT INTO `Dirty` (`sip`, `tableName`, `recordId`, `expire`, `recordHashMd5`, `tabUniqId`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " .
             "VALUES ( ?,?,?,?,?,?,?,?,?,?,? )", ROW_REGULAR,
             [$s, $tableName, $recordId, $expire, $recordHashMd5, $tabUniqId, $feUser, $this->client[CLIENT_COOKIE_QFQ], $formDirtyMode,
@@ -366,20 +374,18 @@ class Dirty {
             return LOCK_NOT_FOUND;
         }
 
-        if ($recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE] == $this->client[CLIENT_COOKIE_QFQ]) {
+        $msgUser = (empty($recordDirty[DIRTY_FE_USER])) ? "another user" : "user '" . $recordDirty[DIRTY_FE_USER] . "'";
+        $rc = LOCK_FOUND_CONFLICT;
+
+        $userMatch = ($recordDirty[DIRTY_FE_USER] != '' && $recordDirty[DIRTY_FE_USER] == $this->store->getVar(TYPO3_FE_USER, STORE_TYPO3));
+        if ($userMatch || $recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE] == $this->client[CLIENT_COOKIE_QFQ]) {
             $msgUser = "you";
-        } else {
-            $msgUser = (empty($recordDirty[DIRTY_FE_USER])) ? "another user" : "user '" . $recordDirty[DIRTY_FE_USER] . "'";
+            $rc = LOCK_FOUND_OWNER;
         }
-        $msgAt = "at " . $recordDirty[COLUMN_CREATED] . " from " . $recordDirty[DIRTY_REMOTE_ADDRESS];
-        $msg = "The record has been locked by $msgUser $msgAt";
+        $until = "until " . date_format(date_create($recordDirty[COLUMN_EXPIRE]), "d.m.Y H:i:s");
+        $msg = "The record has been locked by $msgUser $until.";
 
-        // Is the dirtyRecord mine?
-        if ($recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE] == $this->client[CLIENT_COOKIE_QFQ]) {
-            return LOCK_FOUND_OWNER;
-        } else {
-            return LOCK_FOUND_CONFLICT;
-        }
+        return $rc;
     }
 
     /**
diff --git a/extension/Classes/Core/QuickFormQuery.php b/extension/Classes/Core/QuickFormQuery.php
index 0ae795506db9fddea7218c97df957b668f2221b3..34855b931a7e55ab27ea89beb3d3540be76a4495 100644
--- a/extension/Classes/Core/QuickFormQuery.php
+++ b/extension/Classes/Core/QuickFormQuery.php
@@ -183,7 +183,7 @@ class QuickFormQuery {
             // Adds aliases together with level to TYPO3 store
             // E.g. [alias.1 => "myAlias", alias.1.2 => "mySecondAlias", ...]
             foreach ($btp->aliases as $key => $value) {
-                $this->store->setVar(TOKEN_ALIAS. '.' . $key, $value, STORE_TYPO3);
+                $this->store->setVar(TOKEN_ALIAS . '.' . $key, $value, STORE_TYPO3);
             }
         }
 
@@ -617,7 +617,10 @@ class QuickFormQuery {
             $dirty = new Dirty(false, $this->dbIndexData, $this->dbIndexQfq);
             $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) {
+
+            // Switch to READONLY
+            if (($rcLockFound == LOCK_FOUND_CONFLICT || $rcLockFound == LOCK_FOUND_OWNER)
+                && $recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE) {
                 $this->formSpec[F_MODE_GLOBAL] = F_MODE_READONLY;
             }
         }
@@ -1592,7 +1595,6 @@ class QuickFormQuery {
             F_DO_NOT_LOG_COLUMN,
 
             FE_FILE_MAX_FILE_SIZE,
-
             F_FE_DATA_PATTERN_ERROR_SYSTEM,  // Not a classical element to overwrite by form definition, but should be copied to detect changes per custom setting.
         ];
 
diff --git a/extension/Classes/Core/Report/Variables.php b/extension/Classes/Core/Report/Variables.php
index f50d9bba43db3a4a6e465fa9b01d9aca0cb1956b..a9ee655a8d5a7cc2a3e7900ea46ab0427b12a498 100644
--- a/extension/Classes/Core/Report/Variables.php
+++ b/extension/Classes/Core/Report/Variables.php
@@ -123,7 +123,7 @@ class Variables {
         // No numeric value implies that an alias was used
         if (!is_numeric($alias)) {
             // Get typo3 store
-            // Aliases are saved like [alias.1 => "myAlias", alias1.2 => "mySecondAlias", ...]
+            // Aliases are saved like [alias.1 => "myAlias", alias.1.2 => "mySecondAlias", ...]
             $storeT3 = Store::getStore(STORE_TYPO3);
             // Check for matching value of $alias
             $match = array_search($alias, $storeT3, true);
@@ -153,12 +153,12 @@ class Variables {
                     $arr = explode(':', $matches[3]);
                     $data = OnString::escape($arr[1] ?? '', $this->resultArray[$fullLevel][$varName], $rcFlagWipe);
                 }
-            // This is for the specific case, that the variable references its own level
-            // E.g. myAlias { \n sql = SELECT '{{myAlias.line.count}}' \n }
-            // Note: This is only used for line.count and line.total, because non-existing variables must stay unchanged
-            // E.g. myAlias { \n sql = SELECT 1 \n } \n { \n sql = SELECT '{{myAlias.varName}}' \n }
-            // '{{myAlias.varName}}' will not be changed to '{{1.varName}}'
-            } elseif($varName === LINE_COUNT || $varName === LINE_TOTAL) {
+                // This is for the specific case, that the variable references its own level
+                // E.g. myAlias { \n sql = SELECT '{{myAlias.line.count}}' \n }
+                // Note: This is only used for line.count and line.total, because non-existing variables must stay unchanged
+                // E.g. myAlias { \n sql = SELECT 1 \n } \n { \n sql = SELECT '{{myAlias.varName}}' \n }
+                // '{{myAlias.varName}}' will not be changed to '{{1.varName}}'
+            } elseif ($varName === LINE_COUNT || $varName === LINE_TOTAL) {
                 // myAlias needs to be replaced by the level
                 // E.g. {{1.2.line.count}}
                 $data = $matches[0];
diff --git a/extension/Tests/Unit/Core/Form/DirtyTest.php b/extension/Tests/Unit/Core/Form/DirtyTest.php
index 3fe8a8c1098c372b81aedfe20d9012d6e3f9df3f..f8a18f000fa7d8e1165cf280d25f6aef6c3ff69b 100644
--- a/extension/Tests/Unit/Core/Form/DirtyTest.php
+++ b/extension/Tests/Unit/Core/Form/DirtyTest.php
@@ -9,7 +9,7 @@
 namespace IMATHUZH\Qfq\Tests\Unit\Core\Form;
 
 use IMATHUZH\Qfq\Core\Database\Database;
- 
+
 use IMATHUZH\Qfq\Core\Form\Dirty;
 use IMATHUZH\Qfq\Core\Store\Session;
 use IMATHUZH\Qfq\Core\Store\Sip;
@@ -17,6 +17,8 @@ use IMATHUZH\Qfq\Tests\Unit\Core\Database\AbstractDatabaseTest;
 
 require_once(__DIR__ . '/../Database/AbstractDatabaseTest.php');
 
+const MSG_RECORD_ALREADY_LOCKED = 'Record already locked';
+
 /*
  * Open to check
  * - FORM_DELETE
@@ -352,7 +354,7 @@ class DirtyTest extends AbstractDatabaseTest {
         // Alice lock again
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -508,7 +510,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -700,8 +702,8 @@ class DirtyTest extends AbstractDatabaseTest {
         // Alice lock again
         $result = $dirty->process();
 
-        $msg = 'The record has already';
-        $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $msg];
+        $msg = MSG_RECORD_ALREADY_LOCKED;
+        $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
         $result[API_MESSAGE] = substr($result[API_MESSAGE], 0, strlen($msg));
@@ -855,7 +857,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -977,7 +979,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -1021,7 +1023,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = 'Record already locked ';
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -1065,7 +1067,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
@@ -1109,7 +1111,7 @@ class DirtyTest extends AbstractDatabaseTest {
 
         $result = $dirty->process();
 
-        $msg = 'The record has already';
+        $msg = MSG_RECORD_ALREADY_LOCKED;
         $expected = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $msg];
 
         // cut IP, User and Timestamp
diff --git a/extension/Tests/Unit/Core/Store/StoreTest.php b/extension/Tests/Unit/Core/Store/StoreTest.php
index 449738d1ce62b92c38529e0215f9cdb79de54682..c50ddf77a148896829fce635faf577fd535483bd 100644
--- a/extension/Tests/Unit/Core/Store/StoreTest.php
+++ b/extension/Tests/Unit/Core/Store/StoreTest.php
@@ -439,7 +439,6 @@ class StoreTest extends TestCase {
 //            SYSTEM_DO_NOT_LOG_COLUMN => SYSTEM_DO_NOT_LOG_COLUMN_DEFAULT,
             SYSTEM_PROTECTED_FOLDER_CHECK => 1,
             SYSTEM_CMD_WGET => 'wget >/dev/null 2>&1'
-
         ];
 
         $body = json_encode([
diff --git a/extension/ext_conf_template.txt b/extension/ext_conf_template.txt
index a0e4908e21f6dccd33aa3062927fa1efce5e712b..677b072fd45c842303994dbe66a4cdc2ea782135 100644
--- a/extension/ext_conf_template.txt
+++ b/extension/ext_conf_template.txt
@@ -133,7 +133,6 @@ encryptionMethod =
 protectedFolderCheck = 1
 
 
-
 # cat=form-config/config; type=string; label=Dirty record lock timeout (seconds):Default is '900'. Time in seconds to lock a record, starting from the first modification. If lock expires, it is acquired again on the next modification.
 recordLockTimeoutSeconds = 900
 
diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js
index 0a26cb5d6cee905a14460516281f87bd95973989..fae0ac550e00509376f3e34c188b987b99d30fec 100644
--- a/javascript/src/QfqForm.js
+++ b/javascript/src/QfqForm.js
@@ -213,7 +213,7 @@ var QfqNS = QfqNS || {};
         }];
         if (obj.data.status == "conflict_allow_force") {
             messageButtons.push({
-                label: "Ignore",
+                label: "Continue",
                 eventName: 'ignore'
             });
         }
@@ -278,10 +278,14 @@ var QfqNS = QfqNS || {};
                 break;
             case "conflict_allow_force":
                 messageType = "warning";
-                messageButtons.push({
-                    label: "Ignore",
+
+                messageButtons = [{
+                    label: "Continue",
                     eventName: 'ignore'
-                });
+                }, {
+                    label: "Cancel",
+                    eventName: 'reload'
+                }];
                 break;
             case "error":
                 messageType = "error";