diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst
index 300706227f633a121b02b86a4ba3eba9080d41a9..74b388bdef5a8fe6ee034cfe4503f8f0c714d165 100644
--- a/extension/Documentation/Manual.rst
+++ b/extension/Documentation/Manual.rst
@@ -9,9 +9,19 @@
 ..
 .. --------------------------------------------------
 .. Best Practice T3 reST  https://docs.typo3.org/typo3cms/drafts/github/xperseguers/RstPrimer/
-.. External Links: `Bootstrap <http://getbootstrap.com/>`_:
+.. Italic *italic*
+.. Bold **bold**
+.. Code ``text``
+.. External Links: `Bootstrap <http://getbootstrap.com/>`_
 .. Add Images: https://wiki.typo3.org/ReST_Syntax#Images ...
 ..
+.. Admonitions (https://docs.typo3.org/typo3cms/drafts/github/xperseguers/RstPrimer/#admonitions)
+..       .. note::    .. important::     .. tip::      .. warning::
+..
+.. Definition:
+.. some text becomes strong (only one line)
+..      description has to indented
+
 .. -*- coding: utf-8 -*- with BOM.
 
 
@@ -48,31 +58,17 @@ Preparation
 Report & Form
 ^^^^^^^^^^^^^
 
-In PHP 5.x the QFQ extension needs  the PHP MySQL native driver. The following functions are used and are only available with the
-native driver (see also: http://dev.mysql.com/downloads/connector/php-mysqlnd/):
-
-* mysqli::get_result (important),
-* mysqli::fetch_all (nice to use)
-
-To normalize UTF8 input, the *php5-intl* resp. *php7.0-intl* package is needed by
+To normalize UTF8 input, *php-intl* package is needed by
 
 * normalizer::normalize()
 
 For the `download`_ function, the programs `pdftk` and `file` are necessary to concatenate PDF files.
 
-Preparation for Ubuntu 14.04::
+Preparation for Ubuntu::
 
-	sudo apt-get install php5-mysqlnd php5-intl
-	sudo apt-get install pdftk file                  # for file upload and PDF
-	sudo apt-get install inkscape imagemagick     # to render thumbnails
-	sudo php5enmod mysqlnd
-	sudo service apache2 restart
-
-Preparation for Ubuntu 16.04::
-
-	sudo apt install php7.0-intl
-	sudo apt install pdftk libxrender1 file pdf2svg  # for file upload, PDF and 'HTML to PDF' (wkhtmltopdf), PDF split
-	sudo apt install inkscape imagemagick            # to render thumbnails
+  sudo apt install php-intl
+  sudo apt install pdftk libxrender1 file pdf2svg  # for file upload, PDF and 'HTML to PDF' (wkhtmltopdf), PDF split
+  sudo apt install inkscape imagemagick            # to render thumbnails
 
 .. _wkhtml:
 
@@ -89,32 +85,38 @@ The program is not included in QFQ and has to be manually installed.
   * The current version 0.12.4 might have trouble with https connections. Version 0.12.5-dev (github master branch)
     seems more reliable. Please contact the QFQ authors if you need a compiled Ubuntu version of wkhtmltopdf.
 
-In `configuration`_ specify the: ::
+In `configuration`_ specify the::
 
-    cmdWkhtmltopdf=/opt/wkhtmltox/bin/wkhtmltopdf`.
-    baseUrl=http://www.example.com/`.
+    config.cmdWkhtmltopdf=/opt/wkhtmltox/bin/wkhtmltopdf`.
+    config.baseUrl=http://www.example.com/`.
 
 If wkhtml has been compiled with dedicated libraries (not part of LD_LIBRARY_PATH), specify the LD_LIBRARY_PATH together
 with the path-filename: ::
 
     cmdWkhtmltopdf=LD_LIBRARY_PATH=/opt/wkhtmltox/lib /opt/wkhtmltox/bin/wkhtmltopdf
 
-**Important**: To access FE_GROUP protected pages or content, it's necessary to disable the `[FE][lockIP]` check! `wkhtml`
-will access the Typo3 page locally (localhost) and that IP address is different from the client (=user) IP.
+.. important::
+
+    To access FE_GROUP protected pages or content, it's necessary to disable the `[FE][lockIP]` check! `wkhtml`
+    will access the Typo3 page locally (localhost) and that IP address is different from the client (=user) IP.
 
 Configure via Typo3 Installtool `All configuration > $TYPO3_CONF_VARS['FE']`: ::
 
    [FE][lockIP] = 0
 
-**Warning**: this disables an important anti-'session hijacking' protection. The security level of the whole installation
-will be *lowered*! Again, this is only needed if `wkhtml` needs access to FE_GROUP protected pages & content. As an
-alternative to lower the security level, create a separated page subtree which is only accessible (configured via
-Typoscript) from specific IPs **or** if a FE-User is logged in.
+.. warning::
+
+    ``[FE][lockIP] = 0`` disables an important anti-'session hijacking' protection. The security level of the whole installation
+    will be *lowered*! Again, this is only needed if `wkhtml` needs access to FE_GROUP protected pages & content. As an
+    alternative to lower the security level, create a separated page subtree which is only accessible (configured via
+    Typoscript) from specific IPs **or** if a FE-User is logged in.
 
 If there are problems with converting/downloading FE_GROUP protected pages, check `SHOW_DEBUG_INFO = download` to debug.
 
-**Important**: Converting HTML to PDF gives no error message but RC=-1? Check carefully all includes of CSS, JS, images
-and so on! Typically some of them fails to load and wkhtml stops running!
+.. note::
+
+    Converting HTML to PDF gives no error message but RC=-1? Check carefully all includes of CSS, JS, images
+    and so on! Typically some of them fails to load and wkhtml stops running!
 
 
 HTML to PDF conversion
@@ -130,17 +132,17 @@ Different browser prints the same page in different variations. To prevent this,
 
 Provide a `print this page`-link (replace 'current pageId' )::
 
-	<a href="typo3conf/ext/qfq/Source/api/print.php?id={current pageId}">Print this page</a>
+  <a href="typo3conf/ext/qfq/Source/api/print.php?id={current pageId}">Print this page</a>
 
 Any parameter specified after `print.php` will be delivered to `wkhtmltopdf` as part of the URL.
 
 Typoscript code to implement a print link on every page::
 
-	10 = TEXT
-	10 {
-		wrap = <a href="typo3conf/ext/qfq/Source/api/print.php?id=|&type=99"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Printview</a>
-		data = page:uid
-	}
+  10 = TEXT
+  10 {
+    wrap = <a href="typo3conf/ext/qfq/Source/api/print.php?id=|&type=99"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Printview</a>
+    data = page:uid
+  }
 
 Send Email
 ^^^^^^^^^^
@@ -236,7 +238,9 @@ Setup a *report* to manage all *forms*:
 * Create a Typo3 page.
 * Set the 'URL Alias' to `form` (default) or the individual defined value in parameter `editFormPage` (configuration_).
 * Insert a content record of type *qfq*.
-* In the bodytext insert the following code: ::
+* In the bodytext insert the following code:
+
+.. code-block:: mysql
 
     # If there is a form given by SIP: show
     form={{form:SE}}
@@ -259,7 +263,7 @@ Setup a *report* to manage all *forms*:
         10 {
             # All forms
             sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id) as _pagee
-                         , f.id, f.name, f.title, f.tableName
+                         , f.id, f.name, f.title AS '_striptags', f.tableName
                          , CONCAT('U:form=form&r=', f.id) as _paged
                       FROM Form AS f
                       ORDER BY f.name
@@ -278,7 +282,7 @@ Installation: Check List
 * Protect the directory `<T3 installation>/fileadmin/protected` in Apache against direct file access.
 
   * `<T3 installation>/fileadmin/protected/` should be used for confidential (uploaded / generated) data.
-  * `<T3 installation>/fileadmin/protected/log/...` is the default place for QFQ logfiles.
+  * `<T3 installation>/fileadmin/protected/log/...` is the default place for QFQ log files.
 
 * Protect the directory `<T3 installation>/fileadmin` in Apache to not execute PHP Scripts - malicious uploads won't be executed.
 * Setup a log rotation rule for `sqlLog`.
@@ -342,6 +346,7 @@ Example: *typo3conf/config.qfq.php*: ::
 Extension Manager: QFQ Configuration
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | Keyword                           | Default / Example                                     | Description                                                                |
 +===================================+=======================================================+============================================================================+
@@ -384,7 +389,7 @@ Extension Manager: QFQ Configuration
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | formSubmitLogMode                 | all                                                   | | *all*: every form submission will be logged.                             |
 |                                   |                                                       | | *none*: no logging.                                                      |
-|                                   |                                                       | | See `Form Submit Log page`_ for example QFQ code to display the log.     |
+|                                   |                                                       | | See `form-submit-log-page`_ for example QFQ code to display the log.     |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | redirectAllMailTo                 | john@doe.com                                          | If set, redirect all QFQ generated mails (Form, Report) to the specified.  |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
@@ -433,6 +438,8 @@ Extension Manager: QFQ Configuration
 | securityGetMaxLength              | 50                                                    | GET vars longer than 'x' chars triggers an `attack-recognized`.            |
 |                                   |                                                       | `ExceptionMaxLength`_.                                                     |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
+| securityFailedAuthDelay           | 3                                                     | If authorization fails, sleep 'x' seconds before answering the request.    |
++-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | Form-Config                                                                                                                                                            |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | recordLockTimeoutSeconds          | 900                                                   | Timeout for record locking. After this time, a record will be replaced.    |
@@ -599,7 +606,7 @@ Websites, delivering semester data, school year schedules, or any other type or
 
 In configuration_: ::
 
-	fillStoreSystemBySql1: SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1
+  fillStoreSystemBySql1: SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1
 
 a variable 'periodId' will automatically computed and filled in STORE SYSTEM. Access it via `{{periodId:Y0}}`.
 To get the name and current period: ::
@@ -608,7 +615,8 @@ To get the name and current period: ::
 
 Typically, it's necessary to offer a 'previous' / 'next' link. In this example, the STORE SIP holds the new periodId: ::
 
-  SELECT CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}-1, '|Next') AS _page, ' ', name, ' ', CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}+1, '|Next') AS _page FROM Period AS s WHERE s.id={{periodId:SY0}}
+  SELECT CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}-1, '|Next') AS _page, ' ', name, ' ',
+    CONCAT('p:{{pageAlias:T}}&periodId=', {{periodId:SY0}}+1, '|Next') AS _page FROM Period AS s WHERE s.id={{periodId:SY0}}
 
 Take care for minimum and maximum indexes (do not render the links if out of range).
 
@@ -821,7 +829,7 @@ System tables
 | Split         | Persistent | Data       |
 +---------------+------------+------------+
 
-See `Mail Log page`_ and `Form Submit Log page`_ for some Frontend views for these tables.
+See `mail-log-page`_ and `form-submit-log-page`_ for some Frontend views for these tables.
 
 * Check Bug #5459 - support of system tables in different DBs not supported.
 
@@ -920,7 +928,7 @@ Debug
 SQL Logging
 -----------
 
-configuration_
+Setup in configuration_
 
 .. _SQL_LOG:
 
@@ -968,14 +976,14 @@ configuration_
   * *download*:
 
     * During a download (especially by using wkhtml), temporary files are not deleted automatically. Also the
-      `wkhtmltopdf` and `pdftk` commandlines will be logged to `SQL_LOG`_. Use this only to debug problems on download.
+      ``wkhtmltopdf`` and ``pdftk`` commandlines will be logged to `SQL_LOG`_. Use this only to debug problems on download.
 
 .. _REDIRECT_ALL_MAIL_TO:
 
 Redirect all mail to (catch all)
 --------------------------------
 
-configuration_
+Setup in configuration_
 
 * *redirectAllMailTo=john@doe.com*
 
@@ -988,7 +996,7 @@ configuration_
     * Clear 'CC' and 'Bcc'
     * Write a note and the original configured receiver at the top of the email body.
 
-_`Mail Log page`
+_`mail-log-page`
 
 Mail Log page
 -------------
@@ -1006,7 +1014,8 @@ The following QFQ code could be used for that purpose (put it in a QFQ PageConte
                FROM gGroup AS gr
                INNER JOIN MailLog AS ml ON ml.grId = gr.id
                GROUP BY gr.id
-      head = <form onchange='this.submit();' class='form-inline'><input type='hidden' name='id' value='{{pageAlias:T0}}'>Filter By Group: <select name='grId' class='form-control'><option value=''></option>
+      head = <form onchange='this.submit();' class='form-inline'><input type='hidden' name='id' value='{{pageAlias:T0}}'>
+               Filter By Group: <select name='grId' class='form-control'><option value=''></option>
       rbeg = <option value='
       rend = </option>
       tail = </select>
@@ -1022,17 +1031,20 @@ The following QFQ code could be used for that purpose (put it in a QFQ PageConte
       sql = SELECT id, '</td><td>', grId, '</td><td>', xId, '</td><td>',
                 REPLACE(receiver, ',', '<br>'), '</td><td>', REPLACE(sender, ',', '<br>'), '</td><td>',
                 DATE_FORMAT(modified, '%d.%m.%Y<br>%H:%i:%s'), '</td><td style="word-break:break-word;">',
-                CONCAT('<b>', subject, '</b><br>', IF(@summary = 'true', CONCAT(SUBSTR(body, 1, LEAST(IF(INSTR(body, '\n') = 0, 50, INSTR(body, '\n')), IF(INSTR(body, '<br>') = 0, 50, INSTR(body, '<br>')))-1), ' ...'), CONCAT('<br>', REPLACE(body, '\n', '<br>'))) )
+                CONCAT('<b>', subject, '</b><br>', IF(@summary = 'true', CONCAT(SUBSTR(body, 1,
+                       LEAST(IF(INSTR(body, '\n') = 0, 50, INSTR(body, '\n')), IF(INSTR(body, '<br>') = 0, 50,
+                       INSTR(body, '<br>')))-1), ' ...'), CONCAT('<br>', REPLACE(body, '\n', '<br>'))) )
               FROM MailLog WHERE (grId = @grId OR @grId = 0)
               ORDER BY modified DESC
               LIMIT 100
-      head = <table class="table table-condensed table-hover"><tr><th>Id</th><th>grId</th><th>xId</th><th>To</th><th>From</th><th>Date</th><th>E-Mail</th></tr>
+      head = <table class="table table-condensed table-hover"><tr>
+                 <th>Id</th><th>grId</th><th>xId</th><th>To</th><th>From</th><th>Date</th><th>E-Mail</th></tr>
       tail = </table>
       rbeg = <tr><td>
       rend = </td></tr>
     }
 
-_`Form Submit Log page`
+_`form-submit-log-page`
 
 Form Submit Log page
 --------------------
@@ -1221,11 +1233,11 @@ Rules for CheckType Auto (by priority):
 
 * TypeAheadSQL or TypeAheadLDAP defined: **alnumx**
 * Table definition
-	* integer type: **digit**
-	* floating point number: **numerical**
+  * integer type: **digit**
+  * floating point number: **numerical**
 * FE Type
-	* 'password', 'note': **all**
-	* 'editor', 'text' and encode = 'specialchar': **all**
+  * 'password', 'note': **all**
+  * 'editor', 'text' and encode = 'specialchar': **all**
 * None of the above: **alnumx**
 
 
@@ -1249,16 +1261,16 @@ manipulate FE user passwords via QFQ. See `setFeUserPassword`_
 
 The following `escape` and `hashing` types are available:
 
-	* 'm' - `real_escape_string() <http://php.net/manual/en/mysqli.real-escape-string.php>`_ (m = mysql)
-	* '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 \\'.
-	* 'd' - double ticks " will be escaped against \\".
-	* 'C' - colon ':' will be escaped against \\:.
-	* 'c' - config - the escape type configured in `configuration`_.
-	* 'p' - password hashing: depends on the hashing type in the Typo3 installation, includes salting if configured.
-	* ''  - the escape type configured in `configuration`_.
-	* '-' - no escaping.
+  * 'm' - `real_escape_string() <http://php.net/manual/en/mysqli.real-escape-string.php>`_ (m = mysql)
+  * '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 \\'.
+  * 'd' - double ticks " will be escaped against \\".
+  * 'C' - colon ':' will be escaped against \\:.
+  * 'c' - config - the escape type configured in `configuration`_.
+  * 'p' - password hashing: depends on the hashing type in the Typo3 installation, includes salting if configured.
+  * ''  - the escape type configured in `configuration`_.
+  * '-' - no escaping.
 
 * The `escape` type is defined by the fourth parameter of the variable. E.g.: `{{name:FE:alnumx:m}}` (m = mysql).
 * It's possible to combine different `escape` types, they will be processed in the order given. E.g. `{{name:FE:alnumx:Ls}}` (L, s).
@@ -1290,8 +1302,8 @@ If a value violates the sanitize class, instead of content on of the following t
  * 'c' - The violated class will be set as content, surrounded by '!!'. E.g. '!!digit!!'. This is the default.
  * 'e' - Instead of the value an empty string will be set as content.
  * '0' - Instead of the value the string '0' will be set as content.
- * 'custom text ...' - Instead of the value the custom text will be set as content. If the text contains a ':', that one needs to
-    be escaped by '\'. Check `variable-escape`_ qualifier 'C' to let QFQ do the colon escaping.
+ * 'custom text ...' - Instead of the value the custom text will be set as content. If the text contains a ':', that one
+   needs to be escaped by '\'. Check `variable-escape`_ qualifier 'C' to let QFQ do the colon escaping.
 
 .. _`sql-variables`:
 
@@ -1333,7 +1345,7 @@ Database index
 To access different databases in a `multi-database`_  setup, the database index can be specified after the opening curly
 braces. ::
 
-	{{[1]SELECT ... }}
+  {{[1]SELECT ... }}
 
 For using the indexData and indexQfq (configuration_), it's a good practice to specify the variable name
 instead of the numeric index. ::
@@ -1385,11 +1397,11 @@ Link column variables
 These variables return a link, completely rendered in HTML. The syntax and all features of `column-link`_ are available.
 The following code will render a 'new person' button::
 
-	{{p:form&form=Person|s|N|t:new person AS link}}
+  {{p:form&form=Person|s|N|t:new person AS link}}
 
 For better reading, the format string might be wrapped in single or double quotes (this is optional): ::
 
-	{{"p:form&form=Person|s|N|t:new person" AS link}}
+  {{"p:form&form=Person|s|N|t:new person" AS link}}
 
 These variables are especially helpful in:
 
@@ -1502,16 +1514,20 @@ If you only have access to `.htaccess`, create a file `<site path>/fileadmin/pro
          Require all denied
     </IfModule>
 
-**Important**: all QFQ uploads should save files in or below such a directory.
+.. important::
+
+    All QFQ uploads should save files only in/below such a protected directory.
 
 To offer download of those files, use the reserved column name '_download' (see `download`_) or variants.
 
-**Important**: To protect the installation against executing of uploaded malicious script code, disable PHP for the final
-upload directory. E.g. `fileadmin` (Apache): ::
+.. important::
+
+    To protect the installation against executing of uploaded malicious script code, disable PHP for the final
+    upload directory. E.g. `fileadmin` (Apache)::
 
-		<Directory "/var/www/html/fileadmin">
-			php_admin_flag engine Off
-		</Directory>
+        <Directory "/var/www/html/fileadmin">
+          php_admin_flag engine Off
+        </Directory>
 
 This is in general a good security improvement for directories with user supplied content.
 
@@ -1685,7 +1701,9 @@ Store: *CLIENT* - C
  +=========================+==========================================================================================================================================+
  | s                       | =SIP                                                                                                                                     |
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
- | r                       | record id. Typically stored in SIP, rarely specified on the URL                                                                          |
+ | r                       | record id. Only if specified as GET parameter - typically stored in SIP (=STORE_SIP)                                                     |
+ +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
+ | form                    | Name of form to load. Only if specified as GET parameter - typically stored in SIP (=STORE_SIP)                                          |
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
  | HTTP_HOST               | current HTTP HOST                                                                                                                        |
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
@@ -1693,7 +1711,7 @@ Store: *CLIENT* - C
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
  | '$_SERVER[*]'           | All other variables accessible by *$_SERVER[]*. Only the often used have a pre-defined sanitize class.                                   |
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
- | form                    | Unique name of current form                                                                                                              |
+ | Authorization           | Value of the HTTP Header 'Authorization'. This is typically not set. Mostly used for authentication of REST requests                     |
  +-------------------------+------------------------------------------------------------------------------------------------------------------------------------------+
 
 .. _STORE_TYPO3:
@@ -1997,9 +2015,11 @@ Also, the STORE LDAP remains filled, during the whole form processing time. E.g.
 name and email, it's sufficient to fire one FSL on the first FormElement Action element, and use the same values during further FormElement
 Action Elements.
 
-Important: LDAP access might slow down the *Form* processing on load, update or save! The timeout (default: 3 seconds) have
-to be multiplied by the number of accesses. E.g. a broken LDAP connection and 3 *FormElements* with *FSL*
-results to 9 seconds delay on save. Also be prepared not to receive the expected data.
+.. important::
+
+    LDAP access might slow down the *Form* processing on load, update or save! The timeout (default: 3 seconds) have
+    to be multiplied by the number of accesses. E.g. a broken LDAP connection and 3 *FormElements* with *FSL*
+    results to 9 seconds delay on save. Also be prepared not to receive the expected data.
 
 * *FormElement.parameter.fillStoreLdap* - activate the mode *Fill S* - no value is needed, the existence is sufficient.
 * *Form.parameter* or *FormElement.parameter*:
@@ -2536,10 +2556,10 @@ Optional it might be defined via *Form.parameter* ::
 
 The following shows the same *Form* in the `regular`, `readonly` and `requiredOff` mode::
 
-	10.sql = SELECT CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id, '|Regular') as _pagee,
-	                CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=readonly&r=', p.id, '|Readonly') as _pagee,
-	                CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=requiredOff&r=', p.id, '|Required off') as _pagee
-	                FROM Person AS p
+  10.sql = SELECT CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id, '|Regular') as _pagee,
+                  CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=readonly&r=', p.id, '|Readonly') as _pagee,
+                  CONCAT('p:{{pageAlias:T}}&form=person&formModeGlobal=requiredOff&r=', p.id, '|Required off') as _pagee
+                  FROM Person AS p
 
 ..
 
@@ -2687,11 +2707,11 @@ Add an *action* record, type='afterSave', and assign the record to the given *te
 
 In the parameter field define: ::
 
-		slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT %D,1}}
-		sqlHonorFormElements = city%d, street%d
-		sqlUpdate = {{UPDATE Address SET city='{{city%d:FE:alnumx:s}}', street='{{street%d:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1}}
-		sqlInsert = {{INSERT INTO Address (`personId`, `city`, `street`) VALUES ({{id}}, '{{city%d:FE:alnumx:s}}' , '{{street%d:FE:alnumx:s}}') }}
-		sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1}}
+    slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT %D,1}}
+    sqlHonorFormElements = city%d, street%d
+    sqlUpdate = {{UPDATE Address SET city='{{city%d:FE:alnumx:s}}', street='{{street%d:FE:alnumx:s}}'  WHERE id={{slaveId}} LIMIT 1}}
+    sqlInsert = {{INSERT INTO Address (`personId`, `city`, `street`) VALUES ({{id}}, '{{city%d:FE:alnumx:s}}' , '{{street%d:FE:alnumx:s}}') }}
+    sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1}}
 
 The `slaveId` needs attention: the placeholder `%d` starts always at 1. The `LIMIT` directive starts at 0 - therefore
 use `%D` instead of `%d`, cause `%D` is always one below `%d` - but can **only** be used on the action element.
@@ -3480,11 +3500,11 @@ will be rendered inside the form as a HTML table.
   * *subrecordTableClass*: Optional. Default: 'table table-hover qfq-subrecord-table'. If given, the default will be
      overwritten. Example: ::
 
-	  subrecordTableClass = table table-hover qfq-subrecord-table qfq-table-50
+        subrecordTableClass = table table-hover qfq-subrecord-table qfq-table-50
 
   * Tablesorter in Subrecord:
 
-  	   subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filter
+       subrecordTableClass = table table-hover qfq-subrecord-table tablesorter tablesorter-pager tablesorter-filter
 
   * *subrecordColumnTitleEdit*: Optional. Will be rendered as the column title for the new/edit column.
   * *subrecordColumnTitleDelete*: Optional. Will be rendered as the column title for the delete column.
@@ -3553,8 +3573,8 @@ and will be processed after saving the primary record and before any action Form
 * *FormElement.value* = `<string>` - By default, the full path of any already uploaded file is shown. To show something
   different, e.g. only the filename, define: ::
 
-	 a) {{filenameBase:V}}
-	 b) {{SELECT SUBSTRING_INDEX( '{{pathFileName:R}}', '/', -1)  }}
+   a) {{filenameBase:V}}
+   b) {{SELECT SUBSTRING_INDEX( '{{pathFileName:R}}', '/', -1)  }}
 
 See also `downloadButton`_ to offer a download of an uploaded file.
 
@@ -3623,7 +3643,7 @@ See also `downloadButton`_ to offer a download of an uploaded file.
 
   * *chmodFile* = <unix file permission mode> - e.g. `660` for owner and group read and writeable. Only the numeric mode is allowed.
   * *chmodDir* = <unix file permission mode> - e.g. `770` for owner and group read, writeable and executable. Only the
-     numeric mode is allowed. Will be applied to all new created directories.
+    numeric mode is allowed. Will be applied to all new created directories.
 
   * autoOrient: images might contain EXIF data (e.g. captured via mobile phones) incl. an orientation tag like TopLeft,
     BottomRight and so on. Web-Browser and other grafic programs often understand and respect those information and rotate
@@ -4051,7 +4071,7 @@ Parameter
   `pId` in the link who calls the address form. The following creates a 'new' button for an address for all persons, and
   the pId will be automatically saved in the address table: ::
 
-		SELECT CONCAT('p:{{pageAlias:T}}&form=address&r=0&pId=', p.id) AS _pagen FROM Person AS p
+    SELECT CONCAT('p:{{pageAlias:T}}&form=address&r=0&pId=', p.id) AS _pagen FROM Person AS p
 
   Such parameter, which the form expects to be in the SIP url, should be specified in Form.permitNew and/or Form.permitEdit.
   It's only a check for the webmaster, not to forgot a parameter in a SIP url.
@@ -4108,11 +4128,11 @@ Assuming the Typo3 page has the
 
 Configuration in configuration_: ::
 
-		formLanguageAId = 1
-		formLanguageALabel = English
+    formLanguageAId = 1
+    formLanguageALabel = English
 
-		formLanguageBId = 2
-		formLanguageBLabel = Spanish
+    formLanguageBId = 2
+    formLanguageBLabel = Spanish
 
 The default language is not covered in configuration_.
 
@@ -4122,31 +4142,31 @@ missing definition means 'take the default'. E.g.:
 
 * Form: 'person'
 
-	+--------------------+--------------------------+
-	| Column             | Value                    |
-	+====================+==========================+
-	| title              | Eingabe Person           |
-	+--------------------+--------------------------+
-	| languageParameterA | title=Input Person       |
-	+--------------------+--------------------------+
-	| languageParameterB | title=Persona de entrada |
-	+--------------------+--------------------------+
+  +--------------------+--------------------------+
+  | Column             | Value                    |
+  +====================+==========================+
+  | title              | Eingabe Person           |
+  +--------------------+--------------------------+
+  | languageParameterA | title=Input Person       |
+  +--------------------+--------------------------+
+  | languageParameterB | title=Persona de entrada |
+  +--------------------+--------------------------+
 
 * FormElement 'firstname' in Form 'person':
 
-	+--------------------+------------------------------------------------+
-	| Column             | Value                                          |
-	+====================+================================================+
-	| title              | Vorname                                        |
-	+--------------------+------------------------------------------------+
-	| note               | Bitte alle Vornamen erfassen                   |
-	+--------------------+------------------------------------------------+
-	| languageParameterA | | title=Firstname                              |
-	|                    | | note=Please give all firstnames              |
-	+--------------------+------------------------------------------------+
-	| languageParameterB | | title=Persona de entrada                     |
-	|                    | | note=Por favor, introduzca todos los nombres |
-	+--------------------+------------------------------------------------+
+  +--------------------+------------------------------------------------+
+  | Column             | Value                                          |
+  +====================+================================================+
+  | title              | Vorname                                        |
+  +--------------------+------------------------------------------------+
+  | note               | Bitte alle Vornamen erfassen                   |
+  +--------------------+------------------------------------------------+
+  | languageParameterA | | title=Firstname                              |
+  |                    | | note=Please give all firstnames              |
+  +--------------------+------------------------------------------------+
+  | languageParameterB | | title=Persona de entrada                     |
+  |                    | | note=Por favor, introduzca todos los nombres |
+  +--------------------+------------------------------------------------+
 
 
 The following fields are possible:
@@ -4948,8 +4968,8 @@ Table: Person
 
        # Typeahead
        typeAheadLdapSearch = (|(cn=*?*)(mail=*?*))
-       typeAheadLdapValuePrintf	‘%s / %s’, cn, email
-       typeAheadLdapIdPrintf	‘%s’, email
+       typeAheadLdapValuePrintf ‘%s / %s’, cn, email
+       typeAheadLdapIdPrintf  ‘%s’, email
 
        # dynamicUpdate: show note
        fillStoreLdap
@@ -5100,9 +5120,9 @@ Only SELECT and SHOW queries will fire subqueries.
 
 Processing of the resulting rows and columns:
 
-	* In general, all columns of all rows will be printed out sequentially.
-	* On a per column base, printing of columns can be suppressed by starting the column name with an underscore '_'. E.g.
-	  `SELECT id AS _id`.
+  * In general, all columns of all rows will be printed out sequentially.
+  * On a per column base, printing of columns can be suppressed by starting the column name with an underscore '_'. E.g.
+    `SELECT id AS _id`.
 
      This might be useful to store values, which will be used later on in another query via the `{{id:R}}` or
      `{{<level>.columnName}}` variable. To suppress printing of a column, use a underscore as column name prefix. E.g.
@@ -5250,9 +5270,9 @@ Leading / trailing spaces
 By default, leading or trailing whitespaces are removed from strings behind '='. E.g. 'rend =  test ' becomes 'test' for
 rend. To prevent any leading or trailing spaces, surround them by using single or double ticks. Example: ::
 
-	10.sql = SELECT name FROM Person
-	10.rsep = ' '
-	10.head = "Names: "
+  10.sql = SELECT name FROM Person
+  10.rsep = ' '
+  10.head = "Names: "
 
 
 Braces character for nesting
@@ -6081,23 +6101,23 @@ Optional any number of sources can be concatenated to a single PDF file: 'C|F:<f
 
 Examples in Report::
 
-	# One file attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf" AS _sendmail
+  # One file attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf" AS _sendmail
 
-	# Two files attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf" AS _sendmail
+  # Two files attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf" AS _sendmail
 
-	# Two files and a webpage (converted to PDF) are attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf|p:?id=export&r=123|d:person.pdf" AS _sendmail
+  # Two files and a webpage (converted to PDF) are attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|F:fileadmin/summary.pdf|F:fileadmin/detail.pdf|p:?id=export&r=123|d:person.pdf" AS _sendmail
 
-	# Two webpages (converted to PDF) are attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123|d:person123.pdf|p:?id=export&r=234|d:person234.pdf" AS _sendmail
+  # Two webpages (converted to PDF) are attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123|d:person123.pdf|p:?id=export&r=234|d:person234.pdf" AS _sendmail
 
-	# One file and two webpages (converted to PDF) are *concatenated* to one PDF and attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|C|F:fileadmin/summary.pdf|p:?id=export&r=123|p:?id=export&r=234|d:complete.pdf" AS _sendmail
+  # One file and two webpages (converted to PDF) are *concatenated* to one PDF and attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|C|F:fileadmin/summary.pdf|p:?id=export&r=123|p:?id=export&r=234|d:complete.pdf" AS _sendmail
 
-	# One T3 webpage, protected by a SIP, are attached.
-	10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123&_sip=1|d:person123.pdf" AS _sendmail
+  # One T3 webpage, protected by a SIP, are attached.
+  10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available.|p:?id=export&r=123&_sip=1|d:person123.pdf" AS _sendmail
 
 .. _column_img:
 
@@ -6212,15 +6232,15 @@ Most of the other Link-Class attributes can be used to customize the link. ::
 * For column `_pdf` and `_zip`, the element sources `p:...`, `U:...`, `u:...`, `F:...` might repeated multiple times.
 * Example: ::
 
-		10.sql = SELECT "F:fileadmin/test.pdf" as _pdf,  "F:fileadmin/test.pdf" as _file,  "F:fileadmin/test.pdf" as _zip
-		10.sql = SELECT "p:id=export&r=1" as _pdf,  "p:id=export&r=1" as _file,  "p:id=export&r=1" as _zip
+    10.sql = SELECT "F:fileadmin/test.pdf" as _pdf,  "F:fileadmin/test.pdf" as _file,  "F:fileadmin/test.pdf" as _zip
+    10.sql = SELECT "p:id=export&r=1" as _pdf,  "p:id=export&r=1" as _file,  "p:id=export&r=1" as _zip
 
-		10.sql = SELECT "t:Download PDF|F:fileadmin/test.pdf" as _pdf,  "t:Download PDF|F:fileadmin/test.pdf" as _file,  "t:Download ZIP|F:fileadmin/test.pdf" as _zip
-		10.sql = SELECT "t:Download PDF|p:id=export&r=1" as _pdf,  "t:Download PDF|p:id=export&r=1" as _file,  "t:Download ZIP|p:id=export&r=1" as _zip
+    10.sql = SELECT "t:Download PDF|F:fileadmin/test.pdf" as _pdf,  "t:Download PDF|F:fileadmin/test.pdf" as _file,  "t:Download ZIP|F:fileadmin/test.pdf" as _zip
+    10.sql = SELECT "t:Download PDF|p:id=export&r=1" as _pdf,  "t:Download PDF|p:id=export&r=1" as _file,  "t:Download ZIP|p:id=export&r=1" as _zip
 
-		10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _pdf, "d:complete.zip|t:Download ZIP|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _zip
+    10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _pdf, "d:complete.zip|t:Download ZIP|F:fileadmin/test1.pdf|F:fileadmin/test2.pdf" as _zip
 
-		10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test.pdf|p:id=export&r=1|u:www.example.com" AS _pdf
+    10.sql = SELECT "d:complete.pdf|t:Download PDF|F:fileadmin/test.pdf|p:id=export&r=1|u:www.example.com" AS _pdf
 
 .. _column-save-pdf:
 
@@ -6242,8 +6262,8 @@ Tips:
 
 Examples: ::
 
-	SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf" AS _savePdf
-	SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _savePdf
+  SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf" AS _savePdf
+  SELECT "d:fileadmin/result.pdf|F:fileadmin/_temp_/test.pdf|U:id=test&--orientation=landscape" AS _savePdf
 
 
 .. _column-thumbnail:
@@ -6289,17 +6309,17 @@ tag. Something like `<body style="background-image:url(bgimage.jpg)">` could be
 
 Example: ::
 
-	# SIP protected, IMG tag, thumbnail width 150px
-	10.sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail
+  # SIP protected, IMG tag, thumbnail width 150px
+  10.sql = SELECT 'T:fileadmin/file3.pdf' AS _thumbnail
 
-	# SIP protected, IMG tag, thumbnail width 50px
-	20.sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail
+  # SIP protected, IMG tag, thumbnail width 50px
+  20.sql = SELECT 'T:fileadmin/file3.pdf|W:50' AS _thumbnail
 
-	# No SIP protection, IMG tag, thumbnail width 150px
-	30.sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail
+  # No SIP protection, IMG tag, thumbnail width 150px
+  30.sql = SELECT 'T:fileadmin/file3.pdf|s:0' AS _thumbnail
 
-	# SIP protected, only the URL to the image, thumbnail width 150px
-	40.sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail
+  # SIP protected, only the URL to the image, thumbnail width 150px
+  40.sql = SELECT 'T:fileadmin/file3.pdf|s:1|r:7' AS _thumbnail
 
 
 Dimension
@@ -6314,7 +6334,7 @@ Cleaning
 By default, the thumbnail directories are never cleaned. It's a good idea to install a cronjob which purges all files
 older than 1 year: ::
 
-	find /path/to/files -type f -mtime +365 -delete
+  find /path/to/files -type f -mtime +365 -delete
 
 Render
 ''''''
@@ -6337,7 +6357,7 @@ The secure path needs to be protected against direct file access by the webmaste
 
 QFQ returns a HTML 'img'-tag: ::
 
-	<img src="api/download.php?s=badcaffee1234">
+  <img src="api/download.php?s=badcaffee1234">
 
 Thumbnail: public
 '''''''''''''''''
@@ -6490,49 +6510,49 @@ Parameter and (element) sources
       the key/value tuple in `p:...`, `u:...` or `U:...` has to be separated by '='. Please see last example below.
     * If an option contains an '&' it must be escaped with double '\\'. See example.
 
-	Most of the other Link-Class attributes can be used to customize the link as well.
+  Most of the other Link-Class attributes can be used to customize the link as well.
 
 Example `_link`: ::
 
-	# single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast.
-	SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
+  # single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast.
+  SELECT "d:file.pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
 
-	# single `file`, with mode
-	SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
+  # single `file`, with mode
+  SELECT "d:file.pdf|M:pdf|s|t:Download|F:fileadmin/pdf/test.pdf" AS _link
 
-	# three sources: two pages and one file
-	SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
+  # three sources: two pages and one file
+  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
 
-	# three sources: two pages and one file
-	SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
+  # three sources: two pages and one file
+  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1|F:fileadmin/pdf/test.pdf" AS _link
 
-	# three sources: two pages and one file, parameter to wkhtml will be SIP encoded
-	SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link
+  # three sources: two pages and one file, parameter to wkhtml will be SIP encoded
+  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1&_sip=1|p:id=detail2&r=1&_sip=1|F:fileadmin/pdf/test.pdf" AS _link
 
-	# three sources: two pages and one file, the second page will be in landscape and pagesize A3
-	SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link
+  # three sources: two pages and one file, the second page will be in landscape and pagesize A3
+  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail&r=1|p:id=detail2&r=1&--orientation=Landscape&--page-size=A3|F:fileadmin/pdf/test.pdf" AS _link
 
-	# One source and a header file. Note: the parameter to the header URL is escaped with double backslash.
-	SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link
+  # One source and a header file. Note: the parameter to the header URL is escaped with double backslash.
+  SELECT "d:complete.pdf|s|t:Complete PDF|p:id=detail2&r=1&--orientation=Landscape&--header={{URL:R}}?indexp.php?id=head\\&L=1|F:fileadmin/pdf/test.pdf" AS _link
 
 ..
 
 Example `_pdf`, `_zip`: ::
 
-	# File 1: p:id=1&--orientation=Landscape&--page-size=A3
-	# File 2: p:id=form
-	# File 3: F:fileadmin/file.pdf
-	SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf
+  # File 1: p:id=1&--orientation=Landscape&--page-size=A3
+  # File 2: p:id=form
+  # File 3: F:fileadmin/file.pdf
+  SELECT 't:PDF|a:Creating a new PDF|p:id=1&--orientation=Landscape&--page-size=A3|p:id=form|F:fileadmin/file.pdf' AS _pdf
 
-	# File 1: p:id=1
-	# File 2: u:http://www.example.com
-	# File 3: F:fileadmin/file.pdf
-	SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf
+  # File 1: p:id=1
+  # File 2: u:http://www.example.com
+  # File 3: F:fileadmin/file.pdf
+  SELECT 't:PDF - 3 Files|a:Please be patient|p:id=1|u:http://www.example.com|F:fileadmin/file.pdf' AS _pdf
 
-	# File 1: p:id=1
-	# File 2: p:id=form
-	# File 3: F:fileadmin/file.pdf
-	SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip
+  # File 1: p:id=1
+  # File 2: p:id=form
+  # File 3: F:fileadmin/file.pdf
+  SELECT CONCAT('t:ZIP - 3 Pages|a:Please be patient|p:id=1|p:id=form|F:', p.pathFileName) AS _zip
 
 ..
 
@@ -6551,65 +6571,65 @@ Best practice:
 
 #. Create a clean (=no menu, no website layout) letter layout in a separated T3 branch: ::
 
-	page = PAGE
-	page.typeNum = 0
-	page.includeCSS {
-	  10 = typo3conf/ext/qfq/Resources/Public/Css/qfq-letter.css
-	}
+      page = PAGE
+      page.typeNum = 0
+      page.includeCSS {
+        10 = typo3conf/ext/qfq/Resources/Public/Css/qfq-letter.css
+      }
 
-	// Grant access to any logged in user or specific development IPs
-	[usergroup = *] || [IP = 127.0.0.1,192.168.1.* ]
-	  page.10 < styles.content.get
-	[else]
-	  page.10 = TEXT
-	  page.10.value = access forbidden
-	[global]
+      // Grant access to any logged in user or specific development IPs
+      [usergroup = *] || [IP = 127.0.0.1,192.168.1.* ]
+        page.10 < styles.content.get
+      [else]
+        page.10 = TEXT
+        page.10.value = access forbidden
+      [global]
 
 #. Create a T3 `body` page (e.g. page alias: 'letterbody') with some content. Example static HTML content: ::
 
-	<div class="letter-receiver">
-	  <p>Address</p>
-	</div>
-	<div class="letter-sender">
-	 <p><b>firstName name</b><br>
-	  Phone +00 00 000 00 00<br>
-	  Fax +00 00 000 00 00<br>
-	 </p>
-	</div>
-
-	<div class="letter-date">
-	  Zurich, 01.12.2017
-	</div>
-
-	<div class="letter-body">
-	 <h1>Subject</h1>
-
-	 <p>Dear Mrs...</p>
-	 <p>Lucas ipsum dolor sit amet organa solo skywalker darth c-3p0 anakin jabba mara greedo skywalker.</p>
-
-	 <div class="letter-no-break">
-	 <p>Regards</p>
-	 <p>Company</p>
-	 <img class="letter-signature" src="">
-	 <p>Firstname Name<br>Function</p>
-	 </div>
-	</div>
+      <div class="letter-receiver">
+        <p>Address</p>
+      </div>
+      <div class="letter-sender">
+       <p><b>firstName name</b><br>
+        Phone +00 00 000 00 00<br>
+        Fax +00 00 000 00 00<br>
+       </p>
+      </div>
+
+      <div class="letter-date">
+        Zurich, 01.12.2017
+      </div>
+
+      <div class="letter-body">
+       <h1>Subject</h1>
+
+       <p>Dear Mrs...</p>
+       <p>Lucas ipsum dolor sit amet organa solo skywalker darth c-3p0 anakin jabba mara greedo skywalker.</p>
+
+       <div class="letter-no-break">
+       <p>Regards</p>
+       <p>Company</p>
+       <img class="letter-signature" src="">
+       <p>Firstname Name<br>Function</p>
+       </div>
+      </div>
 
 #. Create a T3 letter-`header` page (e.g. page alias: 'letterheader') , with only the header information: ::
 
-		<header>
-		<img src="fileadmin/logo.png" class="letter-logo">
-
-		<div class="letter-unit">
-		  <p class="letter-title">Department</p>
-		  <p>
-			 Company name<br>
-			 Company department<br>
-			 Street<br>
-			 City
-		  </p>
-		</div>
-		</header>
+        <header>
+        <img src="fileadmin/logo.png" class="letter-logo">
+
+        <div class="letter-unit">
+          <p class="letter-title">Department</p>
+          <p>
+           Company name<br>
+           Company department<br>
+           Street<br>
+           City
+          </p>
+        </div>
+        </header>
 
 #. Create a) a link (Report) to the PDF letter or b) attach the PDF (on the fly rendered) to a mail. Both will call the
    `wkhtml` via the `download` mode and forwards the necessary parameter.
@@ -6626,7 +6646,7 @@ Use in `report`: ::
 
 Sendmail. Parameter: ::
 
-	sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }}
+  sendMailAttachment={{SELECT 'd:Letter.pdf|t:', p.firstName, ' ', p.name, '|p:id=letterbody&pId=', p.id, '&_sip=1&--margin-top=50mm&--margin-bottom=20mm&--header-html={{BASE_URL_PRINT:Y}}?id=letterheader&--footer-right="Seite: [page]/[toPage]"&--footer-font-size=8&--footer-spacing=10' FROM Person AS p WHERE p.id={{id:S}} }}
 
 Replace the static content elements from 2. and 3. by QFQ Content elements as needed: ::
 
@@ -6678,7 +6698,7 @@ Setup
 '''''
 
 * Create a special column name `_excel` (or `_link`) in QFQ/Report. As a source, define a T3 PageContent, which has to
-  deliver the dynamic content (also `_excel-export-sample`). ::
+  deliver the dynamic content (also `excel-export-sample`_). ::
 
     SELECT CONCAT('d:final.xlsx|M:excel|s:1|t:Excel (new)|uid:43') AS _link
 
@@ -6754,7 +6774,7 @@ In Report Syntax: ::
     30.sql = SELECT 'position=D30' AS _XLS,
                      '<some content with special characters like newline/carriage return>' AS _XLSb
 
-.. _excel-export-sample:
+.. _`excel-export-sample`:
 
 Excel export samples: ::
 
@@ -7312,16 +7332,16 @@ Recent List
 A nice feature is to show a list with last changed records. The following will show the 10 last modified (Form or
 FormElement) forms: ::
 
-	10 {
-	  sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id, '|t:', f.name,'|o:', GREATEST(MAX(fe.modified), f.modified)) AS _page
-				  FROM Form AS f
-				  LEFT JOIN FormElement AS fe ON fe.formId = f.id
-				  GROUP BY f.id
-				  ORDER BY GREATEST(MAX(fe.modified), f.modified) DESC
-				  LIMIT 10
-	  head = <h3>Recent Forms</h3>
-	  rsep = ,&ensp;
-	}
+  10 {
+    sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id, '|t:', f.name,'|o:', GREATEST(MAX(fe.modified), f.modified)) AS _page
+          FROM Form AS f
+          LEFT JOIN FormElement AS fe ON fe.formId = f.id
+          GROUP BY f.id
+          ORDER BY GREATEST(MAX(fe.modified), f.modified) DESC
+          LIMIT 10
+    head = <h3>Recent Forms</h3>
+    rsep = ,&ensp;
+  }
 
 .. _`store_user_examples`:
 
@@ -7403,6 +7423,293 @@ last used (STORE_USER) or (first time call during browser session) takes the def
       tail = </div><p></p>
     }
 
+
+.. _`restApi`:
+
+REST
+====
+
+Via `REST <https://en.wikipedia.org/wiki/Representational_state_transfer>`_ it's possible to access the QFQ based
+application. Each REST API endpoint has to be defined as a QFQ Form. The QFQ REST api implements the
+four most used REST HTTP methods:
+
+GET - Read
+    Shows a list of database records or a single record. The QFQ form holds the definition which and what to show.
+
+    List: ``curl -X GET "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/``
+
+    Data (id=123): ``curl -X GET "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/123``
+
+POST - Create new record
+    The QFQ form defines wich columns will be written in which table. Most of QFQ Form functionality can be used. Example:
+
+    ``curl -X POST "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/" -d '{"name":"Miller","firstname":"Joe"}'``
+
+PUT - Update a record
+    Similar to POST, but a given record will be updated.
+
+    ``curl -X PUT "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/123" -d '{"name":"Miller","firstname":"Joe"}'``
+
+DELETE - Delete a record
+    Similar to a QFQ Delete form.
+
+    ``curl -X DELETE "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/123"``
+
+All data will imported / exported in JSON notation.
+
+Any QFQ form becomes a REST form via:  ``Form > Access > Permit REST: get / insert / update / delete``
+
+Endpoint
+--------
+
+.. tip::
+
+    The basic REST API endpoint: ``<domain>/typo3conf/ext/qfq/Source/api/rest.ph``
+
+    ``<domain>/typo3conf/ext/qfq/Source/api/rest.php/<level1>/<id1>/<level2>/<id2>/.../?<var1>=<value1>&...``
+
+
+Append level names and ids after `.../rest.php/`, each separated by '/' .
+
+E.g.:
+
+1. List of all persons: `<domain>/typo3conf/ext/qfq/Source/api/rest.php/person`
+2. Data of person 123: `<domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123`
+3. Adresses of person 123: `<domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123/address`
+4. Adress details of address 45 from person 123: `<domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123/address/45`
+
+QFQ 'Forms' are used as a 'container' (to define all details).
+
+.. tip::
+
+    The QFQ ``form name`` represents  the level name.
+
+Only the last <level> of an URI will be processed. The former ones are just to fulfil a good looking REST API.
+
+.. note::
+
+    Each level name (=form name) is available via STORE_CLIENT and name `_formX`. E.g. in example
+    (1) `{{_form1:C:alnumx}}=person` and `{{_form2:C:alnumx}}=address`.
+
+    Each level id is available via STORE_CLIENT and name `_idX`. E.g. in example
+    (2) `{{_id1:C}}=123` and `{{_id2:C}}=45`.
+
+    Also the `id` after the last `level` in the URI path, 123 in example (2) and 45 in example (4), is copied to
+    variable `r` in STORE_TYPO3, access it via `{{r:T}}`.
+
+
+GET - Read
+----------
+
+A REST (GET) form has two modes:
+
+data
+    Specific content to a given id. Defined via 'form.parameter.restSqlData'. This mode is selected if there is an
+    id>0 given.
+
+list
+    A list of records will be exported.  Defined via 'form.parameter.restSqlList'. This mode is selected if there is no
+    id or id=0.
+
+.. note:
+
+    There are  *no* native-FormElements necessary or loaded. Action FormElements will be processed.
+
+To simplify access to id parameter of the URI, a mapping is possible via 'form.parameter.restParam'.
+E.g. `restParam=pId,adrId` with example d) makes `{{pId:C}}=123` and `{{adrId:C}}=45`. The order of variable
+names corresponds to the position in the URI. `_id1` is always mapped to the first parameter name, `_id2` to
+the second one and so on.
+
+GET Variables provided via URL are available via STORE_CLIENT as usual.
+
+Form:
+
++-------------------+------------------------------------------------------------------------------+
+| Attribute         | Description                                                                  |
++===================+==============================================================================+
+| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
++-------------------+------------------------------------------------------------------------------+
+| table             | Mandatory. Name of the primary table                                         |
++-------------------+------------------------------------------------------------------------------+
+| Permit REST       | *get* Mandatory. The form can be loaded in REST mode.                        |
++-------------------+------------------------------------------------------------------------------+
+
+
+Form.parameter:
+
++-------------------+----------------------------------------------------------------------------------+
+| Attribute         | Description                                                                      |
++===================+==================================================================================+
+| restSqlData       | Mandatory. SQL query selects content shown in data mode.                         |
+|                   | | ``restSqlData={{!SELECT id, name, gender FROM Person WHERE id='{{r:T0}}'' }}`` |
++-------------------+----------------------------------------------------------------------------------+
+| restSqlList       | Mandatory. SQL query selects content shown in data mode.                         |
+|                   | | ``restSqlData={{!SELECT id, name FROM Person }}``                              |
++-------------------+----------------------------------------------------------------------------------+
+| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
++-------------------+----------------------------------------------------------------------------------+
+| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
++-------------------+----------------------------------------------------------------------------------+
+
+.. note:
+
+    There are no `special-column-names`_ available in `restSqlData` or `restSqlList`. Also there are no
+    SIPs possible, cause REST typically does not offer sessions/cookies (which are necessary for SIPs).
+
+
+POST - Insert
+-------------
+
+Form:
+
++-------------------+------------------------------------------------------------------------------+
+| Attribute         | Description                                                                  |
++===================+==============================================================================+
+| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
++-------------------+------------------------------------------------------------------------------+
+| table             | Mandatory. Name of the primary table                                         |
++-------------------+------------------------------------------------------------------------------+
+| Permit REST       | *insert* Mandatory. The form can be loaded in REST mode.                     |
++-------------------+------------------------------------------------------------------------------+
+| id                | Missing or '0'.                                                              |
++-------------------+------------------------------------------------------------------------------+
+
+Form.parameter:
+
++-------------------+----------------------------------------------------------------------------------+
+| Attribute         | Description                                                                      |
++===================+==================================================================================+
+| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
++-------------------+----------------------------------------------------------------------------------+
+| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
++-------------------+----------------------------------------------------------------------------------+
+
+FormElement:
+
+* For each column to save one FormElement with ``FE.name=<column>`` is necessary.
+* A regular QFQ form can be used as REST Post endpoint
+
+
+PUT - Update
+------------
+
+Form:
+
++-------------------+------------------------------------------------------------------------------+
+| Attribute         | Description                                                                  |
++===================+==============================================================================+
+| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
++-------------------+------------------------------------------------------------------------------+
+| table             | Mandatory. Name of the primary table                                         |
++-------------------+------------------------------------------------------------------------------+
+| Permit REST       | *update* Mandatory. The form can be loaded in REST mode.                     |
++-------------------+------------------------------------------------------------------------------+
+| id                | >0                                                                           |
++-------------------+------------------------------------------------------------------------------+
+
+Form.parameter:
+
++-------------------+----------------------------------------------------------------------------------+
+| Attribute         | Description                                                                      |
++===================+==================================================================================+
+| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
++-------------------+----------------------------------------------------------------------------------+
+| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
++-------------------+----------------------------------------------------------------------------------+
+
+FormElement:
+
+* For each column to save one FormElement with ``FE.name=<column>`` is necessary.
+* A regular QFQ form can be used as REST Post endpoint
+
+DELETE - Delete
+---------------
+
+Form:
+
++-------------------+------------------------------------------------------------------------------+
+| Attribute         | Description                                                                  |
++===================+==============================================================================+
+| name              | *<level>* Mandatory. Level name (Endpoint) in URI.                           |
++-------------------+------------------------------------------------------------------------------+
+| table             | Mandatory. Name of the primary table                                         |
++-------------------+------------------------------------------------------------------------------+
+| Permit REST       | *delete* Mandatory. The form can be loaded in REST mode.                     |
++-------------------+------------------------------------------------------------------------------+
+| id                | >0                                                                           |
++-------------------+------------------------------------------------------------------------------+
+
+Form.parameter:
+
++-------------------+----------------------------------------------------------------------------------+
+| Attribute         | Description                                                                      |
++===================+==================================================================================+
+| restParam         | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId``              |
++-------------------+----------------------------------------------------------------------------------+
+| restToken         | Optional. User defined string or dynamic token (see `restAuthorization`_).       |
++-------------------+----------------------------------------------------------------------------------+
+
+.. note:
+
+    There are  *no* native-FormElements necessary - but might exist for dependent records to delete. Action FormElements
+    will be processed.
+
+
+.. _`restAuthorization`:
+
+Authorization
+-------------
+
+A QFQ form is only acessible via REST API, if ``Form.permitRest`` enables on of the HTTP Method: **get, post, put, delete**
+
+``Permit New`` or ``Permit Edit`` don't apply to QFQ forms called via REST.
+
+.. important::
+
+    By default, the REST API is public accessible.
+
+If this is not wished:
+
+* HTTP AUTH might be used (configured via webserver)
+* Any other webserver based access restriction method
+* or the QFQ internal 'HTTP header token based authorization'.
+
+Token based authorization
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A form will require a 'token based authorization', as soon as there is a ``form.parameter.restToken`` defined.
+Therefore the HTTP Header 'Authorization' has to be set with ``token=<secret token>``. The 'secret token' will
+be checked against the server.
+
+Example: ::
+
+  form.parameter.restToken=myCrypticString0123456789
+
+  Test via commandline: curl -X GET -H 'Authorization: Token token=myCrypticString0123456789' "http://localhost/qfq/typo3conf/ext/qfq/Source/api/rest.php/person/123/address/"
+
+The static setup with ``form.parameter.restToken=myCrypticString0123456789`` is fine, as long as only one token
+exist. In case of multiple tokens, replace the static string against a SQL query.
+
+.. tip::
+
+    The HTML Header Authorization token is available in STORE_CLIENT via '`{{Authorization:C:alnumx}}`.
+
+Best Practice: For example all created tokens are saved in a table 'Auth' with a column 'token'. Define::
+
+  form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' }}
+
+To restrict access to a subset of data, just save the limitations inside the Auth record and update the query
+to check it:
+
+.. code-block:: mysql
+
+  form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}'}}
+  form.parameter.restSqlList={{!SELECT p.id, p.name, p.email FROM Person AS p, Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' AND a.attribute=p.attribute}}
+  form.parameter.restSqlData={{!SELECT p.* FROM Person AS p, Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' AND a.attribute=p.attribute AND p.id='{{r:T0}}' }}
+
+If authorization is denied, the request will be answered with a delay of 3 seconds (configured via securityFailedAuthDelay).
+
+
 .. _`system`:
 
 System
@@ -7460,7 +7767,8 @@ to edit `AutoCron` jobs: ::
 
     10 {
         # Table header.
-        sql = SELECT CONCAT('p:{{pageAlias:T}}&form=cron') AS _pagen, 'id', 'Next run','Frequency','Comment','Last run','In progress', 'Status' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'=''
+        sql = SELECT CONCAT('p:{{pageAlias:T}}&form=cron') AS _pagen, 'id', 'Next run','Frequency','Comment',
+                     'Last run','In progress', 'Status' FROM (SELECT 1) AS fake WHERE '{{form:SE}}'=''
         head = <table class='table table-hover qfq-table-50'>
         tail = </table>
         rbeg = <thead><tr>
@@ -7570,16 +7878,16 @@ Access restriction
 To protect AutoCron pages not to be triggered accidental or by unprivileged access, access to those page tree might be
 limited to localhost. Some example Typoscript: ::
 
-	# Access allowed for any logged in user or via 'localhost'
-	[usergroup = *] || [IP = 127.0.0.1]
-	  page.10 < styles.content.get
-	[else]
-	  # Error Message
-	  page.10 = TEXT
-	  page.10.value = <h2>Access denied</h2>Please log in or access this page from an authorized host. Your current IP address:&nbsp;
-	  page.20 = TEXT
-	  page.20.data = getenv : REMOTE_ADDR
-	[global]
+  # Access allowed for any logged in user or via 'localhost'
+  [usergroup = *] || [IP = 127.0.0.1]
+    page.10 < styles.content.get
+  [else]
+    # Error Message
+    page.10 = TEXT
+    page.10.value = <h2>Access denied</h2>Please log in or access this page from an authorized host. Your current IP address:&nbsp;
+    page.20 = TEXT
+    page.20.data = getenv : REMOTE_ADDR
+  [global]
 
 
 
@@ -7591,95 +7899,6 @@ AutoCron / website: HTTPS protocol
   This is useful if there is a general 'HTTP >> HTTPS' redirection configured and the website is accessed via `https://localhost/...`
 
 
-
-.. _`rest`:
-
-REST
-----
-
-QFQ offers an API endpoint for GET (and later POST,PUT,DELETE) operations. ::
-
-  <domain>/typo3conf/ext/qfq/Source/api/rest.php/<level1>/<id1>/<level2>/<id2>/.../?<var1>=<value1>&...
-
-Append level names and ids after 'rest.php/...', separated by '/' each.
-
-E.g.:
-
-a) List of all persons: <domain>/typo3conf/ext/qfq/Source/api/rest.php/person
-b) Data of person 123: <domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123
-c) Adresses of person 123: <domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123/address
-d) Adress details of address 45 from person 123: <domain>/typo3conf/ext/qfq/Source/api/rest.php/person/123/address/45
-
-
-QFQ 'Forms' are used as a 'container' to configure all necessary export/import details per 'level'.
-Each 'level' is represented by a QFQ Form.
-
-Only the last <level> of an URI will be processed. The former ones are just to fullfil a good looking REST API.
-
-Important:  the level name is the QFQ form name.
-
-Each level name (=form name) is available via STORE_CLIENT and name '_formX'. E.g. in example
-d) '{{_form1:C:alnumx}}'='person' and '{{_form2:C:alnumx}}'='address'.
-
-Each level id  is available via STORE_CLIENT and name '_idX'. E.g. in example
-d) '{{_id1:C}}'='123' and '{{_id2:C}}'='45'.
-
-Also the 'id' after the last 'level' in the URI path (123 in example b), and 45 in example d) ) is copied to
-variable 'r' in STORE_TYPO3, access it via '{{r:T}}'.
-
-
-Export (GET)
-^^^^^^^^^^^^
-
-All data is exported in JSON notation.
-
-A REST (GET) form has two modes: ::
-
-  a) data: specific content to a given id. Defined via 'form.parameter.restSqlData'. This mode is selected
-     if there is an id>0 given.
-
-  b) list: a list of records will be exported. Defined via 'form.parameter.restSqlList'. This mode is selected if there is no id or id=0.
-
-There are  *no* FormElements.
-
-To simplify access to id parameter of the URI, a mapping is possible via 'form.parameter.restParam'.
-E.g. 'restParam=pId,adrId' with example d) makes '{{pId:C}}=123' and '{{adrId:C}}=45'. The order of variable
-names corresponds to the position in the URI. _id1 is always mapped to the first parameter name, _id2 to
-the second one and so on.
-
-GET Variables provided via URL are available via STORE_CLIENT as usual.
-
-Form:
-
-+-------------------+------------------------------------------------------------------------------+
-| Attribute         | Description                                                                  |
-+===================+==============================================================================+
-| name=<level>      | Level name in URI                                                            |
-+-------------------+------------------------------------------------------------------------------+
-| permitNew=rest    | The form can be loaded in REST mode with mising parameter 'id' or 'id=0'     |
-+-------------------+------------------------------------------------------------------------------+
-| permitEdit=rest   | The form can be loaded in REST mode with parameter 'id' > 0                  |
-+-------------------+------------------------------------------------------------------------------+
-
-
-Form.parameter:
-
-+-------------------+------------------------------------------------------------------------------+
-| Attribute         | Description                                                                  |
-+===================+==============================================================================+
-| restSqlData       | SQL query selects content shown in data mode.                                |
-|                   | restSqlData={{!SELECT id, name, gender FROM Person WHERE id={{r:T0}} }}      |
-+-------------------+------------------------------------------------------------------------------+
-| restSqlList       | SQL query selects content shown in data mode.                                |
-|                   | restSqlData={{!SELECT id, name FROM Person }}                                |
-+-------------------+------------------------------------------------------------------------------+
-| restParam         | CSV list of variable names.                                                  |
-|                   | restParam=pId,adrId                                                          |
-+-------------------+------------------------------------------------------------------------------+
-
-There are no `special-column-names`_ available in 'restSqlData' or 'restSqlList'. Especially there are no
-SIPs possible, cause REST typically does not offer sessions/cookies which are needed for SIPs.
-
 .. _applicationTest:
 
 Application Test
@@ -7687,7 +7906,7 @@ Application Test
 
 With a framework like https://www.seleniumhq.org/ it's possible to play and verify unattended  test cases.
 
-To assist such frameworks and to make the tests reliable, an individual tag might be assigned to elements which have to
+To assist such frameworks and to make the tests reliable, an individual tag might be assigned to HTML elements which have to
 interact with the test framework.
 
 Form
@@ -7700,7 +7919,9 @@ Report
 ------
 
 Any HTML output can be extended by a tag - that's done by the webmaster. For QFQ generated links, an attribute like
-'data-reference' might be injected via token 'A' (attribute). ::
+'data-reference' might be injected via token 'A' (attribute).
+
+.. code-block:: mysql
 
    SELECT 'p:personedit&form=person&r=1|b|s|A:data-reference="person-edit"|t:Edit person' AS _link
 
@@ -7720,8 +7941,8 @@ Tips:
 
 * On general errors:
 
-	* Always check the Javascript console of your browser, see `javascriptProblem`_.
-	* Always check the Webserver log files.
+  * Always check the Javascript console of your browser, see `javascriptProblem`_.
+  * Always check the Webserver log files.
 
 Caching
 -------
diff --git a/extension/Source/api/rest.php b/extension/Source/api/rest.php
index 6fe01d700ada07bb8b880e816cf3e9985cda772e..ff99752e5470403b24b2d49544fbd9b3b2ab6e80 100644
--- a/extension/Source/api/rest.php
+++ b/extension/Source/api/rest.php
@@ -15,34 +15,101 @@ require_once(__DIR__ . '/../core/exceptions/UserFormException.php');
 require_once(__DIR__ . '/../core/exceptions/CodeException.php');
 require_once(__DIR__ . '/../core/exceptions/DbException.php');
 
-$restId=array();
-$restForm=array();
+$restId = array();
+$restForm = array();
+
+$status = HTTP_400_BAD_REQUEST;
+$data = array();
 
 try {
     try {
         $form = OnString::splitPathInfoToIdForm($_SERVER['PATH_INFO'], $restId, $restForm);
 
-        $id=end($restId);
-                    // Fake Bodytext setup
+        // get latest `ìd`
+        $id = end($restId);
+
+        // Fake Bodytext setup
         $bodytext = TYPO3_RECORD_ID . '=' . $id . PHP_EOL;
         $bodytext .= TYPO3_FORM . '=' . $form . PHP_EOL;
 
+        $method = $_SERVER['REQUEST_METHOD'];
+        switch ($method) {
+            case REQUEST_METHOD_GET:
+                $status = HTTP_200_OK;
+                break;
+
+            case REQUEST_METHOD_POST:
+                if ($id != 0) {
+                    throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id>0 with HTTP method $method",
+                        ERROR_MESSAGE_SUPPORT => '',
+                        ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
+                    ]), ERROR_REST_INVALID_ID);
+                }
+
+                $data = json_decode(file_get_contents('php://input'), true);
+                $status = HTTP_201_CREATED;
+                break;
+
+            case REQUEST_METHOD_PUT:
+                if ($id == 0) {
+                    throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id==0 with HTTP method $method",
+                        ERROR_MESSAGE_SUPPORT => '',
+                        ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
+                    ]), ERROR_REST_INVALID_ID);
+                }
+                $data = json_decode(file_get_contents('php://input'), true);
+                $status = HTTP_200_OK;
+                break;
+
+            case REQUEST_METHOD_DELETE:
+                if ($id == 0) {
+                    throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id==0 with HTTP method $method",
+                        ERROR_MESSAGE_SUPPORT => '',
+                        ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
+                    ]), ERROR_REST_INVALID_ID);
+                }
+                $status = HTTP_200_OK;
+                break;
+
+            default:
+                throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Unsupported/unknown HTTP request method',
+                    ERROR_MESSAGE_SUPPORT => 'HTTP Code: ' . $method,
+                    ERROR_MESSAGE_HTTP_STATUS => HTTP_403_METHOD_NOT_ALLOWED
+                ]), ERROR_UNKNOWN_MODE);
+                break;
+        }
+
+        if ($data === null) {
+            throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Missing or broken JSON",
+                ERROR_MESSAGE_SUPPORT => json_last_error_msg(),
+                ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
+            ]), ERROR_BROKEN_PARAMETER);
+        }
+
+        if (!empty($data)) {
+            $_POST = $data;
+        }
+
         $qfq = new QuickFormQuery(['bodytext' => $bodytext]);
         $answer = $qfq->rest($restId, $restForm);
 
     } catch (qfq\CodeException $e) {
         $answer[API_MESSAGE] = $e->formatMessage();
+        $status = $e->getHttpStatus();
 
     } catch (qfq\UserFormException $e) {
         $answer[API_MESSAGE] = $e->formatMessage();
+        $status = $e->getHttpStatus();
 
     } catch (qfq\DbException $e) {
         $answer[API_MESSAGE] = $e->formatMessage();
-
+        $status = $e->getHttpStatus();
     }
+
 } catch (\Exception $e) {
     $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage();
 }
+
+header('HTTP/1.0 ' . $status);
 header("Content-Type: application/json");
 echo json_encode($answer);
-
diff --git a/extension/Source/core/Constants.php b/extension/Source/core/Constants.php
index 575eb38369e0a70c61b3931fc243427a12ac3a9d..38ad1f896e425937a35a13e3137b4284c2a18d50 100644
--- a/extension/Source/core/Constants.php
+++ b/extension/Source/core/Constants.php
@@ -67,7 +67,7 @@ const SQL_FORM_ELEMENT_BY_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ?"
 const SQL_FORM_ELEMENT_RAW = "SELECT * FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
 const SQL_FORM_ELEMENT_SPECIFIC_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.feIdContainer = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
 const SQL_FORM_ELEMENT_ALL_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
-const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.label, fe.type, fe.encode, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
+const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.value, fe.label, fe.type, fe.encode, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
 const SQL_FORM_ELEMENT_CONTAINER_TEMPLATE_GROUP = "SELECT fe.id, fe.name, fe.label, fe.maxLength, fe.parameter FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
 const SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ? AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ";
 //const SQL_FORM_ELEMENT_NATIVE_TG_COUNT = "SELECT fe.*, IFNULL(feTg.maxLength,0) AS _tgCopies FROM FormElement AS fe LEFT JOIN FormElement AS feTg ON fe.feIdContainer=feTg.id AND feTg.deleted = 'no' AND feTg.class = 'container' AND feTg.type='templateGroup' AND feTg.enabled='yes' WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes'";
@@ -141,6 +141,7 @@ const KVP_VALUE_GIVEN = 'value_given';
 const ERROR_MESSAGE_TO_USER = 'toUser'; // always shown to the user.
 const ERROR_MESSAGE_SUPPORT = 'support'; // Message to help the developer to understand the problem.
 const ERROR_MESSAGE_OS = 'os'; // Error message from the OS - like 'file not found' or specific SQL problem
+const ERROR_MESSAGE_HTTP_STATUS = 'httpStatus'; // HTTP Status Code to report
 
 // QFQ Error Codes
 const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
@@ -227,7 +228,6 @@ const ERROR_SMALLER_THAN_MIN = 1083;
 const ERROR_LARGER_THAN_MAX = 1084;
 const ERROR_INVALID_DECIMAL_FORMAT = 1085;
 const ERROR_INVALID_DATE = 1086;
-const ERROR_FORM_REST = 1087;
 
 // Subrecord
 const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
@@ -363,6 +363,10 @@ const ERROR_FORM_RESERVED_NAME = 2800;
 const ERROR_IMPORT_MISSING_EXPLICIT_TYPE = 2900;
 const ERROR_IMPORT_LIST_SHEET_NAMES = 2901;
 
+// REST
+const ERROR_FORM_REST = 3000;
+const ERROR_REST_AUTHORIZATION = 3001;
+const ERROR_REST_INVALID_ID = 3002;
 //
 // Store Names: Identifier
 //
@@ -418,12 +422,18 @@ const CLIENT_SERVER_ADDRESS = 'SERVER_ADDR';
 const CLIENT_SERVER_PORT = 'SERVER_PORT';
 const CLIENT_REMOTE_ADDRESS = 'REMOTE_ADDR';
 const CLIENT_REQUEST_SCHEME = 'REQUEST_SCHEME';
+const CLIENT_REQUEST_METHOD = 'REQUEST_METHOD';
 const CLIENT_SCRIPT_FILENAME = 'SCRIPT_FILENAME';
 const CLIENT_QUERY_STRING = 'QUERY_STRING';
 const CLIENT_REQUEST_URI = 'REQUEST_URI';
 const CLIENT_SCRIPT_NAME = 'SCRIPT_NAME';
 const CLIENT_PHP_SELF = 'PHP_SELF';
 
+const REQUEST_METHOD_GET = 'GET';
+const REQUEST_METHOD_POST = 'POST';
+const REQUEST_METHOD_PUT = 'PUT';
+const REQUEST_METHOD_DELETE = 'DELETE';
+
 // _COOKIE
 const CLIENT_COOKIE_QFQ = 'cookieQfq';
 
@@ -541,6 +551,7 @@ const SYSTEM_SECURITY_ATTACK_DELAY_DEFAULT = 5; // Detected attack causes x seco
 const SYSTEM_SECURITY_SHOW_MESSAGE = 'securityShowMessage'; // Detected attack shows an error message
 const SYSTEM_SECURITY_GET_MAX_LENGTH = 'securityGetMaxLength'; // Trim every character (before conversion) to SECURITY_GET_MAX_LENGTH chars;
 const SYSTEM_SECURITY_GET_MAX_LENGTH_DEFAULT = 50; // Default max length for get variables
+const SYSTEM_SECURITY_FAILED_AUTH_DELAY = 'securityFailedAuthDelay'; // Failed auth causes x seconds delay
 
 const GET_EXTRA_LENGTH_TOKEN = '_';
 
@@ -876,6 +887,7 @@ const F_TITLE = 'title';
 const F_TABLE_NAME = 'tableName';
 const F_PRIMARY_KEY = 'primaryKey';
 const F_PRIMARY_KEY_DEFAULT = 'id';
+const F_REST_METHOD = 'restMethod';
 const F_REQUIRED_PARAMETER_NEW = 'requiredParameterNew';
 const F_REQUIRED_PARAMETER_EDIT = 'requiredParameterEdit';
 const F_EXTRA_DELETE_FORM = 'extraDeleteForm';
@@ -991,6 +1003,7 @@ const F_SHOW_ID_IN_FORM_TITLE = SYSTEM_SHOW_ID_IN_FORM_TITLE;
 const F_REST_SQL_LIST = 'restSqlList';
 const F_REST_SQL_DATA = 'restSqlData';
 const F_REST_PARAM = 'restParam';
+const F_REST_TOKEN = 'restToken';
 const CLIENT_REST_ID = '_id';
 const CLIENT_REST_FORM = '_form';
 
@@ -1745,4 +1758,17 @@ const DND_SUBRECORD_FORM_ID = 'dnd-subrecord-form-id';
 const DND_ORD_HTML_ID_PREFIX = 'qfq-dnd-ord-id-';
 
 // Application Test: SELENIUM
-const ATTRIBUTE_DATA_REFERENCE = 'data-reference';
\ No newline at end of file
+const ATTRIBUTE_DATA_REFERENCE = 'data-reference';
+
+// REST
+const HTTP_HEADER_AUTHORIZATION = 'Authorization';
+
+const HTTP_200_OK = '200 OK';
+const HTTP_201_CREATED = '201 Created';
+
+const HTTP_400_BAD_REQUEST = '400 Bad Request';
+const HTTP_401_UNAUTHORIZED = '401 Unauthorized';
+const HTTP_403_FORBIDDEN = '403 Forbidden';
+const HTTP_403_METHOD_NOT_ALLOWED = '405 Method Not Allowed';
+const HTTP_404_NOT_FOUND = '404 Not Found';
+const HTTP_409_CONFLICT = '409 Conflict';
\ No newline at end of file
diff --git a/extension/Source/core/QuickFormQuery.php b/extension/Source/core/QuickFormQuery.php
index 5173c891cefc6daed567bf8165b84e3f21cad0e4..a514d4bbf35e72cad1e0978089be167ebf4b4a3e 100644
--- a/extension/Source/core/QuickFormQuery.php
+++ b/extension/Source/core/QuickFormQuery.php
@@ -168,8 +168,11 @@ class QuickFormQuery {
 
         $this->store = Store::getInstance($bodytext, $phpUnit);
 
+        $timeout = $this->store::getVar(SYSTEM_SESSION_TIMEOUT_SECONDS, STORE_SYSTEM);
+        Session::checkSessionExpired($timeout);
+
         // If an FE user logs out and a different user logs in (same browser session) - the old values has to be destroyed!
-        if (Session::getAndDestroyFlagFeUserHasChanged() || Session::checkSessionExpired($timeout)) {
+        if (Session::getAndDestroyFlagFeUserHasChanged()) {
             $this->store->unsetStore(STORE_USER);
         }
 
@@ -341,9 +344,13 @@ class QuickFormQuery {
         $flagApiStructureReGroup = true;
 
         // Fill STORE_FORM
-        if ($formMode === FORM_UPDATE || $formMode === FORM_SAVE) {
-            $fillStoreForm = new FillStoreForm();
-            $fillStoreForm->process($formMode);
+        switch ($formMode) {
+            case FORM_UPDATE:
+            case FORM_SAVE:
+            case FORM_REST:
+                $fillStoreForm = new FillStoreForm();
+                $fillStoreForm->process($formMode);
+                break;
         }
 
         $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT . STORE_ZERO);
@@ -365,12 +372,13 @@ class QuickFormQuery {
             }
         }
 
-        // Session Expire happens quite late, cause it can be configured per form.
+        // Check 'session expire' happens quite late, cause it can be configured per form.
         Session::checkSessionExpired($this->formSpec[F_SESSION_TIMEOUT_SECONDS]);
 
         if ($formName !== false) {
-            // Validate only if there is a 'real' form (not a FORM_DELETE with only a tablename).
-            $sipFound = $this->validateForm($foundInStore, $formMode);
+            // Validate (only if there is a 'real' form, not a FORM_DELETE with only a tablename).
+            // Attention: $formModeNew will be set
+            $sipFound = $this->validateForm($foundInStore, $formMode, $formModeNew);
 
         } else {
             // FORM_DELETE without a form definition: Fake the form with only a tableName.
@@ -411,10 +419,10 @@ class QuickFormQuery {
         }
 
         // Check (and release) dirtyRecord.
-        if ($formMode === FORM_DELETE || $formMode === FORM_SAVE) {
+        if ($formModeNew === FORM_DELETE || $formModeNew === FORM_SAVE) {
             $dirty = new Dirty(false, $this->dbIndexData, $this->dbIndexQfq);
 
-            $answer = $dirty->checkDirtyAndRelease($formMode, $this->formSpec[F_RECORD_LOCK_TIMEOUT_SECONDS],
+            $answer = $dirty->checkDirtyAndRelease($formModeNew, $this->formSpec[F_RECORD_LOCK_TIMEOUT_SECONDS],
                 $this->formSpec[F_DIRTY_MODE], $this->formSpec[F_TABLE_NAME], $this->formSpec[F_PRIMARY_KEY], $recordId, true);
 
             // In case of a conflict, return immediately
@@ -426,7 +434,7 @@ class QuickFormQuery {
         }
 
         // FORM_LOAD: if there is a foreign exclusive record lock - show form in F_MODE_READONLY mode.
-        if ($formMode === FORM_LOAD) {
+        if ($formModeNew === FORM_LOAD) {
             $dirty = new Dirty(false, $this->dbIndexData, $this->dbIndexQfq);
             $recordDirty = array();
             $rcLockFound = $dirty->getCheckDirty($this->formSpec[F_TABLE_NAME], $recordId, $recordDirty, $msg);
@@ -435,7 +443,7 @@ class QuickFormQuery {
             }
         }
 
-        switch ($formMode) {
+        switch ($formModeNew) {
             case FORM_DELETE:
                 $build = new Delete($this->dbIndexData);
                 break;
@@ -468,10 +476,10 @@ class QuickFormQuery {
         }
 
         $formAction = new FormAction($this->formSpec, $this->dbArray[$this->dbIndexData], $this->phpUnit);
-        switch ($formMode) {
+        switch ($formModeNew) {
             case FORM_LOAD:
                 $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
-                $data = $build->process($formMode);
+                $data = $build->process($formModeNew);
                 $tmpClass = is_numeric($this->formSpec[F_BS_COLUMNS]) ? ('col-md-' . $this->formSpec[F_BS_COLUMNS]) : $this->formSpec[F_BS_COLUMNS];
 //                $data = Support::wrapTag("<div class='" . 'col-md-' . $this->formSpec[F_BS_COLUMNS] . "'>", $data);
                 $data = Support::wrapTag('<div class="' . $tmpClass . '">', $data);
@@ -482,7 +490,7 @@ class QuickFormQuery {
             case FORM_UPDATE:
                 $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
                 // data['form-update']=....
-                $data = $build->process($formMode);
+                $data = $build->process($formModeNew);
                 $formAction->elements($recordId, $this->feSpecAction, FE_TYPE_AFTER_LOAD);
                 break;
 
@@ -497,7 +505,7 @@ class QuickFormQuery {
             case FORM_SAVE:
                 $this->logFormSubmitRequest();
 
-                $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);
+                $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3);
 
                 // Action: Before
                 $feTypeList = FE_TYPE_BEFORE_SAVE . ',' . ($recordId == 0 ? FE_TYPE_BEFORE_INSERT : FE_TYPE_BEFORE_UPDATE);
@@ -536,6 +544,11 @@ class QuickFormQuery {
                 // Action: Sendmail
                 $formAction->elements($rc, $this->feSpecAction, FE_TYPE_SENDMAIL);
 
+                if ($formMode == FORM_REST) {
+                    $data = ['id' => $rc];
+                    $flagApiStructureReGroup = false;
+                    break;
+                }
 
                 $customForward = $this->setForwardModePage();
 
@@ -560,10 +573,7 @@ class QuickFormQuery {
                     $feSpecNative = HelperFormElement::setLanguage($feSpecNative, $parameterLanguageFieldName);
                     $this->feSpecNative = HelperFormElement::setFeContainerFormElementId($feSpecNative, $this->formSpec[F_ID], $recordId);
 
-                    // Retrieve FE Values as JSON
-                    // $data['form-update']=...
-                    // $data = $build->process($formMode, $htmlElementNameIdZero);
-                    $data = $build->process($formMode, false, $this->feSpecNative);
+                    $data = $build->process($formModeNew, false, $this->feSpecNative);
                 }
                 break;
 
@@ -578,8 +588,8 @@ class QuickFormQuery {
                 break;
 
             case FORM_REST:
-                $data = $this->doRestGet();
                 $flagApiStructureReGroup = false;
+                $data = $this->doRestGet();
                 break;
 
             default:
@@ -604,7 +614,7 @@ class QuickFormQuery {
      */
     private function doRestGet() {
 
-        $this->copyGenericRestParamToNamed();
+        $this->nameGenericRestParam();
 
         $r = $this->store::getVar(TYPO3_RECORD_ID, STORE_TYPO3);
         $key = empty($r) ? F_REST_SQL_LIST : F_REST_SQL_DATA;
@@ -617,6 +627,43 @@ class QuickFormQuery {
 
     }
 
+    /**
+     * Checks if $serverToken matches HTTP_HEADER_AUTHORIZATION,
+     * If not: throw an exception.
+     *
+     * @param string|array $serverToken
+     * @throws CodeException
+     * @throws UserFormException
+     */
+    private function restCheckAuthToken($serverToken) {
+
+        // No serverToken: no check necessary
+        if ($serverToken === '') {
+            return;
+        }
+
+        $clientToken = $this->store::getVar(HTTP_HEADER_AUTHORIZATION, STORE_CLIENT, SANITIZE_ALLOW_ALL);
+        if ($serverToken === $clientToken) {
+            return;
+        }
+
+        // Delay before answering.
+        $seconds = $this->store::getVar(SYSTEM_SECURITY_FAILED_AUTH_DELAY, STORE_SYSTEM);
+        sleep($seconds);
+
+        if ($clientToken == false) {
+            throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Missing authorization token',
+                ERROR_MESSAGE_SUPPORT => "Missing HTTP Header: " . HTTP_HEADER_AUTHORIZATION,
+                ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
+            ]), ERROR_REST_AUTHORIZATION);
+        }
+
+        throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Authorization token not accepted',
+            ERROR_MESSAGE_SUPPORT => "Missing HTTP Header: " . HTTP_HEADER_AUTHORIZATION,
+            ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
+        ]), ERROR_REST_AUTHORIZATION);
+    }
+
     /**
      * STORE_CLIENT: copy parameter _id1,_id2,...,_idN to named variables, specified via $this->formSpec[F_REST_PARAM] (CSV list)
      *
@@ -624,7 +671,7 @@ class QuickFormQuery {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private function copyGenericRestParamToNamed() {
+    private function nameGenericRestParam() {
 
         $paramNames = explode(',', $this->formSpec[F_REST_PARAM] ?? '');
 
@@ -954,13 +1001,10 @@ class QuickFormQuery {
 
         HelperFormElement::explodeParameter($form, F_PARAMETER);
         unset($form[F_PARAMETER]);
-        if (isset($form[FE_FILL_STORE_VAR])) {
-            $fillStoreVar = $form[FE_FILL_STORE_VAR];
-            unset($form[FE_FILL_STORE_VAR]);
-        }
 
         // Save specific elements to be expanded later.
-        $parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, F_REST_SQL_LIST, F_REST_SQL_DATA]);
+        $parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, FE_FILL_STORE_VAR, F_REST_SQL_LIST, F_REST_SQL_DATA]);
+        $form[FE_FILL_STORE_VAR] = '';
         $form[F_FORWARD_PAGE] = '';
         $form[F_REST_SQL_LIST] = '';
         $form[F_REST_SQL_DATA] = '';
@@ -991,8 +1035,17 @@ class QuickFormQuery {
         // and for evaluating variables in the Form title
         $this->store->fillStoreWithRecord($form[F_TABLE_NAME], $recordId, $this->dbArray[$this->dbIndexData], $form[F_PRIMARY_KEY]);
 
+        // In case $form[F_REST_TOKEN] is a query which results to an empty answer; every token will fail.
+        $flagRestToken = !empty($form[F_REST_TOKEN]);
+
+        // Evaluate all fields
         $formSpec = $this->evaluate->parseArray($form);
 
+        // If it is empty, set it to true to force the TOKEN check (which will always fail)
+        if ($flagRestToken && $form[F_REST_TOKEN] == '') {
+            $form[F_REST_TOKEN] = true;
+        }
+
         $parameterLanguageFieldName = $this->store->getVar(SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME, STORE_SYSTEM);
         $formSpec = HelperFormElement::setLanguage($formSpec, $parameterLanguageFieldName);
 
@@ -1015,8 +1068,11 @@ class QuickFormQuery {
         !empty($form[FORM_LOG_ACTIVE]) && Logger::logFormLine($form, "F:$mode:evaluated:" . date('Y-m-d H:i:s'), $form, true);
 
         // Fire FE_FILL_STORE_VAR after the primary form record has been loaded
-        if (!empty($fillStoreVar)) {
-            $rows = $this->evaluate->parse($fillStoreVar, ROW_EXPECT_0_1);
+        if (!empty($formSpec[FE_FILL_STORE_VAR])) {
+
+            $rows = $this->evaluate->parse($formSpec[FE_FILL_STORE_VAR], ROW_EXPECT_0_1);
+            unset($formSpec[FE_FILL_STORE_VAR]);
+
             if (is_array($rows)) {
                 $this->store->appendToStore($rows, STORE_VAR);
                 // LOG
@@ -1053,6 +1109,7 @@ class QuickFormQuery {
 
             case FORM_SAVE:
             case FORM_UPDATE:
+            case FORM_REST:
                 $feSpecNative = $this->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec[F_ID]], $this->formSpec);
                 break;
 
@@ -1371,22 +1428,23 @@ class QuickFormQuery {
     }
 
     /**
-     * Check if loading of the given form is permitted. If not, throw an exception.
+     * Check if the form loading is permitted. If not, throw an exception.
      *
      * @param string $formNameFoundInStore
      * @param string $formMode
      *
      * @return bool 'true' if SIP exists, else 'false'
-     * @throws \qfq\CodeException
-     * @throws \qfq\UserFormException
-     * @internal param $foundInStore
+     * @throws CodeException
+     * @throws UserFormException
      */
-    private function validateForm($formNameFoundInStore, $formMode) {
+    private function validateForm($formNameFoundInStore, $formMode, &$formModeNew) {
+
+        $formModeNew = $formMode;
 
-        // Retrieve record_id either from SIP (prefered) or via URL
+        // Retrieve record_id either from SIP (preferred) or via URL
         $r = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT, '', $recordIdFoundInStore);
 
-        // If not found: Fake a definition in STORE_TYPO3.
+        // No record id: Fake a definition in STORE_TYPO3.
         if ($r === false) {
             $r = 0;
             $this->store->setVar(TYPO3_RECORD_ID, $r, STORE_TYPO3);
@@ -1406,38 +1464,63 @@ class QuickFormQuery {
             }
         }
 
-        switch ($permitMode) {
-            case  FORM_PERMISSION_SIP:
-                if (!$sipFound || $formNameFoundInStore !== STORE_SIP || $recordIdFoundInStore !== STORE_SIP) {
-                    throw new UserFormException("SIP Parameter needed for this form.", ERROR_SIP_NEEDED_FOR_THIS_FORM);
-                }
-                break;
-            case  FORM_PERMISSION_LOGGED_IN:
-                if (!$feUserLoggedIn) {
-                    throw new UserFormException("User not logged in.", ERROR_USER_NOT_LOGGED_IN);
-                }
-                break;
-            case FORM_PERMISSION_LOGGED_OUT:
-                if ($feUserLoggedIn) {
-                    throw new UserFormException("User logged in.", ERROR_USER_LOGGED_IN);
-                }
-                break;
-            case FORM_PERMISSION_ALWAYS:
-                break;
-            case FORM_PERMISSION_NEVER:
-                throw new UserFormException("Loading form forbidden.", ERROR_FORM_FORBIDDEN);
-                break;
-            case FORM_PERMISSION_REST:
-                if ($formMode != FORM_REST) {
-                    throw new UserFormException("Try to load a REST form", ERROR_FORM_REST);
-                }
-                break;
-            default:
-                throw new CodeException("Unknown permission mode: '" . $permitMode . "'", ERROR_FORM_UNKNOWN_PERMISSION_MODE);
-        }
+        if ($formMode == FORM_REST) {
+
+            $method = $this->store::getVar(CLIENT_REQUEST_METHOD, STORE_CLIENT);
+            if (false === Support::findInSet(strtolower($method), $this->formSpec[F_REST_METHOD])) {
+
+                throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Invalid HTTP method',
+                    ERROR_MESSAGE_SUPPORT => "Endpoint '" . $this->formSpec[F_NAME] . "' is not allowed with HTTP method '$method'",
+                    ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
+                ]), ERROR_FORM_REST);
+
+            }
+
+            $this->restCheckAuthToken($this->formSpec[F_REST_TOKEN] ?? '');
+
+            switch ($method) {
+                case REQUEST_METHOD_GET:
+                    break;
+
+                case REQUEST_METHOD_POST:
+                case REQUEST_METHOD_PUT:
+                    $formModeNew = FORM_SAVE;
+                    break;
+
+                case REQUEST_METHOD_DELETE:
+                    $formModeNew = FORM_DELETE;
+                    break;
+
+                default:
+                    throw new CodeException('This code should never be reached', ERROR_CODE_SHOULD_NOT_HAPPEN);
+            }
+
+        } else {
 
-        if ($formMode == FORM_REST && $permitMode != FORM_PERMISSION_REST) {
-            throw new UserFormException("Try to load a non-REST form in REST mode", ERROR_FORM_REST);
+            switch ($permitMode) {
+                case  FORM_PERMISSION_SIP:
+                    if (!$sipFound || $formNameFoundInStore !== STORE_SIP || $recordIdFoundInStore !== STORE_SIP) {
+                        throw new UserFormException("SIP Parameter needed for this form.", ERROR_SIP_NEEDED_FOR_THIS_FORM);
+                    }
+                    break;
+                case  FORM_PERMISSION_LOGGED_IN:
+                    if (!$feUserLoggedIn) {
+                        throw new UserFormException("User not logged in.", ERROR_USER_NOT_LOGGED_IN);
+                    }
+                    break;
+                case FORM_PERMISSION_LOGGED_OUT:
+                    if ($feUserLoggedIn) {
+                        throw new UserFormException("User logged in.", ERROR_USER_LOGGED_IN);
+                    }
+                    break;
+                case FORM_PERMISSION_ALWAYS:
+                    break;
+                case FORM_PERMISSION_NEVER:
+                    throw new UserFormException("Loading form forbidden.", ERROR_FORM_FORBIDDEN);
+                    break;
+                default:
+                    throw new CodeException("Unknown permission mode: '" . $permitMode . "'", ERROR_FORM_UNKNOWN_PERMISSION_MODE);
+            }
         }
 
         // Form Definition valid?
@@ -1449,6 +1532,7 @@ class QuickFormQuery {
             return $sipFound;
         }
         $sipArray = $this->store->getStore(STORE_SIP);
+
         // Check: requiredParameter: '' or 'form' or 'form,grId' or 'form #formname for form,grId'
         $requiredParameter = ($r > 0) ? $this->formSpec[F_REQUIRED_PARAMETER_EDIT] : $this->formSpec[F_REQUIRED_PARAMETER_NEW];
 
@@ -1827,7 +1911,7 @@ EOF;
 
     /**
      * @param array $restId
-     * @param array $restForm
+     * @param string $method
      * @return array|string
      * @throws CodeException
      * @throws DbException
@@ -1854,6 +1938,9 @@ EOF;
             $ii++;
         }
 
+        $this->store::setVar(SIP_FORM, end($restForm), STORE_SIP);
+        $this->store::setVar(SIP_RECORD_ID, end($restId), STORE_SIP);
+
         return $this->doForm(FORM_REST);
 
     }
diff --git a/extension/Source/core/Save.php b/extension/Source/core/Save.php
index 9df3c42d02f44e3ecc0ba40a22722df3c2dbf8cd..8372e7015a10ee9f3a177998b14811e077dc35ed 100644
--- a/extension/Source/core/Save.php
+++ b/extension/Source/core/Save.php
@@ -64,7 +64,7 @@ class Save {
     }
 
     /**
-     * Starts save process. On succcess, returns forwardmode/page.
+     * Starts save process. Returns recordId.
      *
      * @return int
      * @throws CodeException
@@ -386,6 +386,8 @@ class Save {
                 // No new upload and no existing: take care to remove previous upload file statistics.
                 $this->store->unsetVar(VAR_FILE_MIME_TYPE, STORE_VAR);
                 $this->store->unsetVar(VAR_FILE_SIZE, STORE_VAR);
+                $vars[VAR_FILE_SIZE] = 0;
+                $vars[VAR_FILE_MIME_TYPE] = '';
             } else {
                 $vars = HelperFile::getFileStat($pathFileNameTmp);
                 $this->store->appendToStore($vars, STORE_VAR);
@@ -823,8 +825,7 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private
-    function copyUploadFile(array $formElement, array $statusUpload) {
+    private function copyUploadFile(array $formElement, array $statusUpload) {
         $pathFileName = '';
 
         if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
@@ -887,8 +888,7 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private
-    function autoOrient(array $formElement, $pathFileName) {
+    private function autoOrient(array $formElement, $pathFileName) {
 
         // 'autoOrient' wished?
         if (!isset($formElement[FE_FILE_AUTO_ORIENT]) || $formElement[FE_FILE_AUTO_ORIENT] == '0') {
@@ -929,17 +929,16 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private
-    function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
+    private function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
 
         if (empty($formElement[FE_FILE_SPLIT]) || $statusUpload[FILES_TYPE] != MIME_TYPE_SPLIT_CAPABLE) {
             return;
         }
 
-        $fileDestinationSplit = $this->evaluate->parse($formElement[FE_FILE_DESTINATION_SPLIT]);
-        $fileSplitType = $this->evaluate->parse($formElement[FE_FILE_SPLIT]);
-        $fileSplitTypeOptions = $this->evaluate->parse($formElement[FE_FILE_SPLIT_OPTIONS]);
-        $fileSplitTableName = $this->evaluate->parse($formElement[FE_FILE_SPLIT_TABLE_NAME]);
+        $fileDestinationSplit = $this->evaluate->parse($formElement[FE_FILE_DESTINATION_SPLIT] ?? '');
+        $fileSplitType = $this->evaluate->parse($formElement[FE_FILE_SPLIT] ?? '');
+        $fileSplitTypeOptions = $this->evaluate->parse($formElement[FE_FILE_SPLIT_OPTIONS] ?? '');
+        $fileSplitTableName = $this->evaluate->parse($formElement[FE_FILE_SPLIT_TABLE_NAME] ?? '');
 
         if (empty($fileSplitTableName)) {
             $fileSplitTableName = $this->formSpec[F_TABLE_NAME];
@@ -956,12 +955,10 @@ class Save {
         $cwd = getcwd();
 
         // Create temporary directory
-        $tempDir = Support::createTempDir();
+        $tempDir = HelperFile::mktempdir();
         $newSrc = $tempDir . DIRECTORY_SEPARATOR . QFQ_TEMP_SOURCE;
         HelperFile::copy($pathFileName, $newSrc);
 
-        HelperFile::chdir($tempDir);
-
         // Split destination.
         $pathParts = pathinfo($fileDestinationSplit);
         if (empty($pathParts['filename']) || empty($pathParts['basename'])) {
@@ -986,7 +983,10 @@ class Save {
         }
 
         // Split PDF
+        HelperFile::chdir($tempDir);
         $output = Support::qfqExec($cmd, $rc);
+        HelperFile::chdir($cwd);
+
         if ($rc != 0) {
             throw new UserFormException(
                 json_encode([ERROR_MESSAGE_TO_USER => 'pdf2svg failed', ERROR_MESSAGE_SUPPORT => "[$cwd][cmd=$cmd]$output"]),
@@ -994,7 +994,7 @@ class Save {
         }
 
         // Array of created file names.
-        if (false === ($files = scandir('.'))) {
+        if (false === ($files = scandir($tempDir))) {
             throw new UserFormException(
                 json_encode([ERROR_MESSAGE_TO_USER => 'Splitted files not found', ERROR_MESSAGE_SUPPORT => "[cwd=$cwd] scandir(.)" . HelperFile::errorGetLastAsString()]),
                 ERROR_PDF2JPEG);
@@ -1025,16 +1025,13 @@ class Save {
                 $fileDestination = $file;
             }
 
-            Support::moveFile($file, Support::joinPath($cwd, $fileDestination), true);
+            Support::moveFile($tempDir . DIRECTORY_SEPARATOR . $file, Support::joinPath($cwd, $fileDestination), true);
             HelperFile::chmod($fileDestination, $chmod);
 
             // Insert records.
             $this->db->sql($sql, ROW_REGULAR, [$fileSplitTableName, $xId, $fileDestination]);
         }
 
-        // Pop directory
-        HelperFile::chdir($cwd);
-
         // Remove duplicated source
         HelperFile::unlink($newSrc);
 
@@ -1053,8 +1050,7 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private
-    function doUploadSlave(array $fe, $modeUpload) {
+    private function doUploadSlave(array $fe, $modeUpload) {
         $sql = '';
         $flagUpdateSlaveId = false;
         $flagSlaveDeleted = false;
diff --git a/extension/Source/core/database/DatabaseUpdateData.php b/extension/Source/core/database/DatabaseUpdateData.php
index 7974419353681611a72830f235260dc41c27c78f..464916771b2923f15b2669433d91ee8d988199ef 100644
--- a/extension/Source/core/database/DatabaseUpdateData.php
+++ b/extension/Source/core/database/DatabaseUpdateData.php
@@ -136,7 +136,11 @@ $UPDATE_ARRAY = array(
         "ALTER TABLE `Form` ADD `labelAlign` ENUM('default','left','center','right') NOT NULL DEFAULT 'default' AFTER `forwardPage`;",
         "ALTER TABLE `FormElement` ADD `labelAlign` ENUM('default','left','center','right') NOT NULL DEFAULT 'default' AFTER `maxLength`;",
     ],
-    
+
+    '19.2.3' => [
+        "ALTER TABLE `Form` ADD `restMethod` SET('get','post','put','delete') NOT NULL DEFAULT '' AFTER `permitEdit`; ",
+    ],
+
 );
 
 
diff --git a/extension/Source/core/exceptions/AbstractException.php b/extension/Source/core/exceptions/AbstractException.php
index cb0243ba3a3dc25c65559d249df26ba82a6459f5..000192af75da38767db9beddf4899baf7ca2e966 100644
--- a/extension/Source/core/exceptions/AbstractException.php
+++ b/extension/Source/core/exceptions/AbstractException.php
@@ -29,10 +29,11 @@ require_once(__DIR__ . '/../helper/Support.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                    ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                ERROR_IO_CHMOD);
  *
  * @package qfq
  */
@@ -46,6 +47,8 @@ class AbstractException extends \Exception {
     protected $file = '';
     protected $line = '';
 
+    protected $httpStatusCode = '400 Bad Request';
+
     /**
      * $this->getMessage() might give
      *   a) a simple string, or
@@ -92,10 +95,17 @@ class AbstractException extends \Exception {
         $msg = $this->getMessage();
         $arrMsg = json_decode($msg, true);
         if ($arrMsg === null) {
+
             $arrShow[EXCEPTION_MESSAGE] = $msg;
             $arrMsg[ERROR_MESSAGE_TO_USER] = $msg;
+
         } else {
             $arrShow[EXCEPTION_MESSAGE] = $arrMsg[ERROR_MESSAGE_TO_USER];
+
+            if (isset($arrMsg[ERROR_MESSAGE_HTTP_STATUS])) {
+                $this->httpStatusCode = $arrMsg[ERROR_MESSAGE_HTTP_STATUS];
+            }
+
         }
 
         $arrDebugHidden[EXCEPTION_FILE] = $this->getFile();
@@ -137,7 +147,7 @@ class AbstractException extends \Exception {
 
                 if (!empty($os = $arrMerged[ERROR_MESSAGE_OS] ?? '')) {
                     // [ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist
-                    $before=$this->getTableToken( html_entity_decode($arrMerged[ERROR_MESSAGE_OS],ENT_QUOTES));
+                    $before = $this->getTableToken(html_entity_decode($arrMerged[ERROR_MESSAGE_OS], ENT_QUOTES));
                     $arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1146', $arrMerged[EXCEPTION_SQL_FINAL], $before, "' doesn't exist");
                     $arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1064', $arrMerged[EXCEPTION_SQL_FINAL], "the right syntax to use near '", "' at line [0-9]*$");
                     // [ mysqli: 1054 ] Unknown column "noPsp.pspElement' in 'field list" | "... in 'order clause'"
@@ -173,6 +183,13 @@ class AbstractException extends \Exception {
 
     }
 
+    /**
+     * @return string
+     */
+    public function getHttpStatus() {
+        return $this->httpStatusCode;
+    }
+
     /**
      * Extract 'beforeMatch', incl. dynamic db name as token to do underlining later.
      * E.g.:  "[ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist"
@@ -184,8 +201,8 @@ class AbstractException extends \Exception {
     private function getTableToken($os) {
         $subject = "Table '.*' ";
         $arr = preg_match("/$subject/", $os, $matches);
-        $arr= explode('.', $matches[0]??'');
-        return ($arr[0]??'') . '.';
+        $arr = explode('.', $matches[0] ?? '');
+        return ($arr[0] ?? '') . '.';
     }
 
     /**
diff --git a/extension/Source/core/exceptions/CodeException.php b/extension/Source/core/exceptions/CodeException.php
index 125a96331a85a93c5fbe6b1a099337a67c091fa6..cea02cc255c063c4f5508ae0a0a5b4891710408a 100644
--- a/extension/Source/core/exceptions/CodeException.php
+++ b/extension/Source/core/exceptions/CodeException.php
@@ -21,10 +21,10 @@ require_once(__DIR__ . '/AbstractException.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
+                                ERROR_IO_CHMOD);
  *
  * @package qfq\exceptions
  */
diff --git a/extension/Source/core/exceptions/DbException.php b/extension/Source/core/exceptions/DbException.php
index b9d07e0985e46a0dc70aa9d65239685c9776a524..bfbbfae13088bbbcae3a6dc85cd933afd289f5de 100644
--- a/extension/Source/core/exceptions/DbException.php
+++ b/extension/Source/core/exceptions/DbException.php
@@ -38,6 +38,13 @@ class DbException extends AbstractException {
      *   [ERROR_MESSAGE_TO_USER] 'toUser' - shown in the client to the user - no details here!!!
      *   [ERROR_MESSAGE_SUPPORT] 'support' - help for the developer
      *   [ERROR_MESSAGE_OS] 'os' - message from the OS, like 'file not found'
+     *
+      throw new UserFormException(  json_encode(
+                                       [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                        ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                        ERROR_MESSAGE_OS => 'os' - message from the OS, like 'file not found'
+                                        ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                    ERROR_IO_CHMOD);
      *
      * @return string HTML formatted error string
      * @return string
diff --git a/extension/Source/core/exceptions/DownloadException.php b/extension/Source/core/exceptions/DownloadException.php
index 15c3d767a69203940245d98eeeb60c4ed6a214be..cb9a76def4ce437c5cd2b6d662faba131d53c2c0 100644
--- a/extension/Source/core/exceptions/DownloadException.php
+++ b/extension/Source/core/exceptions/DownloadException.php
@@ -21,10 +21,11 @@ require_once(__DIR__ . '/AbstractException.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                    ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                ERROR_IO_CHMOD);
  *
  * @package qfq\exceptions
  */
diff --git a/extension/Source/core/exceptions/ShellException.php b/extension/Source/core/exceptions/ShellException.php
index 4b98a6daaaf02de9f109f34561f9e04282f26b10..77e131852b4f896bce54408a12ab6164af013561 100644
--- a/extension/Source/core/exceptions/ShellException.php
+++ b/extension/Source/core/exceptions/ShellException.php
@@ -21,10 +21,11 @@ require_once(__DIR__ . '/AbstractException.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                    ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                ERROR_IO_CHMOD);
  *
  * @package qfq\exceptions
  */
diff --git a/extension/Source/core/exceptions/UserFormException.php b/extension/Source/core/exceptions/UserFormException.php
index f2711d62fc4d17f26b04f4b90d7559dca9eaeb79..855baacd3f790b8b5c6eb1048f93d90b8d263ad1 100644
--- a/extension/Source/core/exceptions/UserFormException.php
+++ b/extension/Source/core/exceptions/UserFormException.php
@@ -22,10 +22,11 @@ require_once(__DIR__ . '/AbstractException.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                    ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                ERROR_IO_CHMOD);
  *
  * Call
  *
diff --git a/extension/Source/core/exceptions/UserReportException.php b/extension/Source/core/exceptions/UserReportException.php
index c3f5bdc1b5cc4643f66666cb3f1e6400fed322e5..3f10a32f986c52697a715de774885259191e6652 100644
--- a/extension/Source/core/exceptions/UserReportException.php
+++ b/extension/Source/core/exceptions/UserReportException.php
@@ -21,10 +21,11 @@ require_once(__DIR__ . '/AbstractException.php');
  *
  * Throw with message for User and message for Support.
  *
- * throw new UserFormException(  json_encode(
- *                                  [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
- *                                   ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
- *                               ERROR_IO_CHMOD);
+  throw new UserFormException(  json_encode(
+                                   [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
+                                    ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
+                                    ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
+                                ERROR_IO_CHMOD);
  *
  * @package qfq\exceptions
  */
diff --git a/extension/Source/core/helper/HelperFile.php b/extension/Source/core/helper/HelperFile.php
index fffaa38db2b4f1256d04779154e49f6336a5b75f..5f7e89464c0003cab4e602c9df31cb524e192b8a 100644
--- a/extension/Source/core/helper/HelperFile.php
+++ b/extension/Source/core/helper/HelperFile.php
@@ -61,6 +61,8 @@ class HelperFile {
 
     /**
      * Creates a temporary directory.
+     * Be aware: '/tmp' is under systemd/apache2 (Ubuntu 18...) remapped to something like: '/tmp/systemd-private-...-apache2.service-.../tmp'
+     *
      * @throws UserFormException
      */
     public static function mktempdir() {
@@ -375,10 +377,18 @@ class HelperFile {
      */
     public static function mkDirParent($pathFileName, $chmodDir = false) {
         $path = "";
+        $cwd = '';
+
+        // Leading '/' will be removed - chdir to / to still use correct path
+        if ($pathFileName[0] == '/') {
+            $cwd = getcwd();
+            self::chdir('/');
+        }
 
         // Teile "Directory/File.Extension" auf
         $pathParts = pathinfo($pathFileName);
 
+
         // Zerlege Pfad in einzelne Directories
         $arr = explode("/", $pathParts["dirname"]);
 
@@ -409,6 +419,10 @@ class HelperFile {
             }
             $path .= "/";
         }
+
+        if ($cwd != '') {
+            self::chdir($cwd);
+        }
     }
 
     /**
diff --git a/extension/Source/core/helper/Logger.php b/extension/Source/core/helper/Logger.php
index d6dc807f34be0b7da762e3684d6de7ef85d09997..1307479186669eadda2f9f7e9322721343d97623 100644
--- a/extension/Source/core/helper/Logger.php
+++ b/extension/Source/core/helper/Logger.php
@@ -21,13 +21,28 @@ require_once(__DIR__ . '/../helper/Support.php');
 class Logger {
 
     /**
-     * Append $msg to $filename.
+     * @var String
+     */
+    private static $systemSitePath = '';
+
+    /**
+     * Copy the SystemSitePath to a local variable.
+     *
+     * @param $path
+     */
+    public static function setSystemSitePath($path) {
+        self::$systemSitePath = $path;
+    }
+
+    /**
+     * Append $msg to $filename. Create the file it it not exist.
      *
      * @param $msg
      * @param $filename
      *
      * @param string $mode
      * @param bool $recursion
+     * @throws CodeException
      * @throws UserFormException
      */
     public static function logMessage($msg, $filename, $mode = FILE_MODE_APPEND, $recursion = false) {
@@ -38,17 +53,19 @@ class Logger {
             return;
         }
 
-        $filename = self::relativeToT3Dir($filename);
+        $filename = self::makePathAbsolute($filename);
 
         try {
             $handle = fopen($filename, $mode);
         } catch (\Exception $e) {
-            $dummy=1;
+            $dummy = 1;
         }
 
-        if($handle===false) {
+        $cwd1 = getcwd();
+
+        if ($handle === false) {
 
-            if($recursion){
+            if ($recursion) {
                 throw new UserFormException(
                     json_encode([ERROR_MESSAGE_TO_USER => 'Error: cannot open file',
                         ERROR_MESSAGE_SUPPORT => "Error - cannot open. File: " . $filename .
@@ -73,9 +90,12 @@ class Logger {
     }
 
     /**
+     * Prefix every message with linePre().
+     *
      * @param $msg
      * @param $filename
      * @param string $mode
+     * @throws CodeException
      * @throws UserFormException
      */
     public static function logMessageWithPrefix($msg, $filename, $mode = FILE_MODE_APPEND) {
@@ -87,19 +107,33 @@ class Logger {
      *
      * @param $filename
      * @return string
+     * @throws CodeException
      */
-    private static function relativeToT3Dir($filename) {
+    private static function makePathAbsolute($filename) {
 
         if (isset($filename[0]) && $filename[0] != '/') {
-            if (strpos(getcwd(), 'qfq/' . API_DIR_EXT) !== false) {
-                return ('../../../../../' . $filename);
+
+            if (self::$systemSitePath == '') {
+
+                if (defined('PHPUNIT_QFQ')) {
+                    if (strpos(getcwd(), 'qfq/' . API_DIR_EXT) !== false) {
+                        return ('../../../../../' . $filename);
+                    }
+                    return $filename;
+                }
+
+                throw new CodeException('SystemSitePath is not set and the given logfile should be made absolute.', ERROR_MISSING_VALUE);
             }
+
+            return self::$systemSitePath . DIRECTORY_SEPARATOR . $filename;
         }
 
         return $filename;
     }
 
     /**
+     * Returns a timestamp, IP, cookie.
+     *
      * @return string
      */
     public static function linePre() {
@@ -118,16 +152,14 @@ class Logger {
     }
 
     /**
-     * @param array $fe
+     * Format details of a FormElement.
      *
+     * @param array $fe
      * @return string
      */
     public static function formatFormElementName(array $fe) {
-        Support::setIfNotSet($fe, 'id');
-        Support::setIfNotSet($fe, FE_NAME);
-        Support::setIfNotSet($fe, FE_LABEL);
 
-        return $fe['id'] . ' / ' . $fe[FE_NAME] . ' / ' . $fe[FE_LABEL];
+        return ($fe['id']??'') . ' / ' . ($fe[FE_NAME]??'') . ' / ' . ($fe[FE_LABEL]??'');
     }
 
     /**
@@ -138,6 +170,7 @@ class Logger {
      * @param $pre
      * @param $data
      * @param bool $flagNewLineFirst
+     * @throws CodeException
      * @throws UserFormException
      */
     public static function logFormLine(array $form, $pre, $data, $flagNewLineFirst = false) {
diff --git a/extension/Source/core/helper/Support.php b/extension/Source/core/helper/Support.php
index 18ebf2d6cc3fcea7a09422e39463884346887c8f..ec901d6a59c35ce63748d83a921d6017cfab00c3 100644
--- a/extension/Source/core/helper/Support.php
+++ b/extension/Source/core/helper/Support.php
@@ -209,8 +209,8 @@ class Support {
      *
      * @param string $type
      * @param string|array $value
-     * @param bool $flagOmitEmpty   true|false
-     * @param string $modeEscape    ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
+     * @param bool $flagOmitEmpty true|false
+     * @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
      *
      * @return string correctly formatted attribute. Space at the end.
      * @throws CodeException
@@ -319,7 +319,7 @@ class Support {
     /**
      * Search for the parameter $needle in $haystack. The arguments has to be separated by ','.
      *
-     * Returns false if not found or index of found place. Be careful: use unary operator to compare for 'false'
+     * Returns false if not found, or index (starting with 0) of found place. Be careful: use unary operator to compare for 'false'
      *
      * @param string $needle
      * @param string $haystack
@@ -990,7 +990,7 @@ class Support {
         }
 
         // If min or max is set and if there is the standard error text given, define a more detailed error text.
-        if (($formElement[FE_MIN] != '' || $formElement[FE_MAX] != '') && ($formElement[F_FE_DATA_ERROR]??'') == F_FE_DATA_ERROR_DEFAULT) {
+        if (($formElement[FE_MIN] != '' || $formElement[FE_MAX] != '') && ($formElement[F_FE_DATA_ERROR] ?? '') == F_FE_DATA_ERROR_DEFAULT) {
             $formElement[F_FE_DATA_ERROR] = F_FE_DATA_ERROR_DEFAULT . ' - allowed values: ' . $formElement[FE_MIN] . '...' . $formElement[FE_MAX];
         }
     }
@@ -1281,15 +1281,6 @@ class Support {
         HelperFile::unlink($srcFile);
     }
 
-    /**
-     *
-     */
-    public static function createTempDir() {
-
-        return exec("mktemp -d --tmpdir " . QFQ_TEMP_FILE_PATTERN);
-
-    }
-
     /**
      * Convert 'false' and '<empty string>' to '0'.
      *
diff --git a/extension/Source/core/store/Client.php b/extension/Source/core/store/Client.php
index 93416dc0ad139194d287a36c867120622c010711..6ec3be4f89be41d78441164159fb08687c4a1383 100644
--- a/extension/Source/core/store/Client.php
+++ b/extension/Source/core/store/Client.php
@@ -34,20 +34,20 @@ class Client {
         Sanitize::digitCheckAndCleanGet(CLIENT_PAGE_TYPE);
         Sanitize::digitCheckAndCleanGet(CLIENT_PAGE_LANGUAGE);
 
+        $header = self::getHeader();
+
         if (isset($_GET)) {
             $get = $_GET; // do not use urldecode() - http://php.net/manual/de/function.urldecode.php#refsect1-function.urldecode-notes
         }
 
         if (isset($_POST)) {
             $post = $_POST;
-//            Logger::logMessage(var_export($post, true) . PHP_EOL . PHP_EOL,'post.txt');
         }
 
         if (isset($_COOKIE[SESSION_NAME])) {
             $cookie[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.
         }
@@ -57,8 +57,40 @@ class Client {
             $server[CLIENT_REMOTE_ADDRESS] = '0.0.0.0';
         }
 
-        $arr = array_merge($get, $post, $cookie, $server);
+        // It's important to merge the SERVER array last: those entries shall overwrite client values.
+        $arr = array_merge($header, $get, $post, $cookie, $server);
 
         return Sanitize::normalize($arr);
     }
+
+    /**
+     * @return array
+     */
+    private static function getHeader() {
+
+        $arr = array();
+
+        // getallheaders() does not exist for phpunit tests
+        if (!function_exists('getallheaders')) {
+            return array();
+        }
+
+        $headers = getallheaders();
+
+        foreach ([HTTP_HEADER_AUTHORIZATION] as $key) {
+            if (isset($headers[$key])) {
+                $line = $headers[$key];
+
+                $delimiter = (strpos($line, '=') === false) ? ':' : '=';
+
+                // Header: 'Authorization: Token token=1234'
+                $split = explode($delimiter, $line, 2);
+                if (isset($split[1])) {
+                    $arr[$key] = OnString::trimQuote($split[1]);
+                }
+            }
+        }
+
+        return $arr;
+    }
 }
\ No newline at end of file
diff --git a/extension/Source/core/store/Config.php b/extension/Source/core/store/Config.php
index baa587aa3aaf701cf6adddaa342f11352bfab466..6ca346320f89f87d4bfc8651ce9755680c8cf8c0 100644
--- a/extension/Source/core/store/Config.php
+++ b/extension/Source/core/store/Config.php
@@ -363,6 +363,8 @@ class Config {
 
             SYSTEM_FLAG_PRODUCTION => 'yes',
             SYSTEM_THROW_GENERAL_ERROR => 'auto',
+
+            SYSTEM_SECURITY_FAILED_AUTH_DELAY => '3',
         ];
 
         // To let run legacy code
diff --git a/extension/Source/core/store/FillStoreForm.php b/extension/Source/core/store/FillStoreForm.php
index 16ca6dd8719761b202812120b76348ff5115cd93..606dbc6fde3615e303a4e6651d11983f0bc3366a 100644
--- a/extension/Source/core/store/FillStoreForm.php
+++ b/extension/Source/core/store/FillStoreForm.php
@@ -182,11 +182,12 @@ class FillStoreForm {
         $formModeGlobal = $this->store->getVar(F_MODE_GLOBAL, STORE_SIP . STORE_EMPTY);
 
         if ($formMode == FORM_UPDATE && $formModeGlobal == '') {
+            # During 'update': fake all elements to be not 'required'.
             $formModeGlobal = F_MODE_REQUIRED_OFF;
         }
 
         // If called through 'api/...': get STORE_TYPO3 via SIP parameter.
-        if (isset($clientValues[CLIENT_TYPO3VARS])) {
+        if (isset($clientValues[CLIENT_TYPO3VARS]) && $formMode != FORM_REST) {
             $this->store->fillTypo3StoreFromSip($clientValues[CLIENT_TYPO3VARS]);
         }
 
@@ -209,10 +210,12 @@ class FillStoreForm {
             }
         }
 
-        // Check if there is a 'new record already saved' situation:
-        // yes: the names of the input fields are submitted with '<fieldname>:0' instead of '<fieldname>:<id>'
-        // no: regular situation, take real 'recordid'
-        $fakeRecordId = isset($sipValues[SIP_MAKE_URLPARAM_UNIQ]) ? 0 : $sipValues[SIP_RECORD_ID];
+        if ($formMode != FORM_REST) {
+            // Check if there is a 'new record already saved' situation:
+            // yes: the names of the input fields are submitted with '<fieldname>:0' instead of '<fieldname>:<id>'
+            // no: regular situation, take real 'recordid'
+            $fakeRecordId = isset($sipValues[SIP_MAKE_URLPARAM_UNIQ]) ? 0 : $sipValues[SIP_RECORD_ID];
+        }
 
         // Iterate over all FormElements. Sanatize values. Built an assoc array $newValues.
         foreach ($this->feSpecNative AS $formElement) {
@@ -229,7 +232,7 @@ class FillStoreForm {
             $formElement = $this->evaluate->parseArray($formElement, $skip, $debugStack);
 
             // Get related formElement. Construct the field name used in the form.
-            $clientFieldName = HelperFormElement::buildFormElementName($formElement, $fakeRecordId);
+            $clientFieldName = ($formMode == FORM_REST) ? $formElement[FE_NAME] : HelperFormElement::buildFormElementName($formElement, $fakeRecordId);
 
             // Some Defaults
             $formElement = Support::setFeDefaults($formElement, [F_MODE => $formModeGlobal]);
@@ -247,7 +250,7 @@ class FillStoreForm {
                     throw new CodeException("Missing the " . FE_TYPE_EXTRA . " field '" . $formElement[FE_NAME] . "' in SIP.", ERROR_MISSING_HIDDEN_FIELD_IN_SIP);
                 }
 
-                $newValues[$formElement[FE_NAME]] = $sipValues[$formElement[FE_NAME]];
+                $newValues[$formElement[FE_NAME]] = $sipValues[$formElement[FE_NAME]] ?? '';
                 continue;
             }
 
@@ -273,6 +276,13 @@ class FillStoreForm {
 //                }
 //            }
 
+
+            // FORM_REST: typically form elements are filled and created on form load. This does not exist for REST Forms.
+            // If a FE.value is defined, this has precedence over client supplied content.
+            if ($formMode == FORM_REST && $formElement[FE_VALUE]!='') {
+                $clientValues[$clientFieldName] = $this->evaluate->parse($formElement[FE_VALUE]);
+            }
+
             // copy value to $newValues
             if (isset($clientValues[$clientFieldName])) {
 
@@ -309,7 +319,7 @@ class FillStoreForm {
                             // Check only if there is something.
                             if ($val !== '' && $formMode != FORM_UPDATE && $formElement[FE_MODE] != FE_MODE_HIDDEN) {
                                 $val = Sanitize::sanitize($val, $formElement[FE_CHECK_TYPE], $formElement[FE_CHECK_PATTERN],
-                                    $formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION, $formElement[F_FE_DATA_PATTERN_ERROR]??'');
+                                    $formElement[FE_DECIMAL_FORMAT], SANITIZE_EXCEPTION, $formElement[F_FE_DATA_PATTERN_ERROR] ?? '');
 
                                 if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
 //                                    $val = htmlspecialchars($val, ENT_QUOTES);
diff --git a/extension/Source/core/store/Store.php b/extension/Source/core/store/Store.php
index a48ecd201834521f1a2b653ae608d8af38aaf1f6..8460533faf4e68ec250db29530f796201b390ff9 100644
--- a/extension/Source/core/store/Store.php
+++ b/extension/Source/core/store/Store.php
@@ -124,6 +124,7 @@ class Store {
             CLIENT_SERVER_PORT => SANITIZE_ALLOW_DIGIT,
             CLIENT_REMOTE_ADDRESS => SANITIZE_ALLOW_ALNUMX,
             CLIENT_REQUEST_SCHEME => SANITIZE_ALLOW_ALNUMX,
+            CLIENT_REQUEST_METHOD => SANITIZE_ALLOW_ALNUMX,
             CLIENT_SCRIPT_FILENAME => SANITIZE_ALLOW_ALNUMX,
             CLIENT_QUERY_STRING => SANITIZE_ALLOW_ALL,
             CLIENT_REQUEST_URI => SANITIZE_ALLOW_ALL,
@@ -281,6 +282,8 @@ class Store {
             }
         }
 
+        Logger::setSystemSitePath($config[SYSTEM_SITE_PATH]);
+
         return $config;
     }
 
diff --git a/extension/Source/sql/formEditor.sql b/extension/Source/sql/formEditor.sql
index 6c8088faa279e3a5c1192fb118b2ae595076a2ab..0b252e6cc38ac672caf3ec080763f9aa5cb51e32 100644
--- a/extension/Source/sql/formEditor.sql
+++ b/extension/Source/sql/formEditor.sql
@@ -13,6 +13,7 @@ CREATE TABLE IF NOT EXISTS `Form`
 
   `permitNew`                ENUM ('sip', 'logged_in', 'logged_out', 'always', 'never')  NOT NULL DEFAULT 'sip',
   `permitEdit`               ENUM ('sip', 'logged_in', 'logged_out', 'always', 'never')  NOT NULL DEFAULT 'sip',
+  `restMethod`               SET ('get','post','put','delete')                           NOT NULL DEFAULT '',
   `escapeTypeDefault`        VARCHAR(32)                                                 NOT NULL DEFAULT 'c',
   `render`                   ENUM ('bootstrap', 'table', 'plain')                        NOT NULL DEFAULT 'bootstrap',
   `requiredParameterNew`     VARCHAR(255)                                                NOT NULL DEFAULT '',
@@ -69,15 +70,15 @@ CREATE TABLE IF NOT EXISTS `FormElement`
   `modeSql`            TEXT                                                                       NOT NULL,
   `class`              ENUM ('native', 'action', 'container')                                     NOT NULL DEFAULT 'native',
   `type`               ENUM ('checkbox', 'date', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text',
-                             'editor', 'annotate', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload',
-                             'annotate', 'imageCut', 'fieldset', 'pill', 'templateGroup',
-                             'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad',
-                             'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail',
-                             'paste')                                                             NOT NULL DEFAULT 'text',
+    'editor', 'annotate', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload',
+    'annotate', 'imageCut', 'fieldset', 'pill', 'templateGroup',
+    'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad',
+    'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail',
+    'paste')                                                                                      NOT NULL DEFAULT 'text',
   `subrecordOption`    SET ('edit', 'delete', 'new')                                              NOT NULL DEFAULT '',
   `encode`             ENUM ('none', 'specialchar')                                               NOT NULL DEFAULT 'specialchar',
   `checkType`          ENUM ('auto', 'alnumx', 'digit', 'numerical', 'email', 'pattern', 'allbut',
-                             'all')                                                               NOT NULL DEFAULT 'auto',
+    'all')                                                                                        NOT NULL DEFAULT 'auto',
   `checkPattern`       VARCHAR(255)                                                               NOT NULL DEFAULT '',
 
   `onChange`           VARCHAR(255)                                                               NOT NULL DEFAULT '',
@@ -164,20 +165,19 @@ WHERE FIND_IN_SET(Form.name, 'form,formElement,copyForm,cron') > 0;
 #
 # FormEditor: Form
 INSERT INTO Form (id, name, title, noteInternal, tableName, permitNew, permitEdit, render, multiSql, parameter)
-VALUES
-(1, 'form', 'Form Editor: {{SELECT id, " / ", name FROM Form WHERE id = {{r:S0}}}} (DB: {{dbNameQfq:Y}})',
- 'FormElement Editor',
- 'Form', 'sip', 'sip', 'bootstrap', '', 'maxVisiblePill=5\nclass=container-fluid\ndbIndex={{indexQfq:Y}}');
+VALUES (1, 'form', 'Form Editor: {{SELECT id, " / ", name FROM Form WHERE id = {{r:S0}}}} (DB: {{dbNameQfq:Y}})',
+        'FormElement Editor',
+        'Form', 'sip', 'sip', 'bootstrap', '', 'maxVisiblePill=5\nclass=container-fluid\ndbIndex={{indexQfq:Y}}');
 
 # FormEditor: FormElements for 'form'
 INSERT INTO FormElement (id, formId, name, label, mode, type, checkType, class, ord, size, note, clientJs, value,
                          sql1, parameter, feIdContainer, subrecordOption, modeSql, placeholder)
-VALUES
-(1, 1, 'basic', 'Basic', 'show', 'pill', 'all', 'container', 100, 0, '', '', '', '', '', 0, '', '', ''),
-(2, 1, 'formelement', 'Formelement', 'show', 'pill', 'all', 'container', 200, 0, '', '', '', '', '', 0, '', '', ''),
-(3, 1, 'layout', 'Layout', 'show', 'pill', 'all', 'container', 300, 0, '', '', '', '', '', 0, '', '', ''),
-(4, 1, 'access', 'Access', 'show', 'pill', 'all', 'container', 400, 0, '', '', '', '', '', 0, '', '', ''),
-(5, 1, 'multi', 'Multi', 'hidden', 'pill', 'all', 'container', 500, 0, '', '', '', '', '', 0, '', '', '');
+VALUES (1, 1, 'basic', 'Basic', 'show', 'pill', 'all', 'container', 100, 0, '', '', '', '', '', 0, '', '', ''),
+       (2, 1, 'formelement', 'Formelement', 'show', 'pill', 'all', 'container', 200, 0, '', '', '', '', '', 0, '', '',
+        ''),
+       (3, 1, 'layout', 'Layout', 'show', 'pill', 'all', 'container', 300, 0, '', '', '', '', '', 0, '', '', ''),
+       (4, 1, 'access', 'Access', 'show', 'pill', 'all', 'container', 400, 0, '', '', '', '', '', 0, '', '', ''),
+       (5, 1, 'multi', 'Multi', 'hidden', 'pill', 'all', 'container', 500, 0, '', '', '', '', '', 0, '', '', '');
 
 # FormEditor: FormElements for 'form'
 INSERT INTO FormElement (formId, name, label, mode, type, checkType, class, ord, size, maxLength, note, clientJs, value,
@@ -262,18 +262,23 @@ VALUES
   (1, 'permitEdit', 'Permit Edit', 'show', 'radio', 'all', 'native', 360, 0, 10,
    '<a href="{{documentation:Y}}#form-permitnewedit">Info</a>', '', '', '', 'buttonClass=btn-default', 4, '', '', '',
    'specialchar', 'no', ''),
-  (1, 'escapeTypeDefault', 'Escape type default', 'show', 'radio', 'all', 'native', 370, 0, 10,
+
+  (1, 'prestMethod', 'Permit REST', 'show', 'checkbox', 'all', 'native', 370, 0, 10,
+   '<a href="{{documentation:Y}}#rest">Info</a>', '', '', '', 'buttonClass=btn-default\nitemList=get,post:insert,put:update,delete', 4, '', '', '',
+   'specialchar', 'no', ''),
+
+  (1, 'escapeTypeDefault', 'Escape type default', 'show', 'radio', 'all', 'native', 380, 0, 10,
    '<a href="{{documentation:Y}}#variable-escape">Info</a>', '', '', '',
    'itemList=c:config,s:single,d:double,l:ldap search,L:ldap value,m:mysql realEscapeString,-:none\nbuttonClass=btn-default',
    4, '', '', '', 'specialchar', 'no', ''),
-  (1, 'dirtyMode', 'Record Locking', 'show', 'radio', 'all', 'native', 380, 0, 10,
+  (1, 'dirtyMode', 'Record Locking', 'show', 'radio', 'all', 'native', 390, 0, 10,
    '<a href="{{documentation:Y}}#locking-record">Info</a>', '', '', '',
    'buttonClass=btn-default', 4, '', '', '', 'specialchar', 'no', ''),
-  (1, 'recordLockTimeoutSeconds', 'Lock timeout (seconds)', 'show', 'text', 'all', 'native', 390, 0, 0,
+  (1, 'recordLockTimeoutSeconds', 'Lock timeout (seconds)', 'show', 'text', 'all', 'native', 400, 0, 0,
    '<a href="{{documentation:Y}}#locking-record">Info</a>', '',
    '{{SELECT IF("{{recordLockTimeoutSeconds:R0}}"=0,"{{recordLockTimeoutSeconds:Y0}}","{{recordLockTimeoutSeconds:R0}}")}}',
    '', '', 4, '', '', '', 'specialchar', 'no', ''),
-  (1, 'primaryKey', 'Primary Key', 'show', 'text', 'all', 'native', 400, 0, 0,
+  (1, 'primaryKey', 'Primary Key', 'show', 'text', 'all', 'native', 410, 0, 0,
    '<a href="{{documentation:Y}}#form-primary-key">Info</a>', '', '', '', '', 4, '', '', 'id', 'specialchar', 'no', ''),
 
   # Multi
@@ -293,136 +298,151 @@ VALUES
 # FormEditor: FormElement
 INSERT INTO Form (id, name, title, noteInternal, tableName, permitNew, permitEdit, render, multiSql, parameter,
                   requiredParameterNew)
-VALUES
-(2, 'formElement',
- 'Form Element Editor. Form : {{SELECT f.id, " / ",  f.name  FROM Form AS f WHERE f.id = {{formId:S0}}  }} (DB: {{dbNameQfq:Y}})',
- 'Please secure the form',
- 'FormElement', 'sip', 'sip', 'bootstrap', '',
- 'maxVisiblePill=5\nclassBody=qfq-color-blue-1\ndbIndex={{indexQfq:Y}}', 'formId');
+VALUES (2, 'formElement',
+        'Form Element Editor. Form : {{SELECT f.id, " / ",  f.name  FROM Form AS f WHERE f.id = {{formId:S0}}  }} (DB: {{dbNameQfq:Y}})',
+        'Please secure the form',
+        'FormElement', 'sip', 'sip', 'bootstrap', '',
+        'maxVisiblePill=5\nclassBody=qfq-color-blue-1\ndbIndex={{indexQfq:Y}}', 'formId');
 
 # FormEditor: FormElements for 'formElement'
 INSERT INTO FormElement (id, formId, name, label, mode, type, checkType, class, ord, size, note, clientJs, value,
                          sql1, parameter, feIdContainer, subrecordOption, modeSql)
-VALUES
-(100, 2, 'basic', 'Basic', 'show', 'pill', 'all', 'container', 10, 0, '', '', '', '', '', 0, '', ''),
-(101, 2, 'check_order', 'Check & Order', 'show', 'pill', 'all', 'container', 290, 0, '', '', '', '', '', 0, '',
- ''),
-(102, 2, 'layout', 'Layout', 'show', 'pill', 'all', 'container', 390, 0, '', '', '', '', '', 0, '', ''),
-(103, 2, 'value', 'Value', 'show', 'pill', 'all', 'container', 490, 0, '', '', '', '', '', 0, '', '');
+VALUES (100, 2, 'basic', 'Basic', 'show', 'pill', 'all', 'container', 10, 0, '', '', '', '', '', 0, '', ''),
+       (101, 2, 'check_order', 'Check & Order', 'show', 'pill', 'all', 'container', 290, 0, '', '', '', '', '', 0, '',
+        ''),
+       (102, 2, 'layout', 'Layout', 'show', 'pill', 'all', 'container', 390, 0, '', '', '', '', '', 0, '', ''),
+       (103, 2, 'value', 'Value', 'show', 'pill', 'all', 'container', 490, 0, '', '', '', '', '', 0, '', '');
 
 INSERT INTO FormElement (formId, name, label, mode, type, checkType, class, ord, size, maxLength, note, clientJs, value,
                          sql1, parameter, feIdContainer, subrecordOption, dynamicUpdate, bsLabelColumns, bsInputColumns,
                          bsNoteColumns, modeSql, placeholder, encode)
-VALUES
-(2, 'feIdContainer', 'Container', 'show', 'select', 'all', 'native', 120, 0, 0,
- '<a href="{{documentation:Y}}#class-container">Info</a>', '', '',
- '{{!SELECT fe.id, CONCAT(fe.type, " / ", fe.name, " (", COUNT(feSub.id), ")" ) FROM FormElement As fe LEFT JOIN FormElement As feSub ON feSub.feIdContainer=fe.id WHERE fe.formId={{formId:SR0}} AND fe.class="container" GROUP BY fe.id ORDER BY fe.type, fe.ord, fe.name }}',
- 'emptyItemAtStart', 100, '', 'no', '', '', '',
- '{{SELECT IF(COUNT(fe.id)>0, "show", "hidden") FROM Form AS f LEFT JOIN FormElement AS fe ON f.id=fe.formId AND fe.class="container" WHERE f.id={{formId:S0}} GROUP BY f.id}}',
- '', 'specialchar'),
-(2, 'enabled', 'Enabled', 'show', 'checkbox', 'all', 'native', 130, 0, 0,
- '<a href="{{documentation:Y}}#class-native">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '',
- 'specialchar'),
-(2, 'dynamicUpdate', 'Dynamic Update', 'show', 'checkbox', 'all', 'native', 135, 0, 0,
- '<a href="{{documentation:Y}}#dynamic-update">Info</a>',
- '', '', '', '', 100, '', 'no', '', '', '', '', '', 'specialchar'),
-(2, 'name', 'Name', 'show', 'text', 'all', 'native', 140, 0, 0, '<a href="{{documentation:Y}}#class-native">Info</a>',
- '', '', '',
- 'typeAheadSql = [{{indexData:Y}}]SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = "{{DB_1_NAME:Y}}" AND table_name = "{{SELECT f.tableName FROM Form AS f WHERE f.id={{formId:S0}}}}" AND COLUMN_NAME LIKE ? ORDER BY COLUMN_NAME\ntypeAheadMinLength = 1\ntypeAheadLimit = 100\ntypeAheadPedantic = 0\n',
- 100, '<a href="{{documentation:Y}}#class-native">Info</a>', 'no', '', '', '', '', '', 'specialchar'),
-(2, 'label', 'Label', 'show', 'text', 'all', 'native', 150, 0, 0, '<a href="{{documentation:Y}}#class-native">Info</a>',
- '', '', '', '', 100, '', 'no', '', '', '', '', '', 'none'),
-(2, 'mode', 'Mode', 'show', 'radio', 'all', 'native', 160, 0, 0, '<a href="{{documentation:Y}}#class-native">Info</a>',
- '', '', '', 'buttonClass=btn-default', 100, '', 'no', '', '', '', '', '', 'specialchar'),
-(2, 'modeSql', 'Mode sql', 'show', 'text', 'all', 'native', 170, '70,2', 0,
- '<a href="{{documentation:Y}}#dynamic-update">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '', 'none'),
-(2, 'class', 'Class', 'show', 'select', 'all', 'native', 180, 0, 0,
- '<a href="{{documentation:Y}}#class-container">Info</a>', '', '{{class:FSRD0:alnumx}}', '', '', 100, '', 'yes', '', '',
- '', '', '', 'none'),
-
-(2, 'type', 'Type', 'show', 'select', 'all', 'native', 190, 0, 0,
- '<a href="{{documentation:Y}}#class-native">Native</a>, <a href="{{documentation:Y}}#class-action">Action</a>, <a href="{{documentation:Y}}#class-container">Container</a>',
- '', '', '',
- 'itemList={{SELECT IF( "{{class:FRD0:alnumx}}"="native","checkbox,date,time,datetime,dateJQW,datetimeJQW,extra,gridJQW,text,editor,annotate,imageCut,note,password,radio,select,subrecord,upload", IF("{{class:FRD0:alnumx}}"="action","beforeLoad,beforeSave,beforeInsert,beforeUpdate,beforeDelete,afterLoad,afterSave,afterInsert,afterUpdate,afterDelete,sendMail,paste", "fieldset,pill,templateGroup")  ) }}',
- 100, '', 'yes', '', '', '', '', '', 'specialchar'),
-(2, 'subrecordOption', 'Subrecord Option', 'show', 'checkbox', 'all', 'native', 200, 0, 0,
- '<a href="{{documentation:Y}}#subrecord-option">Info</a>', '', '', '',
- '', 100, '', 'yes', '', '', '',
- '{{ SELECT IF("{{type:FRE:alnumx}}"="subrecord" AND "{{class:FRE:alnumx}}"="native", "show", "hidden") }}', '',
- 'specialchar'),
-(2, 'parameterLanguageA', 'Language: {{formLanguageALabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
- '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
- '{{SELECT IF("{{formLanguageAId:YE}}"="","hidden","show" ) }}', '', 'none'),
-(2, 'parameterLanguageB', 'Language: {{formLanguageBLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
- '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
- '{{SELECT IF("{{formLanguageBId:YE}}"="","hidden","show" ) }}', '', 'none'),
-(2, 'parameterLanguageC', 'Language: {{formLanguageCLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
- '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
- '{{SELECT IF("{{formLanguageCId:YE}}"="","hidden","show" ) }}', '', 'none'),
-(2, 'parameterLanguageD', 'Language: {{formLanguageDLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
- '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
- '{{SELECT IF("{{formLanguageDId:YE}}"="","hidden","show" ) }}', '', 'none'),
-
-(2, 'encode', 'Encode', 'show', 'radio', 'all', 'native', 300, 0, 0,
- '<a href="{{documentation:Y}}#field-encode">Info</a>', '', '', '', 'buttonClass=btn-default', 101, '', 'no', '', '',
- '', '', '', 'specialchar'),
-(2, 'checkType', 'Check Type', 'show', 'radio', 'all', 'native', 310, 0, 0,
- '<a href="{{documentation:Y}}#field-checktype">Info</a>', '', '', '', 'buttonClass=btn-default', 101, '', 'yes', '',
- '', '', '', '', 'specialchar'),
-(2, 'checkPattern', 'Check Pattern', 'show', 'text', 'all', 'native', 320, 0, 0,
- '<a href="{{documentation:Y}}#field-checkpattern">Info</a>, <a href="https://regex101.com/">Regex101</a>', '', '', '',
- '', 101, '', 'yes', '', '', '',
- '{{ SELECT IF("{{checkType:FRE:alnumx}}"="pattern" OR "{{checkType:FRE:allbut}}" LIKE "min%", "show", "hidden") }}',
- '', 'none'),
-#(2, 'onChange', 'JS onChange', 'show', 'text', 'all', 'native', 330, 0, 0, '', '', '', '', '', 101, '', 'no', '', '', '', '', '', 'none'),
-(2, 'ord', 'Order', 'show', 'text', 'all', 'native', 340, 0, 0, '<a href="{{documentation:Y}}#field-ord">Info</a>', '',
- '{{SELECT IF({{ord:R0}}=0,  MAX(IFNULL(fe.ord,0))+10,{{ord:R0}})  FROM (SELECT 1) AS a LEFT JOIN FormElement AS fe ON fe.formId={{formId:S0}} GROUP BY fe.formId}}',
- '', '', 101, '', 'no', '', '', '', '', '', 'specialchar'),
-(2, 'tabindex', 'tabindex', 'show', 'text', 'all', 'native', 350, 0, 0,
- '<a href="{{documentation:Y}}#field-tabindex">Info</a>', '', '', '', '', 101, '', 'no', '', '', '', '', '',
- 'specialchar'),
-(2, 'adminNote', 'Internal Note', 'show', 'text', 'all', 'native', 360, '60,4', 0, '', '', '', '', '', 101, '', 'no',
- '', '',
- '', '', '', 'specialchar'),
-
-(2, 'labelAlign', 'Label Align', 'show', 'radio', 'all', 'native', 400, 0, 0,
- '<a href="{{documentation:Y}}#class-native">Info</a>', '', '', '', 'buttonClass=btn-default', 102, '', 'no', '', '',
- '', '', '', 'specialchar'),
-(2, 'size', 'Size', 'show', 'text', 'all', 'native', 405, 0, 0, '<a href="{{documentation:Y}}#field-size">Info</a>', '',
- '', '', '', 102, '', 'no', '', '', '', '', '', 'specialchar'),
-(2, 'bsLabelColumns', 'BS Label Columns', 'show', 'text', 'all', 'native', 410, 0, 0,
- '<a href="{{documentation:Y}}#field-bslabelcolumns">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '',
- '{{SELECT IF(f.bsLabelColumns != '''', f.bsLabelColumns, ''{{bsLabelColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
- 'specialchar'),
-(2, 'bsInputColumns', 'BS Input Columns', 'show', 'text', 'all', 'native', 420, 0, 0, '', '', '', '', '', 102, '', 'no',
- '', '', '', '',
- '{{SELECT IF(f.bsInputColumns != '''', f.bsInputColumns, ''{{bsInputColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
- 'specialchar'),
-(2, 'bsNoteColumns', 'BS Note Columns', 'show', 'text', 'all', 'native', 430, 0, 0, '', '', '', '', '', 102, '', 'no',
- '', '', '', '',
- '{{SELECT IF(f.bsNoteColumns != '''', f.bsNoteColumns, ''{{bsNoteColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
- 'specialchar'),
-(2, 'rowLabelInputNote', 'Label / Input / Note', 'show', 'checkbox', 'alnumx', 'native', 440, 0, 10,
- '<a href="{{documentation:Y}}#field-rowlabelinputnote">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '',
- 'specialchar'),
-(2, 'maxLength', 'Maxlength', 'show', 'text', 'all', 'native', 450, 0, 0,
- '<a href="{{documentation:Y}}#field-maxlength">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '',
- 'specialchar'),
-(2, 'note', 'Note', 'show', 'text', 'all', 'native', 460, '40,5', 0,
- '<a href="{{documentation:Y}}#field-note">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '', 'none'),
-(2, 'tooltip', 'Tooltip', 'show', 'text', 'all', 'native', 470, 0, 0,
- '<a href="{{documentation:Y}}#field-tooltip">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '', 'none'),
-(2, 'placeholder', 'Placeholder', 'show', 'text', 'all', 'native', 480, 0, 0,
- '<a href="{{documentation:Y}}#field-placeholder">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '', 'none'),
-
-(2, 'value', 'value', 'show', 'text', 'all', 'native', 500, '40,2', 0,
- '<a href="{{documentation:Y}}#field-value">Info</a>', '', '', '', '', 103, '', 'no', '', '', '', '', '', 'none'),
-(2, 'sql1', 'sql1', 'show', 'text', 'all', 'native', 510, '40,5', 0,
- '<a href="{{documentation:Y}}#sql1">Info</a><br><br>MariaDB: <a href="https://mariadb.com/kb/en/mariadb/select/">Select</a>, <a href="https://mariadb.com/kb/en/mariadb/functions-and-operators/">Functions</a>',
- '', '', '', '', 103, '', 'no', '', '', '', '', '', 'none'),
-(2, 'parameter', 'Parameter', 'show', 'text', 'all', 'native', 520, '40,8', 0,
- '<a href="{{documentation:Y}}#fe-parameter-attributes">Info</a>',
- '', '', '', '', 103, '', 'no', '', '', '', '', '', 'none');
+VALUES (2, 'feIdContainer', 'Container', 'show', 'select', 'all', 'native', 120, 0, 0,
+        '<a href="{{documentation:Y}}#class-container">Info</a>', '', '',
+        '{{!SELECT fe.id, CONCAT(fe.type, " / ", fe.name, " (", COUNT(feSub.id), ")" ) FROM FormElement As fe LEFT JOIN FormElement As feSub ON feSub.feIdContainer=fe.id WHERE fe.formId={{formId:SR0}} AND fe.class="container" GROUP BY fe.id ORDER BY fe.type, fe.ord, fe.name }}',
+        'emptyItemAtStart', 100, '', 'no', '', '', '',
+        '{{SELECT IF(COUNT(fe.id)>0, "show", "hidden") FROM Form AS f LEFT JOIN FormElement AS fe ON f.id=fe.formId AND fe.class="container" WHERE f.id={{formId:S0}} GROUP BY f.id}}',
+        '', 'specialchar'),
+       (2, 'enabled', 'Enabled', 'show', 'checkbox', 'all', 'native', 130, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '',
+        'specialchar'),
+       (2, 'dynamicUpdate', 'Dynamic Update', 'show', 'checkbox', 'all', 'native', 135, 0, 0,
+        '<a href="{{documentation:Y}}#dynamic-update">Info</a>',
+        '', '', '', '', 100, '', 'no', '', '', '', '', '', 'specialchar'),
+       (2, 'name', 'Name', 'show', 'text', 'all', 'native', 140, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Info</a>',
+        '', '', '',
+        'typeAheadSql = [{{indexData:Y}}]SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = "{{DB_1_NAME:Y}}" AND table_name = "{{SELECT f.tableName FROM Form AS f WHERE f.id={{formId:S0}}}}" AND COLUMN_NAME LIKE ? ORDER BY COLUMN_NAME\ntypeAheadMinLength = 1\ntypeAheadLimit = 100\ntypeAheadPedantic = 0\n',
+        100, '<a href="{{documentation:Y}}#class-native">Info</a>', 'no', '', '', '', '', '', 'specialchar'),
+       (2, 'label', 'Label', 'show', 'text', 'all', 'native', 150, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Info</a>',
+        '', '', '', '', 100, '', 'no', '', '', '', '', '', 'none'),
+       (2, 'mode', 'Mode', 'show', 'radio', 'all', 'native', 160, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Info</a>',
+        '', '', '', 'buttonClass=btn-default', 100, '', 'no', '', '', '', '', '', 'specialchar'),
+       (2, 'modeSql', 'Mode sql', 'show', 'text', 'all', 'native', 170, '70,2', 0,
+        '<a href="{{documentation:Y}}#dynamic-update">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '',
+        'none'),
+       (2, 'class', 'Class', 'show', 'select', 'all', 'native', 180, 0, 0,
+        '<a href="{{documentation:Y}}#class-container">Info</a>', '', '{{class:FSRD0:alnumx}}', '', '', 100, '', 'yes',
+        '', '',
+        '', '', '', 'none'),
+
+       (2, 'type', 'Type', 'show', 'select', 'all', 'native', 190, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Native</a>, <a href="{{documentation:Y}}#class-action">Action</a>, <a href="{{documentation:Y}}#class-container">Container</a>',
+        '', '', '',
+        'itemList={{SELECT IF( "{{class:FRD0:alnumx}}"="native","checkbox,date,time,datetime,dateJQW,datetimeJQW,extra,gridJQW,text,editor,annotate,imageCut,note,password,radio,select,subrecord,upload", IF("{{class:FRD0:alnumx}}"="action","beforeLoad,beforeSave,beforeInsert,beforeUpdate,beforeDelete,afterLoad,afterSave,afterInsert,afterUpdate,afterDelete,sendMail,paste", "fieldset,pill,templateGroup")  ) }}',
+        100, '', 'yes', '', '', '', '', '', 'specialchar'),
+       (2, 'subrecordOption', 'Subrecord Option', 'show', 'checkbox', 'all', 'native', 200, 0, 0,
+        '<a href="{{documentation:Y}}#subrecord-option">Info</a>', '', '', '',
+        '', 100, '', 'yes', '', '', '',
+        '{{ SELECT IF("{{type:FRE:alnumx}}"="subrecord" AND "{{class:FRE:alnumx}}"="native", "show", "hidden") }}', '',
+        'specialchar'),
+       (2, 'parameterLanguageA', 'Language: {{formLanguageALabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
+        '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
+        '{{SELECT IF("{{formLanguageAId:YE}}"="","hidden","show" ) }}', '', 'none'),
+       (2, 'parameterLanguageB', 'Language: {{formLanguageBLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
+        '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
+        '{{SELECT IF("{{formLanguageBId:YE}}"="","hidden","show" ) }}', '', 'none'),
+       (2, 'parameterLanguageC', 'Language: {{formLanguageCLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
+        '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
+        '{{SELECT IF("{{formLanguageCId:YE}}"="","hidden","show" ) }}', '', 'none'),
+       (2, 'parameterLanguageD', 'Language: {{formLanguageDLabel:YE}}', 'show', 'text', 'all', 'native', 210, '60,2', 0,
+        '<a href="{{documentation:Y}}#multi-language-form">Info</a>', '', '', '', '', 100, '', 'no', '', '', '',
+        '{{SELECT IF("{{formLanguageDId:YE}}"="","hidden","show" ) }}', '', 'none'),
+
+       (2, 'encode', 'Encode', 'show', 'radio', 'all', 'native', 300, 0, 0,
+        '<a href="{{documentation:Y}}#field-encode">Info</a>', '', '', '', 'buttonClass=btn-default', 101, '', 'no', '',
+        '',
+        '', '', '', 'specialchar'),
+       (2, 'checkType', 'Check Type', 'show', 'radio', 'all', 'native', 310, 0, 0,
+        '<a href="{{documentation:Y}}#field-checktype">Info</a>', '', '', '', 'buttonClass=btn-default', 101, '', 'yes',
+        '',
+        '', '', '', '', 'specialchar'),
+       (2, 'checkPattern', 'Check Pattern', 'show', 'text', 'all', 'native', 320, 0, 0,
+        '<a href="{{documentation:Y}}#field-checkpattern">Info</a>, <a href="https://regex101.com/">Regex101</a>', '',
+        '', '',
+        '', 101, '', 'yes', '', '', '',
+        '{{ SELECT IF("{{checkType:FRE:alnumx}}"="pattern" OR "{{checkType:FRE:allbut}}" LIKE "min%", "show", "hidden") }}',
+        '', 'none'),
+       #(2, 'onChange', 'JS onChange', 'show', 'text', 'all', 'native', 330, 0, 0, '', '', '', '', '', 101, '', 'no', '', '', '', '', '', 'none'),
+       (2, 'ord', 'Order', 'show', 'text', 'all', 'native', 340, 0, 0,
+        '<a href="{{documentation:Y}}#field-ord">Info</a>', '',
+        '{{SELECT IF({{ord:R0}}=0,  MAX(IFNULL(fe.ord,0))+10,{{ord:R0}})  FROM (SELECT 1) AS a LEFT JOIN FormElement AS fe ON fe.formId={{formId:S0}} GROUP BY fe.formId}}',
+        '', '', 101, '', 'no', '', '', '', '', '', 'specialchar'),
+       (2, 'tabindex', 'tabindex', 'show', 'text', 'all', 'native', 350, 0, 0,
+        '<a href="{{documentation:Y}}#field-tabindex">Info</a>', '', '', '', '', 101, '', 'no', '', '', '', '', '',
+        'specialchar'),
+       (2, 'adminNote', 'Internal Note', 'show', 'text', 'all', 'native', 360, '60,4', 0, '', '', '', '', '', 101, '',
+        'no',
+        '', '',
+        '', '', '', 'specialchar'),
+
+       (2, 'labelAlign', 'Label Align', 'show', 'radio', 'all', 'native', 400, 0, 0,
+        '<a href="{{documentation:Y}}#class-native">Info</a>', '', '', '', 'buttonClass=btn-default', 102, '', 'no', '',
+        '',
+        '', '', '', 'specialchar'),
+       (2, 'size', 'Size', 'show', 'text', 'all', 'native', 405, 0, 0,
+        '<a href="{{documentation:Y}}#field-size">Info</a>', '',
+        '', '', '', 102, '', 'no', '', '', '', '', '', 'specialchar'),
+       (2, 'bsLabelColumns', 'BS Label Columns', 'show', 'text', 'all', 'native', 410, 0, 0,
+        '<a href="{{documentation:Y}}#field-bslabelcolumns">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '',
+        '{{SELECT IF(f.bsLabelColumns != '''', f.bsLabelColumns, ''{{bsLabelColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
+        'specialchar'),
+       (2, 'bsInputColumns', 'BS Input Columns', 'show', 'text', 'all', 'native', 420, 0, 0, '', '', '', '', '', 102,
+        '', 'no',
+        '', '', '', '',
+        '{{SELECT IF(f.bsInputColumns != '''', f.bsInputColumns, ''{{bsInputColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
+        'specialchar'),
+       (2, 'bsNoteColumns', 'BS Note Columns', 'show', 'text', 'all', 'native', 430, 0, 0, '', '', '', '', '', 102, '',
+        'no',
+        '', '', '', '',
+        '{{SELECT IF(f.bsNoteColumns != '''', f.bsNoteColumns, ''{{bsNoteColumns:Y}}'') FROM Form AS f WHERE f.id = {{formId}} }}',
+        'specialchar'),
+       (2, 'rowLabelInputNote', 'Label / Input / Note', 'show', 'checkbox', 'alnumx', 'native', 440, 0, 10,
+        '<a href="{{documentation:Y}}#field-rowlabelinputnote">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '',
+        '',
+        'specialchar'),
+       (2, 'maxLength', 'Maxlength', 'show', 'text', 'all', 'native', 450, 0, 0,
+        '<a href="{{documentation:Y}}#field-maxlength">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '',
+        'specialchar'),
+       (2, 'note', 'Note', 'show', 'text', 'all', 'native', 460, '40,5', 0,
+        '<a href="{{documentation:Y}}#field-note">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '', 'none'),
+       (2, 'tooltip', 'Tooltip', 'show', 'text', 'all', 'native', 470, 0, 0,
+        '<a href="{{documentation:Y}}#field-tooltip">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '',
+        'none'),
+       (2, 'placeholder', 'Placeholder', 'show', 'text', 'all', 'native', 480, 0, 0,
+        '<a href="{{documentation:Y}}#field-placeholder">Info</a>', '', '', '', '', 102, '', 'no', '', '', '', '', '',
+        'none'),
+
+       (2, 'value', 'value', 'show', 'text', 'all', 'native', 500, '40,2', 0,
+        '<a href="{{documentation:Y}}#field-value">Info</a>', '', '', '', '', 103, '', 'no', '', '', '', '', '',
+        'none'),
+       (2, 'sql1', 'sql1', 'show', 'text', 'all', 'native', 510, '40,5', 0,
+        '<a href="{{documentation:Y}}#sql1">Info</a><br><br>MariaDB: <a href="https://mariadb.com/kb/en/mariadb/select/">Select</a>, <a href="https://mariadb.com/kb/en/mariadb/functions-and-operators/">Functions</a>',
+        '', '', '', '', 103, '', 'no', '', '', '', '', '', 'none'),
+       (2, 'parameter', 'Parameter', 'show', 'text', 'all', 'native', 520, '40,8', 0,
+        '<a href="{{documentation:Y}}#fe-parameter-attributes">Info</a>',
+        '', '', '', '', 103, '', 'no', '', '', '', '', '', 'none');
 
 INSERT INTO `FormElement` (`id`, `formId`, `feIdContainer`, `dynamicUpdate`, `enabled`, `name`, `label`, `mode`,
                            `modeSql`, `class`, `type`, `subrecordOption`, `encode`, `checkType`, `checkPattern`,
@@ -431,11 +451,11 @@ INSERT INTO `FormElement` (`id`, `formId`, `feIdContainer`, `dynamicUpdate`, `en
                            `sql1`, `parameter`, `parameterLanguageA`, `parameterLanguageB`, `parameterLanguageC`,
                            `parameterLanguageD`, `clientJs`, `feGroup`, `deleted`)
 
-VALUES
-(NULL, '2', '0', 'no', 'yes', 'Check Name Conflict', '', 'show', '', 'action', 'beforeSave', '', 'specialchar', 'auto',
- '', '', '650', '0', '', '', '', '', '', 'row,label,/label,input,/input,note,/note,/row', '', '', '', '', '', '',
- 'sqlValidate={{!SELECT fe.id FROM FormElement AS fe WHERE "{{class:F:alnumx}}"=fe.class AND fe.formId={{formId:RF}} AND fe.name!="" AND fe.name="{{name:F:alnumx}}" AND fe.id!={{id:R0}} }}\r\n\r\nexpectRecords=0\r\n\r\nmessageFail=There is already another {{class:F:alnumx}} form element whith name "{{name:F:alnumx}}".',
- '', '', '', '', '', '', 'no');
+VALUES (NULL, '2', '0', 'no', 'yes', 'Check Name Conflict', '', 'show', '', 'action', 'beforeSave', '', 'specialchar',
+        'auto',
+        '', '', '650', '0', '', '', '', '', '', 'row,label,/label,input,/input,note,/note,/row', '', '', '', '', '', '',
+        'sqlValidate={{!SELECT fe.id FROM FormElement AS fe WHERE "{{class:F:alnumx}}"=fe.class AND fe.formId={{formId:RF}} AND fe.name!="" AND fe.name="{{name:F:alnumx}}" AND fe.id!={{id:R0}} }}\r\n\r\nexpectRecords=0\r\n\r\nmessageFail=There is already another {{class:F:alnumx}} form element whith name "{{name:F:alnumx}}".',
+        '', '', '', '', '', '', 'no');
 
 # ----------------------------------------
 # MailLog
@@ -506,25 +526,24 @@ CREATE TABLE IF NOT EXISTS `Clipboard`
 
 # Form: CopyForm
 INSERT INTO Form (id, name, title, tableName, showButton, forwardMode, forwardPage, parameter)
-VALUES
-(3, 'copyForm', 'Copy a form', 'Clipboard', 'close,save', 'url-sip', '?id={{pageId:T}}&form=form&r={{formId:P0}}',
- 'submitButtonText = Copy Form');
+VALUES (3, 'copyForm', 'Copy a form', 'Clipboard', 'close,save', 'url-sip',
+        '?id={{pageId:T}}&form=form&r={{formId:P0}}',
+        'submitButtonText = Copy Form');
 
 # FormElements: CopyForm
 INSERT INTO FormElement (formId, name, label, type, class, ord, sql1, parameter)
-VALUES
-(3, 'idSrc', 'Source Form', 'select', 'native', 10,
- '{{!SELECT f.id, CONCAT(f.name, " / ", f.title) FROM Form AS f ORDER BY f.name}}', ''),
-(3, 'myNewFormName', 'New Form Name', 'text', 'native', 20, '', ''),
-(3, 'clearClipboard', '', 'beforeSave', 'action', 100, '',
- 'sqlValidate={{!SELECT f.id FROM Form AS f WHERE f.name LIKE "{{myName:FE:alnumx}}" LIMIT 1}}\nexpectRecords = 0\nmessageFail = There is already a form with this name\nsqlAfter={{DELETE FROM Clipboard WHERE cookie="{{cookieQfq:C0:alnumx}}" }}'),
-(3, 'updateClipboardRecord', '', 'afterSave', 'action', 110, '',
- 'sqlAfter={{UPDATE Clipboard AS c, Form AS f SET c.cookie="{{cookieQfq:C0:alnumx}}", c.formIdPaste=f.id /* PasteForm */  WHERE c.id={{id:R}} AND f.name="{{form:SE}}" }}'),
-(3, 'formId', '', 'paste', 'action', 200, '{{!SELECT {{id:P}} AS id, "{{myNewFormName:FE:allbut}}" AS name}}',
- 'recordDestinationTable=Form'),
-(3, 'formElementId', '', 'paste', 'action', 210,
- '{{!SELECT fe.id AS id, {{formId:P}} AS formId FROM FormElement AS fe WHERE fe.formId={{id:P}} ORDER BY fe.ord}}',
- 'recordDestinationTable=FormElement\ntranslateIdColumn=feIdContainer');
+VALUES (3, 'idSrc', 'Source Form', 'select', 'native', 10,
+        '{{!SELECT f.id, CONCAT(f.name, " / ", f.title) FROM Form AS f ORDER BY f.name}}', ''),
+       (3, 'myNewFormName', 'New Form Name', 'text', 'native', 20, '', ''),
+       (3, 'clearClipboard', '', 'beforeSave', 'action', 100, '',
+        'sqlValidate={{!SELECT f.id FROM Form AS f WHERE f.name LIKE "{{myName:FE:alnumx}}" LIMIT 1}}\nexpectRecords = 0\nmessageFail = There is already a form with this name\nsqlAfter={{DELETE FROM Clipboard WHERE cookie="{{cookieQfq:C0:alnumx}}" }}'),
+       (3, 'updateClipboardRecord', '', 'afterSave', 'action', 110, '',
+        'sqlAfter={{UPDATE Clipboard AS c, Form AS f SET c.cookie="{{cookieQfq:C0:alnumx}}", c.formIdPaste=f.id /* PasteForm */  WHERE c.id={{id:R}} AND f.name="{{form:SE}}" }}'),
+       (3, 'formId', '', 'paste', 'action', 200, '{{!SELECT {{id:P}} AS id, "{{myNewFormName:FE:allbut}}" AS name}}',
+        'recordDestinationTable=Form'),
+       (3, 'formElementId', '', 'paste', 'action', 210,
+        '{{!SELECT fe.id AS id, {{formId:P}} AS formId FROM FormElement AS fe WHERE fe.formId={{id:P}} ORDER BY fe.ord}}',
+        'recordDestinationTable=FormElement\ntranslateIdColumn=feIdContainer');
 
 # AutoCRON
 CREATE TABLE IF NOT EXISTS `Cron`
@@ -555,47 +574,50 @@ CREATE TABLE IF NOT EXISTS `Cron`
 
 # Form: AutoCron
 INSERT INTO Form (id, name, title, tableName, parameter, dirtyMode)
-VALUES
-(4, 'cron', 'autoCron', 'Cron', 'dbIndex={{indexQfq:Y}}',
- 'none');
+VALUES (4, 'cron', 'autoCron', 'Cron', 'dbIndex={{indexQfq:Y}}',
+        'none');
 
 # FormElements: AutoCron
 INSERT INTO FormElement (formId, name, label, mode, modeSql, type, encode, checkType, ord, parameter, size, note,
                          dynamicUpdate, bsLabelColumns, bsInputColumns, bsNoteColumns)
-VALUES
-(4, 'status', 'Enabled', 'show', '', 'checkbox', 'specialchar', 'alnumx', 10, '', '', '', 'no', '', '', ''),
-(4, 'type', 'Type', 'show', '', 'radio', 'specialchar', 'alnumx', 20, 'buttonClass=btn-default', '', '', 'yes', '', '',
- ''),
-(4, 'nextRun', 'Next run', 'show', '', 'text', 'specialchar', 'alnumx', 30,
- 'extraButtonInfo = Cronjob will be started if specified timestamp is over. If timestamp=0: Job will never be started<br>Every time the jobs runs, this timestamp will be increased automatically by "frequency".',
- '', '', 'no', '', '', ''),
-(4, 'frequency', 'Frequency', 'show', '', 'text', 'specialchar', 'alnumx', 40,
- 'extraButtonInfo = Repeat AutoCron-job with the specified interval. If empty: no repeating.<br>E.g.: "1 DAY", "15 MINUTE'', "6 MONTH" - used directly in SQL-Function "DATE_ADD(&lt;nextrun&gt;, INTERVAL &lt;frequency&gt;)"',
- '', '', 'no', '', '', ''),
-(4, 'comment', 'Comment', 'show', '', 'text', 'specialchar', 'allbut', 50, '', '', '', 'no', '', '', ''),
-(4, 'sql1', 'Mail', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","show","hidden") }}', 'text', 'none', 'all', 60,
- 'extraButtonInfo = Query: &#123;&#123;!SELECT ... as sendMailTo...&#125;&#125;<br><b>sendMailTo / sendMailCc / sendMailBcc</b>: Separate multiple by comma.<br><b>sendMailFrom</b><br><b>sendMailSubject</b><br><b>sendMailReplyTo</b>: Optional<br><b>sendMailFlagAutoSubmit</b>: Optional. on|off. Default on - if "on", suppresses OoO answers from receivers.<br><b>sendMailGrId</b>: Optional<br><b>sendMailXId</b>: Optional',
- '60,4', '', 'yes', '', '', ''),
-(4, 'content', '{{SELECT IF("{{type:FR:alnumx}}"="mail","Mail body","URL") }}', 'show', '', 'text', 'none', 'all', 70,
- '', '40,4',
- 'Website: URL absolute like "http://..." or relative like "?id=pagealias..."<br>Mail: Static Body or &#123;{SELECT ...&#125;}',
- 'yes', '', '', ''),
-
-(4, 'outputFile', 'Log output to file', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}', 'text',
- 'none', 'all', 80, '', '', 'CWD: Site installation directory', 'yes', '', '', ''),
-(4, 'outputMode', 'Mode output', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}', 'radio',
- 'specialchar', 'alnumx', 90, 'buttonClass=btn-default', '', '', 'yes', '', '', ''),
-(4, 'outputPattern', 'Pattern to look for on output', 'show',
- '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}', 'text', 'none', 'all', 100, '', '',
- 'If pattern isn\'t found, return an error.<br>Check <a href="https://secure.php.net/manual/en/pcre.pattern.php">pcre</a> / <a href="https://regexp101.com">regexp101.com</a> ',
- 'yes', '', '', ''),
-
-(4, 'lastRun', 'Last run', 'readonly', '', 'text', 'specialchar', 'alnumx', 120, '', '', '', 'no', '', '', ''),
-(4, 'lastStatus', 'Laststatus', 'readonly', '', 'text', 'specialchar', 'alnumx', 130, '', '50,6', '', 'no', '3', '9',
- '0'),
-(4, 'inProgress', 'In progress since', 'show', '', 'text', 'specialchar', 'alnumx', 140,
- 'extraButtonInfo = Start time of a running job. When job is finished, this will be set back to 0. A new job will only be started, if this is 0. A progress duration >10mins will be treated as an error.',
- '', '', 'no', '', '', '');
+VALUES (4, 'status', 'Enabled', 'show', '', 'checkbox', 'specialchar', 'alnumx', 10, '', '', '', 'no', '', '', ''),
+       (4, 'type', 'Type', 'show', '', 'radio', 'specialchar', 'alnumx', 20, 'buttonClass=btn-default', '', '', 'yes',
+        '', '',
+        ''),
+       (4, 'nextRun', 'Next run', 'show', '', 'text', 'specialchar', 'alnumx', 30,
+        'extraButtonInfo = Cronjob will be started if specified timestamp is over. If timestamp=0: Job will never be started<br>Every time the jobs runs, this timestamp will be increased automatically by "frequency".',
+        '', '', 'no', '', '', ''),
+       (4, 'frequency', 'Frequency', 'show', '', 'text', 'specialchar', 'alnumx', 40,
+        'extraButtonInfo = Repeat AutoCron-job with the specified interval. If empty: no repeating.<br>E.g.: "1 DAY", "15 MINUTE'', "6 MONTH" - used directly in SQL-Function "DATE_ADD(&lt;nextrun&gt;, INTERVAL &lt;frequency&gt;)"',
+        '', '', 'no', '', '', ''),
+       (4, 'comment', 'Comment', 'show', '', 'text', 'specialchar', 'allbut', 50, '', '', '', 'no', '', '', ''),
+       (4, 'sql1', 'Mail', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","show","hidden") }}', 'text', 'none', 'all',
+        60,
+        'extraButtonInfo = Query: &#123;&#123;!SELECT ... as sendMailTo...&#125;&#125;<br><b>sendMailTo / sendMailCc / sendMailBcc</b>: Separate multiple by comma.<br><b>sendMailFrom</b><br><b>sendMailSubject</b><br><b>sendMailReplyTo</b>: Optional<br><b>sendMailFlagAutoSubmit</b>: Optional. on|off. Default on - if "on", suppresses OoO answers from receivers.<br><b>sendMailGrId</b>: Optional<br><b>sendMailXId</b>: Optional',
+        '60,4', '', 'yes', '', '', ''),
+       (4, 'content', '{{SELECT IF("{{type:FR:alnumx}}"="mail","Mail body","URL") }}', 'show', '', 'text', 'none',
+        'all', 70,
+        '', '40,4',
+        'Website: URL absolute like "http://..." or relative like "?id=pagealias..."<br>Mail: Static Body or &#123;{SELECT ...&#125;}',
+        'yes', '', '', ''),
+
+       (4, 'outputFile', 'Log output to file', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}',
+        'text',
+        'none', 'all', 80, '', '', 'CWD: Site installation directory', 'yes', '', '', ''),
+       (4, 'outputMode', 'Mode output', 'show', '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}', 'radio',
+        'specialchar', 'alnumx', 90, 'buttonClass=btn-default', '', '', 'yes', '', '', ''),
+       (4, 'outputPattern', 'Pattern to look for on output', 'show',
+        '{{SELECT IF("{{type:FR:alnumx}}"="mail","hidden","show") }}', 'text', 'none', 'all', 100, '', '',
+        'If pattern isn\'t found, return an error.<br>Check <a href="https://secure.php.net/manual/en/pcre.pattern.php">pcre</a> / <a href="https://regexp101.com">regexp101.com</a> ',
+        'yes', '', '', ''),
+
+       (4, 'lastRun', 'Last run', 'readonly', '', 'text', 'specialchar', 'alnumx', 120, '', '', '', 'no', '', '', ''),
+       (4, 'lastStatus', 'Laststatus', 'readonly', '', 'text', 'specialchar', 'alnumx', 130, '', '50,6', '', 'no', '3',
+        '9',
+        '0'),
+       (4, 'inProgress', 'In progress since', 'show', '', 'text', 'specialchar', 'alnumx', 140,
+        'extraButtonInfo = Start time of a running job. When job is finished, this will be set back to 0. A new job will only be started, if this is 0. A progress duration >10mins will be treated as an error.',
+        '', '', 'no', '', '', '');
 
 CREATE TABLE IF NOT EXISTS `Split`
 (
diff --git a/extension/Tests/unit/core/BuildFormPlainTest.php b/extension/Tests/unit/core/BuildFormPlainTest.php
index d3291aecf95d593f13faacb7201c8d746dc1af76..acb3050b5150456cc8cea23204c5516bd91ff671 100644
--- a/extension/Tests/unit/core/BuildFormPlainTest.php
+++ b/extension/Tests/unit/core/BuildFormPlainTest.php
@@ -544,6 +544,7 @@ class BuildFormPlainTest extends AbstractDatabaseTest {
 
         $result = $build->buildSubrecord($formElement, 'name:1', '', $json);
         $this->assertEquals('<table class="table table-hover qfq-subrecord-table" ><thead><tr><th>id</th><th>name</th><th>firstName</th></tr></thead><tbody ><tr class="record" ><td><span class="text-muted">1</span></td><td>Doe</td><td>John</td></tr><tr class="record" ><td><span class="text-muted">2</span></td><td>Smith</td><td>Jane</td></tr></tbody></table>', $result);
+//        $this->assertEquals('Please save this record first.', $result);
 
         $this->store->setStore(['id' => 1], STORE_RECORD, true);
         $result = $build->buildSubrecord($formElement, 'name:1', '', $json);
diff --git a/extension/Tests/unit/core/database/AbstractDatabaseTest.php b/extension/Tests/unit/core/database/AbstractDatabaseTest.php
index ffd86b357c08d78d5f5b722d0a7ffd3e1600660f..0f68e57073a94c473888ea2dfbe2b5c87ea81d4c 100644
--- a/extension/Tests/unit/core/database/AbstractDatabaseTest.php
+++ b/extension/Tests/unit/core/database/AbstractDatabaseTest.php
@@ -19,7 +19,7 @@ abstract class AbstractDatabaseTest extends TestCase {
     static protected $mysqli = null;
 
     /**
-     * @var Database
+     * @var array
      */
     protected $dbArray = array();
 
diff --git a/extension/Tests/unit/core/store/StoreTest.php b/extension/Tests/unit/core/store/StoreTest.php
index 25f025a32f64c76e39bc865c78ac9380277dfebe..c9b0a68daaadf1517a0e3e86dcae63bd4ebd5fb5 100644
--- a/extension/Tests/unit/core/store/StoreTest.php
+++ b/extension/Tests/unit/core/store/StoreTest.php
@@ -10,6 +10,8 @@ namespace qfq;
 
 use PHPUnit\Framework\TestCase;
 
+#require_once(__DIR__ . '/../../../../Source/core/Constants.php');
+
 /**
  * Class StoreTest
  * @package qfq
@@ -31,7 +33,6 @@ class StoreTest extends TestCase {
     public function setUp() {
         // Client Variables has to setup before the first instantiation of 'Store'
         $_GET[CLIENT_RECORD_ID] = '1234';
-//        $_GET[CLIENT_SIP] = '12badcaffee34';
         $_GET['key01'] = '1234';
         $_POST['key02'] = '2345';
         $_POST['key03'] = '3456';
@@ -400,8 +401,9 @@ class StoreTest extends TestCase {
             'LDAP_1_RDN' => 'LDAP_1_RDN',
             'LDAP_1_PASSWORD' => 'LDAP_1_PASSWORD',
             SYSTEM_LABEL_ALIGN => SYSTEM_LABEL_ALIGN_LEFT,
-            F_FE_DATA_PATTERN_ERROR=> F_FE_DATA_PATTERN_ERROR_DEFAULT,
-            F_FE_DATA_PATTERN_ERROR_SYSTEM=> F_FE_DATA_PATTERN_ERROR_DEFAULT,
+            F_FE_DATA_PATTERN_ERROR => F_FE_DATA_PATTERN_ERROR_DEFAULT,
+            F_FE_DATA_PATTERN_ERROR_SYSTEM => F_FE_DATA_PATTERN_ERROR_DEFAULT,
+            SYSTEM_SECURITY_FAILED_AUTH_DELAY => '3',
         ];
 
         $body = <<< EOT
@@ -439,7 +441,7 @@ EOT;
         # The following won't be checked by content, cause they will change on different installations.
 //        foreach ([ SYSTEM_SQL_LOG, SYSTEM_MAIL_LOG, SYSTEM_SITE_PATH, SYSTEM_EXT_PATH, SYSTEM_SEND_E_MAIL] as $key) {
         foreach ([SYSTEM_SITE_PATH, SYSTEM_EXT_PATH, SYSTEM_SEND_E_MAIL, SYSTEM_SESSION_TIMEOUT_SECONDS] as $key) {
-            $this->assertTrue(isset($config[$key]), "Missing default value for '$key' "  );
+            $this->assertTrue(isset($config[$key]), "Missing default value for '$key' ");
             unset ($config[$key]);
         }
         // check default values
diff --git a/extension/composer.json b/extension/composer.json
index 30f5fc75f0333caca1b1ce27bcfdf613982fd805..7a4d37ab298f30bde3d206f0b3f1b56f9fed1d57 100644
--- a/extension/composer.json
+++ b/extension/composer.json
@@ -1,24 +1,24 @@
 {
-    "require": {
-        "phpoffice/phpspreadsheet": "^1.3",
-        "ext-json": "*"
-    },
-    "require-dev": {
-        "phpunit/phpunit": "^6.5"
-    },
-    "autoload": {
-        "psr-4": {
-            "qfq\\": ["qfq/",
-                "Source/api/",
-                "Source/external/",
-                "Source/core/",
-                "Source/core/database/",
-                "Source/core/exceptions/",
-                "Source/core/form/",
-                "Source/core/helper/",
-                "Source/core/report/",
-                "Source/core/store/",
-                "Source/core/typo3/"]
-        }
+  "require": {
+    "phpoffice/phpspreadsheet": "^1.3",
+    "ext-json": "*"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^6.5"
+  },
+  "autoload": {
+    "psr-4": {
+      "qfq\\": ["qfq/",
+        "Source/api/",
+        "Source/external/",
+        "Source/core/",
+        "Source/core/database/",
+        "Source/core/exceptions/",
+        "Source/core/form/",
+        "Source/core/helper/",
+        "Source/core/report/",
+        "Source/core/store/",
+        "Source/core/typo3/"]
     }
+  }
 }
diff --git a/extension/ext_conf_template.txt b/extension/ext_conf_template.txt
index 68abad4f0fee03c2fbde355b0b241da84446c8c7..2f680469f10c81da671a44a8392f3521494b77e7 100644
--- a/extension/ext_conf_template.txt
+++ b/extension/ext_conf_template.txt
@@ -103,6 +103,9 @@ securityShowMessage = true
 # cat=security/security; type=string; label='GET'-Parameter max length:Default is '50'. GET vars longer than 'x' character triggers an `attack-detected`.
 securityGetMaxLength = 50
 
+# cat=security/security; type=string; label=Failed auth delay in seconds:Default is '3'.
+securityFailedAuthDelay = 3
+
 # cat=security/security; type=string; label=Session Timeout in seconds:Default is empty to take the php.ini system value (minimum of  'session.cookie_lifetime' and 'session.gc_maxlifetime').
 sessionTimeoutSeconds =