diff --git a/CHANGELOG.md b/CHANGELOG.md
index d938f8b16f4ca7d086fcbbdc98e8921b2985e6bf..ad39f552eb24b6338489e1694ad0d0c3d0ef070d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,10 +36,72 @@ Features
 Bug Fixes
 ^^^^^^^^^
 
+Version 0.25.4
+--------------
+
+Date: 22.11.17
+
+Notes
+^^^^^
+
+* New keywords / features in report:
+
+  * `altsql`: Fire the query if there is no record selected in `sql`. Shown after `althead`
+  * `shead`: Static head - will always be shown (before `head`), independent of sql selects records or not.
+  * `stail`: Static tail - will always be shown (after `tail`), independent of sql selects records or not.
+
+Features
+^^^^^^^^
+
+* #2948 /altsql, shead, stail - new directives in Report.
+* #4255 / Attachments fuer 'Email'. Static files can be attached to mails.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4980 / Variables in Report: a) nested not replaced, b) 'rbeg' not replaced, c) missing unit tests.
+
+
+Version 0.25.3
+--------------
+
+Date: 19.11.2017
+
+Notes
+^^^^^
+
+* Report:
+
+  * Special column name 'sendmail': the old way of position dependent parameter are deprecated. Instead use the new
+    defined token. See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#column_sendmail
+
+  * Every row is now merged in STORE_RECORD. Inner SQL statement can now retrieve outer values via STORE_RECORD.
+    E.g. `{{column:R}}`. No more level keys!
+
+* The config.qfq.ini directive `VAR_ADD_BY_SQL` is replaced by `FILL_STORE_SYSTEM_BY_SQL_?`. Up to 3 statements are possible.
+
+Features
+^^^^^^^^
+
+* Report / sendmail: control via token.
+* #4967 / config.qfq.ini: Rename 'VAR_ADD_BY_SQL' to 'FILL_STORE_SYSTEM_BY_SQL_1'. Handle up to 3 FILL_STORE_SYSTEM_SQL_x.
+  Implement an optional error message together with a full stop.
+* #4766: Set STORE_RECORD in Report per row.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4966 / Variable {{feUser:T}} is not available in config.qfq.ini `FILL_STORE_SYSTEM_?` - changed ordering of store
+  initialization. Now: TCY...
+* #4944 / Delete: broken when using 'tableName' (instead of form).
+* #4904 / Undefined Index: DIRTY_FE_USER - PHP problem that constants cant be replaced inside of single ticks. Fixed.
+* #4965: insert path to QFQ cookie/session, to make usage of multiple QFQ installation on one host possible.
+
+
 Version 0.25.2
 --------------
 
-Date: 8.11.17
+Date: 8.11.2017
 
 Notes
 ^^^^^
@@ -66,7 +128,7 @@ Bug Fixes
 Version 0.25.1
 --------------
 
-Date: 3.11.17
+Date: 3.11.2017
 
 Bug Fixes
 ^^^^^^^^^
diff --git a/Makefile b/Makefile
index a75d02b7b1d0e1ec05226b143337c07dc35812e5..25607bb6a2b4c3b6b262655787f371e5641ac5c8 100644
--- a/Makefile
+++ b/Makefile
@@ -140,7 +140,7 @@ sonar: .sonar_scanner
 
 .PHONY: nightly maintainer-clean snapshot release git-revision t3sphinx build-dist make-dist-dir dist-move-doc dist-copy-extension pip-temp-directory plantuml sonar
 
-copyReleasNotes:
+copyReleaseNotes:
 	cp extension/Documentation/Release.rst extension/RELEASE.txt
 	cp extension/Documentation/Release.rst CHANGELOG.md
 
diff --git a/doc/NewVersion.md b/doc/NewVersion.md
index 77ddc67bbdcbe4580a503dccfc5f4e8426a43d62..b8ebaae2bdb1dac5cc2486e38b578389da6af951 100644
--- a/doc/NewVersion.md
+++ b/doc/NewVersion.md
@@ -20,7 +20,7 @@ Neue Versionsnummer
 
    * **Anpassen**: qfq/extension/Documentation/Release.rst
      
-   * Release.rst **verteilen**:  make copyReleasNotes
+   * Release.rst **verteilen**:  make copyReleaseNotes
    
    * Manuell:
     
@@ -48,8 +48,8 @@ Neue Versionsnummer
 
 6) **New Tag**: 
 
-   git tag v0.25.2
-   git push -u origin v0.25.2
+   git tag v0.25.4
+   git push -u origin v0.25.4
 
 7) PhpStorm: **Sync** all files to VM qfq.
 
diff --git a/extension/Documentation/Index.rst b/extension/Documentation/Index.rst
index d4315f6ffc324dabd3738ebda4a2f58e3ac0dafa..bc68233068be5138f8a26f9eb7c485d53e7a6efe 100644
--- a/extension/Documentation/Index.rst
+++ b/extension/Documentation/Index.rst
@@ -61,3 +61,4 @@ QFQ Extension
     	Release
     	Links
     	README
+    	License
diff --git a/extension/Documentation/License.rst b/extension/Documentation/License.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ac4f527828e89d48f7eef5c926e407ca63127a86
--- /dev/null
+++ b/extension/Documentation/License.rst
@@ -0,0 +1,35 @@
+.. ==================================================
+.. Header hierachy
+.. ==
+..  --
+..   ^^
+..    ''
+..     ;;
+..      ,,
+..
+.. --------------------------------------------------
+.. Best Practice T3 reST  https://docs.typo3.org/typo3cms/drafts/github/xperseguers/RstPrimer/
+.. External Links: `Bootstrap <http://getbootstrap.com/>`_:
+.. Add Images: https://wiki.typo3.org/ReST_Syntax#Images
+..
+.. -*- coding: utf-8 -*- with BOM.
+
+
+.. include:: Includes.txt
+
+.. _license:
+
+License
+=======
+
+* QFQ is licensed under GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007
+
+Software distributed together with QFQ
+======================================
+
+* jQuery - http://jquery.com
+* jQWidgets - https://www.jqwidgets.com
+* Bootstrap - http://getbootstrap.com
+* Fabric.js - http://fabricjs.com
+* Chart.js - https://github.com/nnnick/Chart.js.git
+* sendEmail - https://github.com/mogaal/sendemail
diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst
index 58f8a2fd638fff6b7ab0cb26407ea404236a4931..c0427c179af1ab94eacbba9e79900045e0d57385 100644
--- a/extension/Documentation/Manual.rst
+++ b/extension/Documentation/Manual.rst
@@ -61,14 +61,14 @@ For the `download`_ function, the programs `pdftk` and `file` are necessary to c
 Preparation for Ubuntu 14.04::
 
 	sudo apt-get install php5-mysqlnd php5-intl
-	sudo apt-get install pdftk file pdf2svg            # for file upload and PDF, PDF split
+	sudo apt-get install pdftk file                # for file upload and PDF
 	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, PDF split and 'HTML to PDF' (wkhtmltopdf)
+	sudo apt install pdftk libxrender1 file pdf2svg       # for file upload, PDF and 'HTML to PDF' (wkhtmltopdf), PDF split
 
 .. _wkhtml:
 
@@ -238,11 +238,15 @@ config.qfq.ini
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 | DB_INDEX_QFQ                | DB_INDEX_QFQ = 1                                | Optional. Default: 1.                                                      |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
-| SQL_LOG                     | SQL_LOG=sql.log                                 | Filename to log SQL commands: relative to <ext_dir> or absolute.           |
+| SQL_LOG                     | SQL_LOG=../../sql.log                           | Filename to log SQL commands: relative to <ext_dir> or absolute.           |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 | SQL_LOG_MODE                | SQL_LOG_MODE=modify                             | *all*: every statement will be logged - this might a lot.                  |
 |                             |                                                 | *modify*: log only statements who change data.                             |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| MAIL_LOG                    | SQL_LOG=../../mail.log                          | Filename to log `sendEmail` commands: relative to <ext_dir> or absolute.   |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| SEND_E_MAIL_OPTIONS         | SEND_E_MAIL_OPTIONS="-o tls=yes"                | General options. Check: http://caspian.dotconf.net/menu/Software/SendEmail |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 | SHOW_DEBUG_INFO             | SHOW_DEBUG_INFO=auto                            | FE - Possible values: yes|no|auto|download. For 'auto': If a BE User is    |
 |                             |                                                 | logged in, a debug information will be shown on the FE.                    |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
@@ -353,8 +357,8 @@ config.qfq.ini
 | DOCUMENTATION_QFQ           | DOCUMENTATION_QFQ=http://docs.typo3.org...      | Link to the online documentation of QFQ. Every QFQ installation also       |
 |                             |                                                 | contains a local copy: typo3conf/ext/qfq/Documentation/html/Manual.html    |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
-| VAR_ADD_BY_SQL              | VAR_ADD_BY_SQL = {{!SELECT s.id AS ...          | Specific values read from the database to fill the system store during QFQ |
-|                             |                                                 | load. See `VariablesAddBySql`_ for a usecase.                              |
+| FILL_STORE_SYSTEM_BY_SQL    | FILL_STORE_SYSTEM_BY_SQL = {{!SELECT s.id AS ...| Specific values read from the database to fill the system store during QFQ |
+|                             |                                                 | load. See `fillStoreSystemBySql`_ for a usecase.                           |
 +-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 | FORM_LANGUAGE_A_ID          | FORM_LANGUAGE_A__ID = 1                         | In Typo3 configured pageLanguage id. The number after the 'L' parameter.   |
 | FORM_LANGUAGE_B_ID          |                                                 |                                                                            |
@@ -445,7 +449,9 @@ Example: *typo3conf/config.qfq.ini*
 	; Local Documentation (doc fits to installed version):  typo3conf/ext/qfq/Documentation/html/Manual.html
 	;DOCUMENTATION_QFQ = https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html
 
-	;VAR_ADD_BY_SQL = 'SELECT s.id AS _periodId FROM Period AS s WHERE s.start<=NOW() ORDER BY s.start DESC LIMIT 1'
+	;FILL_STORE_SYSTEM_BY_SQL_1 = 'SELECT s.id AS periodId FROM Period AS s WHERE s.start<=NOW() ORDER BY s.start DESC LIMIT 1'
+   ; Important: only define an error message, if QFQ should stop running in case of an error or not exact 1 record.
+   ;FILL_STORE_SYSTEM_BY_SQL_ERROR_MSG_1 = No current period found
 
 	;FORM_LANGUAGE_A_ID = 1
 	;FORM_LANGUAGE_A_LABEL = english
@@ -474,17 +480,23 @@ E.g. to setup a contact address and reuse the information inside your installati
 
       {{ADMINISTRATIVE_CONTACT:Y}}, {{ADMINISTRATIVE_ADDRESS:Y}}, {{ADMINISTRATIVE_NAME}}
 
-.. _`VariablesAddBySql`:
+.. _`fillStoreSystemBySql`:
 
-Variables add by SQL
-^^^^^^^^^^^^^^^^^^^^
+Fill STORE_SYSTEM by SQL
+^^^^^^^^^^^^^^^^^^^^^^^^
 
-A specified SELECT statement in `config.qfq.ini`_ in variable `VAR_ADD_BY_SQL` fill be fired after filling the SYSTEM STORE.
-The query should have 0 (nothing happens) or 1 row. The column names and column values will be added as variables to the SYSTEM_STORE.
-Existing variables will be overwritten. Be carefull not to overwrite needed values.
+A specified SELECT statement in `config.qfq.ini`_ in variable `FILL_STORE_SYSTEM_BY_SQL_1` (or `FILL_STORE_SYSTEM_BY_SQL_2`,
+or `FILL_STORE_SYSTEM_BY_SQL_3`) will be fired. The query should have 0 (nothing happens) or 1 row. All columns will be
+**added** to the existing STORE_SYSTEM. Existing variables will be overwritten. Be careful not to overwrite system values.
  
-This option is usefull to make generic custom values, saved in the database, accessible to all QFQ Report and Forms.
-Access such variables as usual via `{{<varname>:Y}}`.
+This option is useful to make generic custom values, saved in the database, accessible to all QFQ Report and Forms.
+Access such variables via `{{<varname>:Y}}`.
+
+In case QFQ should stop working if a given query does not select exact one record (e.g. a missing period), define an
+error message: ::
+
+  FILL_STORE_SYSTEM_BY_SQL_1 = "SELECT name FROM Person WHERE name='Doe'"
+  FILL_STORE_SYSTEM_BY_SQL_ERROR_MSG_1 = Too many or to few "Doe's" in our database
 
 .. _`periodId`:
 
@@ -493,23 +505,21 @@ periodId
 
 This is
 
-* a usecase, implemented via `VariablesAddBySql`_,
+* a usecase, implemented via `fillStoreSystemBySql`_,
 * a way to access `Period.id` with respect to the current period (the period itself is custom defined).
 
 After a full QFQ installation, three things are prepared:
 
 * a table `Period` (extend / change it to your needs, fill them with your periods),
 * one sample record in table `Period`,
-* in `config.qfq.ini`_ the default definition of `VAR_ADD_BY_SQL` will set the variable `periodId` during QFQ load.
+* in `config.qfq.ini`_ the default definition of `FILL_STORE_SYSTEM_BY_SQL_1` will set the variable `periodId` during QFQ load.
 
 Websites, delivering semester data, schoolyears schedules, or any other type or periods, often need an index to the
-*current* period. One way is a) to mark the current period and b) to change the marker every time when the next period
-becomes current.
-The QFQ approach works without a marker and without manual intervention: the whished index will be computed during QFQ load.
+*current* period.
 
 In `config.qfq.ini`: ::
 
-	VAR_ADD_BY_SQL = 'SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1'
+	FILL_STORE_SYSTEM_BY_SQL_1 = '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: ::
@@ -629,13 +639,19 @@ QFQ Keywords (Bodytext)
  +-------------------+---------------------------------------------------------------------------------+
  | <level>.fend      | End token for every field (=column)                                             |
  +-------------------+---------------------------------------------------------------------------------+
- | <level>.head      | Start token for whole <level>                                                   |
+ | <level>.shead     | Static start token for whole <level>, independent if records are selected       |
+ |                   | Shown before `head`.                                                            |
+ +-------------------+---------------------------------------------------------------------------------+
+ | <level>.stail     | Static end token for whole <level>, independent if records are selected.        |
+ |                   | Shown after `tail`.                                                             |
  +-------------------+---------------------------------------------------------------------------------+
- | <level>.tail      | End token for whole <level>                                                     |
+ | <level>.head      | Dynamic start token for whole <level>. Only if at least one record is select.   |
+ +-------------------+---------------------------------------------------------------------------------+
+ | <level>.tail      | Dynamic end token for whole <level>. Only if at least one record is select.     |
  +-------------------+---------------------------------------------------------------------------------+
  | <level>.rbeg      | Start token for row.                                                            |
  +-------------------+---------------------------------------------------------------------------------+
- | <level>.rbgd      | Alternating (per row) token                                                     |
+ | <level>.rbgd      | Alternating (per row) token.                                                    |
  +-------------------+---------------------------------------------------------------------------------+
  | <level>.rend      | End token for row. Will be rendered **before** subsequent levels are processed  |
  +-------------------+---------------------------------------------------------------------------------+
@@ -647,7 +663,10 @@ QFQ Keywords (Bodytext)
  +-------------------+---------------------------------------------------------------------------------+
  | <level>.sql       | SQL Query                                                                       |
  +-------------------+---------------------------------------------------------------------------------+
- | <level>.althead   | If <level>.sql is empty, these token will be rendered                           |
+ | <level>.althead   | If <level>.sql is empty, these token will be rendered.                          |
+ +-------------------+---------------------------------------------------------------------------------+
+ | <level>.altsql    | If <level>.sql is empty, these query will be fired. No sub queries.             |
+ |                   | Shown after `althead`                                                           |
  +-------------------+---------------------------------------------------------------------------------+
  | debugShowBodyText | If='1' and config.qfq.ini:*SHOW_DEBUG_INFO = yes*, shows a tooltip with bodytext|
  +-------------------+---------------------------------------------------------------------------------+
@@ -656,10 +675,64 @@ QFQ Keywords (Bodytext)
  | sqlLogMode        | Overwrites config.qfq.ini: `SQL_LOG_MODE`_ . Only affects `Report`, not `Form`. |
  +-------------------+---------------------------------------------------------------------------------+
 
+Databases
+---------
+
+A Typo3 / QFQ Installation needs at least two databases. One for the Typo3 installation and one for QFQ.
+
+QFQ itself can be separated in 'QFQ system' and 'QFQ data' databases, if necessary (than at least three databases are needed).
+Furthermore a `Form` can operate on any additional database, specified per `Form`.parameter.dbIndex and configured via `config.qfq.ini`_.
+
+* Option 'A' is the most simple and commonly used.
+* Option 'B' separate the T3 and QFQ databases on two database hosts.
+* Option 'C' is like 'B' but with a shared 'QFQ data'-database between three 'Typo3 / QFQ' instances.
+* Further variants are possible.
+
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+|   | Domain         | Website Host | T3                            | QFQ system                   | QFQ data                         |
++===+================+==============+===============================+==============================+==================================+
+| A | standalone.edu | 'w'          | <dbHost>, <dbname>_t3, <dbnameSingle>_db                                                        |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+| B | appB1.edu      | 'wApp'       | <dbHostApp>, <dbnameB1>_t3    | <dbHostB1>, <dbnameApp>_db                                      |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+| B | appB2.edu      | 'wApp'       | <dbHostApp>, <dbnameB2>_t3    | <dbHostB2>, <dbnameApp>_db                                      |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+| C | appC1.edu      | 'wAppC'      | <dbHostAppC>, <dbnameC1>_t3   | <dbHostC>, <dbnameSysC1>_db  | <dbHostData>_db, <dbNameData>_db |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+| C | appC2.edu      | 'wAppC'      | <dbHostAppC>, <dbnameC2>_t3   | <dbHostC>, <dbnameSysC2>_db  | <dbHostData>_db, <dbNameData>_db |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+| C | appC3.edu      | 'wAppC3'     | <dbHostAppC3>, <dbnameC3>_t3  | <dbHostC3>, <dbnameSysC3>_db | <dbHostData>_db, <dbNameData>_db |
++---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
+
+In `config.qfq.ini`_ mutliple database credentials can be prepared. Mandatory is at least one credential setup like
+`DB_1_USER`, `DB_1_SERVER`, `DB_1_PASSWORD`, `DB_1_NAME`. The number '1' indicates the `dbIndex`. Increment the number
+to specify further database credential setups.
+
+Often the `DB_1_xxx` is identically to the used Typo3 database credentials.
+
+If not explicit specified, 'QFQ system' and 'QFQ database' will use the same database with the same credentials (setup 'A').
+
+To define separate 'QFQ data' and 'QFQ system', in `config.qfq.ini`_ define  `DB_1_USER`, ... (e.g. 'QFQ data') and `DB_2_USER`,
+... (e.g. 'QFQ system') and specify the assignment::
+
+	DB_INDEX_DATA = 1
+	DB_INDEX_QFQ = 2
+
+To let a form operate (show, load and save) on a different database, use `Form.parameter.dbIndexData` (see `form-parameter`_).
+
+Different QFQ versions, shared database
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When using different QFQ versions and a shared 'QFQ data'-database, there is some risk of conflicting
+'QFQ system' tables. Best is to always use the same QFQ version on all instances.
+
 .. _debug:
 
 Debug
-^^^^^
+=====
+
+SQL Logging
+-----------
 
 File: `config.qfq.ini`_
 
@@ -713,6 +786,11 @@ File: `config.qfq.ini`_
 
 .. _REDIRECT_ALL_MAIL_TO:
 
+Redirect all mail to (catch all)
+--------------------------------
+
+File: `config.qfq.ini`_
+
 * *REDIRECT_ALL_MAIL_TO=john@doe.com*
 
   * During the development, it might be helpful to configure a 'catch all' email address, which QFQ uses as the final receiver
@@ -726,57 +804,6 @@ File: `config.qfq.ini`_
 
 .. _variables:
 
-Databases
----------
-
-A Typo3 / QFQ Installation needs at least two databases. One for the Typo3 installation and one for QFQ.
-
-QFQ itself can be separated in 'QFQ system' and 'QFQ data' databases, if necessary (than at least three databases are needed).
-Furthermore a `Form` can operate on any additional database, specified per `Form`.parameter.dbIndex and configured via `config.qfq.ini`_.
-
-* Option 'A' is the most simple and commonly used.
-* Option 'B' separate the T3 and QFQ databases on two database hosts.
-* Option 'C' is like 'B' but with a shared 'QFQ data'-database between three 'Typo3 / QFQ' instances.
-* Further variants are possible.
-
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-|   | Domain         | Website Host | T3                            | QFQ system                   | QFQ data                         |
-+===+================+==============+===============================+==============================+==================================+
-| A | standalone.edu | 'w'          | <dbHost>, <dbname>_t3, <dbnameSingle>_db                                                        |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-| B | appB1.edu      | 'wApp'       | <dbHostApp>, <dbnameB1>_t3    | <dbHostB1>, <dbnameApp>_db                                      |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-| B | appB2.edu      | 'wApp'       | <dbHostApp>, <dbnameB2>_t3    | <dbHostB2>, <dbnameApp>_db                                      |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-| C | appC1.edu      | 'wAppC'      | <dbHostAppC>, <dbnameC1>_t3   | <dbHostC>, <dbnameSysC1>_db  | <dbHostData>_db, <dbNameData>_db |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-| C | appC2.edu      | 'wAppC'      | <dbHostAppC>, <dbnameC2>_t3   | <dbHostC>, <dbnameSysC2>_db  | <dbHostData>_db, <dbNameData>_db |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-| C | appC3.edu      | 'wAppC3'     | <dbHostAppC3>, <dbnameC3>_t3  | <dbHostC3>, <dbnameSysC3>_db | <dbHostData>_db, <dbNameData>_db |
-+---+----------------+--------------+-------------------------------+------------------------------+----------------------------------+
-
-In `config.qfq.ini`_ mutliple database credentials can be prepared. Mandatory is at least one credential setup like
-`DB_1_USER`, `DB_1_SERVER`, `DB_1_PASSWORD`, `DB_1_NAME`. The number '1' indicates the `dbIndex`. Increment the number
-to specify further database credential setups.
-
-Often the `DB_1_xxx` is identically to the used Typo3 database credentials.
-
-If not explicit specified, 'QFQ system' and 'QFQ database' will use the same database with the same credentials (setup 'A').
-
-To define separate 'QFQ data' and 'QFQ system', in `config.qfq.ini`_ define  `DB_1_USER`, ... (e.g. 'QFQ data') and `DB_2_USER`,
-... (e.g. 'QFQ system') and specify the assignment::
-
-	DB_INDEX_DATA = 1
-	DB_INDEX_QFQ = 2
-
-To let a form operate (show, load and save) on a different database, use `Form.parameter.dbIndexData` (see `form-parameter`_).
-
-Different QFQ versions, shared database
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When using different QFQ versions and a shared 'QFQ data'-database, there is some risk of conflicting
-'QFQ system' tables. Best is to always use the same QFQ version on all instances.
-
 Variable
 ========
 
@@ -793,7 +820,7 @@ Some examples, including nesting::
   #---------------------------------------------
   {{r}}
   {{index:FS}}
-  {{name:FS:alnumx:s}}
+  {{name:FS:alnumx:s:my default}}
 
   # SQL
   #---------------------------------------------
@@ -1305,13 +1332,9 @@ Store: *VARS* - V
  +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
  | slaveId                 | see *FormElement* `action`                                                                                                                 |
  +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
- | fileDestination         | Destination (path & filename) for an uploaded file. Defined in an 'upload'-FormElement.parameter. Valid: same as 'filename'.               |
- +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
  | filename                | Original filename of an uploaded file via an 'upload'-FormElement. Valid only during processing of the current 'upload'-formElement.       |
  +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
- | filenameBase            | Like 'filename', but without the extension (if there is one)                                                                               |
- +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
- | filenameExt             | Only the extension of 'filename' (if there is one)                                                                                         |
+ | fileDestination         | Destination (path & filename) for an uploaded file. Defined in an 'upload'-FormElement.parameter. Valid: same as 'filename'.               |
  +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
 
 The directive `fillStoreVar` will fill the this store with custom values. Existing values will remain or are overwritten,
@@ -1320,7 +1343,7 @@ depending of if they exist in the given statement. E.g.: ::
     fillStoreVar = {{!SELECT p.name, p.email FROM Person AS p WHERE p.id={{pId:S}} }}
 
 * After filling the store, the values can be retrieved via `{{name:v}}` and `{{email:V}}`.
-* Be carefull by specifying general purpose variables like `id`, `r`, `pageId` and so on. This might conflict with existing variables.
+* Be careful by specifying general purpose variables like `id`, `r`, `pageId` and so on. This might conflict with existing variables.
 * `fillStoreVar` can be used in `form-parameter`_ and `fe-parameter-attributes`_
 
 
@@ -1417,7 +1440,7 @@ Server configurations.
 
 To decide which Parameter should be placed on *Form.parameter* and which on *FormElement.parameter*: If LDAP access is ...
 
-* only necessary in one *FormElement*, most usefull setup is to specify all values in that specific *FormElement*,
+* only necessary in one *FormElement*, most useful setup is to specify all values in that specific *FormElement*,
 * needed on multiple *FormElement*s (of the same *Form*, e.g. one *input* with *typeAhead*, one *note* and one *action*), it's more
   efficient to specify the base parameter *ldapServer*, *ldapBaseDn* in *Form.parameter* and the rest on the current
   *FormElement*.
@@ -1545,7 +1568,7 @@ E.g.::
 
    Result: (& (|(a=*X*)(b=*X*)) (|(a=*Y*)(b=*Y*))
 
-Attention: this option is only usefull in specific environments. Only use it, if it is really needed. The query becomes
+Attention: this option is only useful in specific environments. Only use it, if it is really needed. The query becomes
 much more cpu / IO intensive.
 
 
@@ -2252,7 +2275,7 @@ Fields:
 |                     | 'disabled' )                | *Readonly*: user can't change any data. Data not saved.                                             |
 |                     |                             | *Disabled*: *FormElement* is not visible.                                                           |
 +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+
-|Mode sql             | `SELECT` statement with     | A value given here overwrites the setting from `mode`. Most usefull with :ref:`dynamic-update`.     |
+|Mode sql             | `SELECT` statement with     | A value given here overwrites the setting from `mode`. Most useful with :ref:`dynamic-update`.      |
 |                     | a value like in `mode`      | E.g.: {{SELECT IF( '{{otherFunding:FR:alnumx}}'='yes' ,'show', 'hidden' }}                          |
 +---------------------+-----------------------------+-----------------------------------------------------------------------------------------------------+
 |Class                | enum('native', 'action',    | Details below.                                                                                      |
@@ -3251,7 +3274,7 @@ Parameter: sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter
   * *sqlBefore*: always fired (before any *sqlInsert*, *sqlUpdate*, ..)
   * *sqlInsert*: fired if *slaveId* == `0` or *slaveId* == `''`.
   * *sqlUpdate*: fired if *slaveId* > `0`.
-  * *sqlDelete*: fired if *slaveId* > `0`, after *sqlInsert* or *sqlUpdate*. Be carefull not to delete filled records!
+  * *sqlDelete*: fired if *slaveId* > `0`, after *sqlInsert* or *sqlUpdate*. Be careful not to delete filled records!
     Always add a check, if values given, not to delete the record! *sqlHonorFormElements* helps to skip such checks.
   * *sqlAfter*: always fired (after *sqlInsert*, *sqlUpdate* or *sqlDelete*).
   * *sqlHonorFormElements*: list of *FormElement* names (this parameter is optional).
@@ -3347,10 +3370,14 @@ Type: sendmail
   * *sendMailFrom* - Sender of the email. Optional: 'realname <john@doe.com>'. **Mandatory**.
   * *sendMailSubject* - Subject of the email.
   * *sendMailReplyTo* - Reply this email address. Optional: 'realname <john@doe.com>'.
+  * *sendMailAttachment* - List of files to attach to the mail. Multiple files separated by comma.
+  * *sendMailHeader* - Specify custom header.
   * *sendMailFlagAutoSubmit* - **on|off** - If 'on' (default), the mail contains the header
     'Auto-Submitted: auto-send' - this suppress a) OoO replies, b) forwarding of emails.
   * *sendMailGrId* - Will be copied to the mailLog record. Helps to setup specific logfile queries.
   * *sendMailXId* - Will be copied to the mailLog record. Helps to setup specific logfile queries.
+  * *sendMailXId2* - Will be copied to the mailLog record. Helps to setup specific logfile queries.
+  * *sendMailXId3* - Will be copied to the mailLog record. Helps to setup specific logfile queries.
 
 * To use values of the submitted form, use the STORE_FORM. E.g. `{{name:F:allbut}}`
 * To use the `id` of a new created or already existing one, use the STORE_RECORD. E.g. `{{id:R}}`
@@ -4256,19 +4283,13 @@ FAQ
 Report
 ======
 
-The QFQ extension is activated through tt-content records. One or more tt-content records per page are necessary to render
-*forms* and *reports*.
-
 QFQ content element
 -------------------
 
-QFQ is used by configuring Typo3 content elements. Insert one or more QFQ content elements on a Typo3 page.
-Specify column and language per content record as wished.
-
-The title of the QFQ content element will not be rendered. It's only visible in the backend for orientation.
+The QFQ extension is activated through tt-content records. One or more tt-content records per page are necessary to render
+*forms* and *reports*. Specify column and language per content record as wished.
 
-General
--------
+The title of the QFQ content element will not be rendered. It's only visible in the backend for orientation of the webmaster.
 
 To display a report on any given TYPO3 page, create a content element of type 'QFQ Element' (plugin) on that page.
 
@@ -4317,15 +4338,18 @@ Syntax of `report`
 
     For **each** row of a query (this means *all* queries), all subqueries will be fired once.
 
-    *   E.g. if the outer query selects 5 rows, and a nested query always select 3 rows, than the total number of rows are 5 x 3 = 15 rows.
+    *   E.g. if the outer query selects 5 rows, and a nested query select 3 rows, than the total number of rows are 5 x 3 = 15 rows.
 
     There is a set of **variables** that will get replaced before the SQL-Query gets executed:
 
-        Column values of the recent rows: {{<level>.<columnname>}}
+        Variables from specific stores: {{<name>[:<store/s>[:...]]}}
 
-        Global variables: {{global.<name>}}
+        STORE_RECORD is automatically merged with the existing STORE_RECORD content and the current row. Use the STORE_RECORD
+        to access outer level values.
 
-        Variables from specific stores: {{<name>[:<store/s>[:<sanitize class>]]}}
+        Column values of a given row: {{<level>.<columnname>}}
+
+        Global variables: {{global.<name>}}
 
         Current row index: {{<level>.line.count}}
 
@@ -4338,22 +4362,21 @@ Syntax of `report`
     Be aware that line.count / line.total have to be known before the query is fired. E.g. `10.sql = SELECT {{10.line.count}}, ... WHERE {{10.line.count}} = ...`
     won't work as expected. `{{10.line.count}}` can't be replaced before the query is fired, but will be replaced during processing the result!
 
-
     Different types of SQL queries are possible: SELECT, INSERT, UPDATE, DELETE, SHOW
 
     Only SELECT and SHOW queries will fire subqueries.
 
-    *   Processing of the resulting rows and columns:
+    Processing of the resulting rows and columns:
 
-    *   In general, all columns of all rows will be printed out sequentially.
+    * In general, all columns of all rows will be printed out sequentially.
 
-        On a per column base, printing of columns can be suppressed. This might be useful to select values which will be
-        accessed later on in another query via the {{level.columnname}} variable. To suppress printing of a column, use a
-        underscore as column name prefix.
+    * On a per column base, printing of columns can be suppressed by starting the columnname 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. `SELECT id AS _id`
 
-        Reserved column names have a special meaning and will be processed in a special way. See `Processing of columns in the SQL result`_ for details.
+    Reserved column names have a special meaning and will be processed in a special way. See `Processing of columns in the SQL result`_ for details.
 
-        There are extensive ways to wrap columns and rows automatically. See :ref:`wrapping-rows-and-columns`
+    There are extensive ways to wrap columns and rows automatically. See :ref:`wrapping-rows-and-columns`
 
 Debug the bodytext
 ------------------
@@ -4496,7 +4519,33 @@ Be careful to:
 Access column values
 ^^^^^^^^^^^^^^^^^^^^
 
-Columns of the upper / outer level result can be accessed via variables, eg. {{10.pId}} will be replaced by the value in the pId column.
+Columns of the upper / outer level result can be accessed via variables in two ways
+
+ * STORE_RECORD: `{{pId:R}}`
+ * Level Key: `{{10.pId}}`
+
+The STORE_RECORD will always be merged with previous content. The Level Keys are unique.
+
+Example STORE_RECORD: ::
+
+  10.sql= SELECT p.id AS _pId, p.name FROM Person AS p
+  10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{pId:R}}
+  10.5.20.sql = SELECT '{{pId:R}}'
+  10.10.sql = SELECT '{{pId:R}}'
+
+The line '10.10' will output 'dummy' in cases where there is at least one corresponding address.
+If there are no addresses (all persons) it reports the person id.
+If there is at least one address, it reports 'dummy', cause that's the last stored content. This behaviour might change in the future.
+
+Example 'Level Key': ::
+
+  10.sql= SELECT p.id AS _pId, p.name FROM Person AS p
+  10.5.sql = SELECT adr.city, 'dummy' AS _pId FROM Address AS adr WHERE adr.pId={{10.pId}}
+  10.5.20.sql = SELECT '{{10.pId}}'
+  10.10.sql = SELECT '{{10.pId}}'
+
+
+Notes to the level level:
 
 +-------------+------------------------------------------------------------------------------------------------------------------------+
 | Levels      |A report is divided into levels. The Example has levels *10*, *20*, *30*, *30.5*, *30.5.1*, *50*                        |
@@ -4934,7 +4983,7 @@ is allowed to access: ::
       page.10.value = Please access from localhost or log in as 'admin' user.
    [global]
 
-..
+.. _column_pageX:
 
 Columns: _page[X]
 ^^^^^^^^^^^^^^^^^
@@ -5002,6 +5051,8 @@ The colum name is composed of the string *page* and a trailing character to spec
 |<create sip> |s                                                                                                |                                                          |'s': create a SIP                                              |
 +-------------+-------------------------------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------+
 
+.. _column_paged:
+
 Column: _paged
 ^^^^^^^^^^^^^^
 
@@ -5047,6 +5098,8 @@ Examples:
     SELECT 'U:table=Person&r=123|q:Do you want delete John Doe?' AS _paged
     SELECT 'U:form=person-main&r=123|q:Do you want delete John Doe?' AS _paged
 
+.. _column_ppageX:
+
 Columns: _Page[X]
 ^^^^^^^^^^^^^^^^^
 
@@ -5057,7 +5110,7 @@ Columns: _Page[X]
 
     "[<page id|alias>[&param=value&...]] | [text] | [tooltip] | [question parameter] | [class] | [target] | [render mode]" as _Pagee.
 
-..
+.. _column_ppaged:
 
 Column: _Paged
 ^^^^^^^^^^^^^^
@@ -5070,7 +5123,7 @@ Column: _Paged
     "[table=<table name>&r-<record id>[&param=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.
     "[form=<form name>&r-<record id>[&param=value&...] | [text] | [tooltip] | [question parameter] | [class] | [render mode]" as _Paged.
 
-..
+.. _column_vertical:
 
 Column: _vertical
 ^^^^^^^^^^^^^^^^^
@@ -5122,7 +5175,7 @@ angle.
 
 ..
 
-
+.. _column_mailto:
 
 Column: _mailto
 ^^^^^^^^^^^^^^^
@@ -5171,11 +5224,12 @@ Easily create Email links.
 
 ..
 
+.. _column_sendmail:
 
 Column: _sendmail
 ^^^^^^^^^^^^^^^^^
 
-<TO:email[,email]>|<FROM:email>|<subject>|<body>|[<REPLY-TO:email>]|[<flag autosubmit: on /off>]|[<grid>]|[xId]|<CC:email[,email]>|<BCC:email[,email]>
+t:<TO:email[,email]>|f:<FROM:email>|s:<subject>|b:<body>|[F:<REPLY-TO:email>]|[a:<flag autosubmit: on /off>]|[g:<grid>]|[x:xId]|[c:<CC:email[,email]]>|[B:<BCC:email[,email]]|[y:xId2]|[z:xId3]>
 
 
 Send text emails. Every mail will be logged in the table `mailLog`.
@@ -5184,54 +5238,60 @@ Send text emails. Every mail will be logged in the table `mailLog`.
 
 ::
 
-    SELECT "john@doe.com|jane@doe.com|Reminder tomorrow|Please dont miss the meeting tomorrow" AS _sendmail
+    SELECT "t:john@doe.com|f:jane@doe.com|s:Reminder tomorrow|b:Please dont miss the meeting tomorrow" AS _sendmail
+    SELECT "t:john@doe.com|f:jane@doe.com|s:Reminder tomorrow|b:Please dont miss the meeting tomorrow|A:off|g:1|x:2|y:3|z:4" AS _sendmail
 
 ..
 
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|**Parameter**                                               |**Description**                                                                           |**Required**|
-+============================================================+==========================================================================================+============+
-|TO:email[,email]                                            |Comma-separated list of receiver email addresses. Optional: `realname <john@doe.com>`     |    yes     |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|FROM:email                                                  |Sender of the email. Optional: 'realname <john@doe.com>'                                  |    yes     |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|subject                                                     |Subject of the email                                                                      |    yes     |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|body                                                        |Message                                                                                   |    yes     |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|REPLY-TO:email                                              |Email address to reply to (if different from sender)                                      |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|flagAutoSubmit  'on' / 'off'                                |If 'on' (default), add mail header 'Auto-Submitted: auto-send' - suppress OoO replies     |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|grId                                                        |Will be copied to the mailLog record. Helps to setup specific logfile queries             |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|xId                                                         |Will be copied to the mailLog record. Helps to setup specific logfile queries             |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|CC:email[,email]                                            |Comma-separated list of receiver email addresses. Optional: 'realname <john@doe.com>'     |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
-|BCC:email[,email]                                           |Comma-separated list of receiver email addresses. Optional: 'realname <john@doe.com>'     |            |
-+------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+|***Token** | **Parameter**                  |**Description**                                                                                   |**Required**|
++===+========================================+==================================================================================================+============+
+| f | FROM:email                             |**FROM**: Sender of the email. Optional: 'realname <john@doe.com>'                                |    yes     |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| t | email[,email]                          |**TO**: Comma separated list of receiver email addresses. Optional: `realname <john@doe.com>`     |    yes     |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| c | email[,email]                          |**CC**: Comma separated list of receiver email addresses. Optional: 'realname <john@doe.com>'     |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| B | email[,email]                          |**BCC**: Comma separated list of receiver email addresses. Optional: 'realname <john@doe.com>'    |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| r | REPLY-TO:email                         |**Reply-to**: Email address to reply to (if different from sender)                                |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| s | Subject                                |**Subject**: Subject of the email                                                                 |    yes     |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| b | Body                                   |**Body**: Message                                                                                 |    yes     |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| h | Header                                 |**Custom Header**: Separate multiple header with \r\n                                             |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| a | Attachment                             |**Attachment**: Comma separated list of filenames to attach to the mail                           |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| A | flagAutoSubmit  'on' / 'off'           |If 'on' (default), add mail header 'Auto-Submitted: auto-send' - suppress OoO replies             |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| g | grId                                   |Will be copied to the mailLog record. Helps to setup specific logfile queries                     |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| x | xId                                    |Will be copied to the mailLog record. Helps to setup specific logfile queries                     |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| y | xId2                                   |Will be copied to the mailLog record. Helps to setup specific logfile queries                     |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
+| z | xId3                                   |Will be copied to the mailLog record. Helps to setup specific logfile queries                     |            |
++---+----------------------------------------+--------------------------------------------------------------------------------------------------+------------+
 
 
 **Minimal Example**
 
 ::
 
-
-    10.sql = SELECT "john.doe@example.com|company@example.com|Latest News|The new version is now available." AS _sendmail
+    10.sql = SELECT "t:john.doe@example.com|f:company@example.com|s:Latest News|b:The new version is now available." AS _sendmail
 
 ..
 
-
-
 This will send an email with subject *Latest News* from company@example.com to john.doe@example.com.
 
 **Advanced Examples**
 
 ::
 
-
-    10.sql = SELECT "customer1@example.com,Firstname Lastname <customer2@example.com>, Firstname Lastname <customer3@example.com>|company@example.com|Latest News|The new version is now available.|sales@example.com|on|101|222|ceo@example.com|backup@example.com" AS _sendmail
+    10.sql = SELECT "t:customer1@example.com,Firstname Lastname <customer2@example.com>, Firstname Lastname <customer3@example.com>|
+                     f:company@example.com|s:Latest News|b:The new version is now available.|r:sales@example.com|A:on|g:101|x:222|c:ceo@example.com|B:backup@example.com" AS _sendmail
 
 ..
 
@@ -5241,6 +5301,7 @@ Additional the CEO as well as backup will receive the mail via CC and BCC.
 
 For debugging, please check `REDIRECT_ALL_MAIL_TO`_.
 
+.. _column_img:
 
 Column: _img
 ^^^^^^^^^^^^
@@ -5299,7 +5360,7 @@ Renders images. Allows to define an alternative text and a title attribute for t
 
 ..
 
-
+.. _column_exec:
 
 Column: _exec
 ^^^^^^^^^^^^^
@@ -5334,6 +5395,7 @@ Runs batch files or executables on the webserver. In case of an error, returncod
 
 ..
 
+.. _column_pdf:
 
 Column: _pdf | _file | _zip
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5361,7 +5423,7 @@ Most of the other Link-Class attributes can be used to customize the link.
 
 		SELECT "d:complete.pdf|t:Download PDF|f:fileadmin/test.pdf|U:id=export&r=1|u:www.w3c.org" AS _pdf
 
-..
+.. _column_ppdf:
 
 Column: _Pdf | _File | _Zip
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -5384,6 +5446,7 @@ A limited set of attributes is supported: ::
 
 ..
 
+.. _column_F:
 
 Column: _F
 ^^^^^^^^^^
@@ -5819,6 +5882,8 @@ Same as above, but written in the nested notation ::
 
 * Columns starting with a '_' won't be printed but can be accessed as regular columns.
 
+.. _help:
+
 Help
 ====
 
@@ -5832,6 +5897,39 @@ Tips:
 	* Always check the Javascript console of your browser, see `javascriptProblem`_.
 	* Always check the Webserver logfiles, see `webserverErrorLog`_.
 
+QFQ specific
+------------
+
+Variable empty: {{...}}
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Specify the required sanitize class. Remember: for STORE_FORM and STORE_CLIENT the default is `digit`. This means if
+the variable content is a string, this violates the sanitize class and the replaced content will be an empty string!
+
+Form: put the problematic variable or SQL statement in the 'title' or note 'field' of a `FormElement`. This should show
+the content. For SQL statements insert a character before the SQL Keyword to avoid SQL triggering.
+
+Error read file config.qfq.ini: syntax error on line xx
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Check the given line number. If it's a SQL statement, enclose it in single or double ticks.
+
+Logging
+-------
+
+General webserver error log
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For apache: /var/log/apache2/error_log
+
+Especially if you got a blank page (no rendering at all), this is typically an uncaught PHP error. Check the error message
+and report the bug.
+
+Call to undefined function qfq\\mb_internal_encoding()
+''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Check that all required php modules are installed. See `preparation`_.
+
 
 Error Messages
 --------------
@@ -5856,22 +5954,4 @@ Open the 'Webdeveloper Tools' (FF: F12, Chrome/Opera: Right mouse click > Inspec
 
 .. _`webserverErrorLog`:
 
-Webserver error log
--------------------
-
-For apache: /var/log/apache2/error_log
-
 
-Call to undefined function qfq\\mb_internal_encoding()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Check that all required php modules are installed. See `preparation`_.
-
-QFQ specific
-------------
-
-Variable empty: {{...}}
-^^^^^^^^^^^^^^^^^^^^^^^
-
-Specify the required sanitize class. Remember: for STORE_FORM and STORE_CLIENT the default is `digit`. This means if
-the variable content is a string, this violates the sanitize class and the replaced content will be an empty string!
\ No newline at end of file
diff --git a/extension/Documentation/Release.rst b/extension/Documentation/Release.rst
index d938f8b16f4ca7d086fcbbdc98e8921b2985e6bf..ad39f552eb24b6338489e1694ad0d0c3d0ef070d 100644
--- a/extension/Documentation/Release.rst
+++ b/extension/Documentation/Release.rst
@@ -36,10 +36,72 @@ Features
 Bug Fixes
 ^^^^^^^^^
 
+Version 0.25.4
+--------------
+
+Date: 22.11.17
+
+Notes
+^^^^^
+
+* New keywords / features in report:
+
+  * `altsql`: Fire the query if there is no record selected in `sql`. Shown after `althead`
+  * `shead`: Static head - will always be shown (before `head`), independent of sql selects records or not.
+  * `stail`: Static tail - will always be shown (after `tail`), independent of sql selects records or not.
+
+Features
+^^^^^^^^
+
+* #2948 /altsql, shead, stail - new directives in Report.
+* #4255 / Attachments fuer 'Email'. Static files can be attached to mails.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4980 / Variables in Report: a) nested not replaced, b) 'rbeg' not replaced, c) missing unit tests.
+
+
+Version 0.25.3
+--------------
+
+Date: 19.11.2017
+
+Notes
+^^^^^
+
+* Report:
+
+  * Special column name 'sendmail': the old way of position dependent parameter are deprecated. Instead use the new
+    defined token. See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#column_sendmail
+
+  * Every row is now merged in STORE_RECORD. Inner SQL statement can now retrieve outer values via STORE_RECORD.
+    E.g. `{{column:R}}`. No more level keys!
+
+* The config.qfq.ini directive `VAR_ADD_BY_SQL` is replaced by `FILL_STORE_SYSTEM_BY_SQL_?`. Up to 3 statements are possible.
+
+Features
+^^^^^^^^
+
+* Report / sendmail: control via token.
+* #4967 / config.qfq.ini: Rename 'VAR_ADD_BY_SQL' to 'FILL_STORE_SYSTEM_BY_SQL_1'. Handle up to 3 FILL_STORE_SYSTEM_SQL_x.
+  Implement an optional error message together with a full stop.
+* #4766: Set STORE_RECORD in Report per row.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4966 / Variable {{feUser:T}} is not available in config.qfq.ini `FILL_STORE_SYSTEM_?` - changed ordering of store
+  initialization. Now: TCY...
+* #4944 / Delete: broken when using 'tableName' (instead of form).
+* #4904 / Undefined Index: DIRTY_FE_USER - PHP problem that constants cant be replaced inside of single ticks. Fixed.
+* #4965: insert path to QFQ cookie/session, to make usage of multiple QFQ installation on one host possible.
+
+
 Version 0.25.2
 --------------
 
-Date: 8.11.17
+Date: 8.11.2017
 
 Notes
 ^^^^^
@@ -66,7 +128,7 @@ Bug Fixes
 Version 0.25.1
 --------------
 
-Date: 3.11.17
+Date: 3.11.2017
 
 Bug Fixes
 ^^^^^^^^^
diff --git a/extension/Documentation/Settings.cfg b/extension/Documentation/Settings.cfg
index 049c4ba5eaf2c7831aed485dbe937243d2f44402..d68e42d958b18dc3e12ffb07f635a4eef9663d50 100644
--- a/extension/Documentation/Settings.cfg
+++ b/extension/Documentation/Settings.cfg
@@ -3,7 +3,7 @@
 
 project     = QFQ - Quick Form Query
 version     = 0.25
-release     = 0.25.2
+release     = 0.25.4
 t3author    = Carsten Rose
 copyright   = since 2017 by the author
 
diff --git a/extension/Documentation/_make/conf.py b/extension/Documentation/_make/conf.py
index 00defb2ddadb07b49d7f874c1bb3367644b19686..9c190b27927769178dc9900413d288e8e4d7233f 100644
--- a/extension/Documentation/_make/conf.py
+++ b/extension/Documentation/_make/conf.py
@@ -59,7 +59,7 @@ copyright = u'2017, Carsten Rose'
 # The short X.Y version.
 version = '0.25'
 # The full version, including alpha/beta/rc tags.
-release = '0.25.2'
+release = '0.25.4'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/extension/RELEASE.txt b/extension/RELEASE.txt
index d938f8b16f4ca7d086fcbbdc98e8921b2985e6bf..ad39f552eb24b6338489e1694ad0d0c3d0ef070d 100644
--- a/extension/RELEASE.txt
+++ b/extension/RELEASE.txt
@@ -36,10 +36,72 @@ Features
 Bug Fixes
 ^^^^^^^^^
 
+Version 0.25.4
+--------------
+
+Date: 22.11.17
+
+Notes
+^^^^^
+
+* New keywords / features in report:
+
+  * `altsql`: Fire the query if there is no record selected in `sql`. Shown after `althead`
+  * `shead`: Static head - will always be shown (before `head`), independent of sql selects records or not.
+  * `stail`: Static tail - will always be shown (after `tail`), independent of sql selects records or not.
+
+Features
+^^^^^^^^
+
+* #2948 /altsql, shead, stail - new directives in Report.
+* #4255 / Attachments fuer 'Email'. Static files can be attached to mails.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4980 / Variables in Report: a) nested not replaced, b) 'rbeg' not replaced, c) missing unit tests.
+
+
+Version 0.25.3
+--------------
+
+Date: 19.11.2017
+
+Notes
+^^^^^
+
+* Report:
+
+  * Special column name 'sendmail': the old way of position dependent parameter are deprecated. Instead use the new
+    defined token. See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#column_sendmail
+
+  * Every row is now merged in STORE_RECORD. Inner SQL statement can now retrieve outer values via STORE_RECORD.
+    E.g. `{{column:R}}`. No more level keys!
+
+* The config.qfq.ini directive `VAR_ADD_BY_SQL` is replaced by `FILL_STORE_SYSTEM_BY_SQL_?`. Up to 3 statements are possible.
+
+Features
+^^^^^^^^
+
+* Report / sendmail: control via token.
+* #4967 / config.qfq.ini: Rename 'VAR_ADD_BY_SQL' to 'FILL_STORE_SYSTEM_BY_SQL_1'. Handle up to 3 FILL_STORE_SYSTEM_SQL_x.
+  Implement an optional error message together with a full stop.
+* #4766: Set STORE_RECORD in Report per row.
+
+Bug Fixes
+^^^^^^^^^
+
+* #4966 / Variable {{feUser:T}} is not available in config.qfq.ini `FILL_STORE_SYSTEM_?` - changed ordering of store
+  initialization. Now: TCY...
+* #4944 / Delete: broken when using 'tableName' (instead of form).
+* #4904 / Undefined Index: DIRTY_FE_USER - PHP problem that constants cant be replaced inside of single ticks. Fixed.
+* #4965: insert path to QFQ cookie/session, to make usage of multiple QFQ installation on one host possible.
+
+
 Version 0.25.2
 --------------
 
-Date: 8.11.17
+Date: 8.11.2017
 
 Notes
 ^^^^^
@@ -66,7 +128,7 @@ Bug Fixes
 Version 0.25.1
 --------------
 
-Date: 3.11.17
+Date: 3.11.2017
 
 Bug Fixes
 ^^^^^^^^^
diff --git a/extension/config.qfq.example.ini b/extension/config.qfq.example.ini
index 812edd509edb866eebf45302bd3e8957ce820ecb..08b14d462ce934a3712f1309f74f768da4e11a2f 100644
--- a/extension/config.qfq.example.ini
+++ b/extension/config.qfq.example.ini
@@ -23,6 +23,9 @@ SQL_LOG = ../../sql.log
 ; all|modify|error|none
 SQL_LOG_MODE = modify
 
+;MAIL_LOG = ../../mail.log
+;SEND_E_MAIL_OPTIONS = "-o ... "  - check http://caspian.dotconf.net/menu/Software/SendEmail
+
 ; [auto|yes|no][,download]. 'auto': if BE User is logged in the value will be replaced by 'yes', else 'no'. Additional choose 'download'.
 SHOW_DEBUG_INFO = auto
 
@@ -108,7 +111,9 @@ WKHTMLTOPDF = /opt/wkhtmltox/bin/wkhtmltopdf
 ; Local Documentation (doc fits to installed version):  typo3conf/ext/qfq/Documentation/html/Manual.html
 ;DOCUMENTATION_QFQ = https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html
 
-; VAR_ADD_BY_SQL = SELECT id AS _periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1
+; FILL_STORE_SYSTEM_BY_SQL_1 = "SELECT id AS _periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1"
+; Important: only define an error message, if QFQ should stop running in case of an SQL error or not exact 1 record.
+; FILL_STORE_SYSTEM_BY_SQL_ERROR_MSG_1 = No current period found
 
 ; FORM_LANGUAGE_A_ID =       E.g. FORM_LANGUAGE_A_ID = 1
 ; FORM_LANGUAGE_A_LABEL =    E.g. FORM_LANGUAGE_A_ID = English
diff --git a/extension/ext_emconf.php b/extension/ext_emconf.php
index 4d144aaffe8b9327bccca0d943b5a36d1850c1bf..7995fad2ff5719b6eed95fdf28796b3f29627090 100644
--- a/extension/ext_emconf.php
+++ b/extension/ext_emconf.php
@@ -10,6 +10,6 @@ $EM_CONF[$_EXTKEY] = array(
     'dependencies' => 'fluid,extbase',
     'clearcacheonload' => true,
     'state' => 'alpha',
-    'version' => '0.25.2'
+    'version' => '0.25.4'
 );
 
diff --git a/extension/qfq/external/AutoCron.php b/extension/qfq/external/AutoCron.php
index e35b0e8e091743d79488280d7053313a8a4094d0..730f9b361181b59796f8b1d6cd9aae30cdeaec8a 100644
--- a/extension/qfq/external/AutoCron.php
+++ b/extension/qfq/external/AutoCron.php
@@ -142,7 +142,8 @@ class AutoCron {
      */
     private function mailEntryFill(array $mailEntry) {
         foreach ([FE_SENDMAIL_TO, FE_SENDMAIL_CC, FE_SENDMAIL_BCC, FE_SENDMAIL_FROM, FE_SENDMAIL_SUBJECT,
-                     FE_SENDMAIL_REPLY_TO, FE_SENDMAIL_FLAG_AUTO_SUBMIT, FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID] as $key) {
+                     FE_SENDMAIL_REPLY_TO, FE_SENDMAIL_FLAG_AUTO_SUBMIT, FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID,
+                     FE_SENDMAIL_X_ID2, FE_SENDMAIL_X_ID3] as $key) {
             if (!isset($mailEntry[$key])) {
                 $mailEntry[$key] = '';
             }
@@ -184,6 +185,9 @@ class AutoCron {
             $mail[SENDMAIL_IDX_X_ID] = $this->evaluate->parse($mailEntry[FE_SENDMAIL_X_ID]);
             $mail[SENDMAIL_IDX_RECEIVER_CC] = $this->evaluate->parse($mailEntry[FE_SENDMAIL_CC]);
             $mail[SENDMAIL_IDX_RECEIVER_BCC] = $this->evaluate->parse($mailEntry[FE_SENDMAIL_BCC]);
+            $mail[SENDMAIL_IDX_X_ID2] = $this->evaluate->parse($mailEntry[FE_SENDMAIL_X_ID2]);
+            $mail[SENDMAIL_IDX_X_ID3] = $this->evaluate->parse($mailEntry[FE_SENDMAIL_X_ID3]);
+
             $mail[SENDMAIL_IDX_SRC] = "AutoCron: Cron.id=" . $job[COLUMN_ID];
 
             // Mail: send
diff --git a/extension/qfq/external/sendEmail b/extension/qfq/external/sendEmail
new file mode 100755
index 0000000000000000000000000000000000000000..9f9392e6e4bd9d2ef27825b808f9217e7bcfb9b5
--- /dev/null
+++ b/extension/qfq/external/sendEmail
@@ -0,0 +1,2235 @@
+#!/usr/bin/perl -w
+##############################################################################
+## sendEmail
+## Written by: Brandon Zehm <caspian@dotconf.net>
+##
+## License:
+##  sendEmail (hereafter referred to as "program") is free software;
+##  you can redistribute it and/or modify it under the terms of the GNU General
+##  Public License as published by the Free Software Foundation; either version
+##  2 of the License, or (at your option) any later version.
+##  When redistributing modified versions of this source code it is recommended
+##  that that this disclaimer and the above coder's names are included in the
+##  modified code.
+##
+## Disclaimer:
+##  This program is provided with no warranty of any kind, either expressed or
+##  implied.  It is the responsibility of the user (you) to fully research and
+##  comprehend the usage of this program.  As with any tool, it can be misused,
+##  either intentionally (you're a vandal) or unintentionally (you're a moron).
+##  THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM
+##  or anything that happens because of your use (or misuse) of this program,
+##  including but not limited to anything you, your lawyers, or anyone else
+##  can dream up.  And now, a relevant quote directly from the GPL:
+##
+## NO WARRANTY
+##
+##  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+##  FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+##  OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+##  PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+##  OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+##  TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+##  PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+##  REPAIR OR CORRECTION.
+##
+##############################################################################
+use strict;
+use IO::Socket;
+
+
+########################
+##  Global Variables  ##
+########################
+
+my %conf = (
+    ## General
+    "programName"          => $0,                                  ## The name of this program
+    "version"              => '1.56',                              ## The version of this program
+    "authorName"           => 'Brandon Zehm',                      ## Author's Name
+    "authorEmail"          => 'caspian@dotconf.net',               ## Author's Email Address
+    "timezone"             => '+0000',                             ## We always use +0000 for the time zone
+    "hostname"             => 'changeme',                          ## Used in printmsg() for all output (is updated later in the script).
+    "debug"                => 0,                                   ## Default debug level
+    "error"                => '',                                  ## Error messages will often be stored here
+    
+    ## Logging
+    "stdout"               => 1,
+    "logging"              => 0,                                   ## If this is true the printmsg function prints to the log file
+    "logFile"              => '',                                  ## If this is specified (form the command line via -l) this file will be used for logging.
+    
+    ## Network
+    "server"               => 'localhost',                         ## Default SMTP server
+    "port"                 => 25,                                  ## Default port
+    "bindaddr"             => '',                                  ## Default local bind address
+    "alarm"                => '',                                  ## Default timeout for connects and reads, this gets set from $opt{'timeout'}
+    "tls_client"           => 0,                                   ## If TLS is supported by the client (us)
+    "tls_server"           => 0,                                   ## If TLS is supported by the remote SMTP server
+    
+    ## Email
+    "delimiter"            => "----MIME delimiter for sendEmail-"  ## MIME Delimiter
+                              . rand(1000000),                     ## Add some randomness to the delimiter
+    "Message-ID"           => rand(1000000) . "-sendEmail",        ## Message-ID for email header
+    
+);
+
+
+## This hash stores the options passed on the command line via the -o option.
+my %opt = (
+    ## Addressing
+    "reply-to"             => '',                                  ## Reply-To field
+    
+    ## Message
+    "message-file"         => '',                                  ## File to read message body from
+    "message-header"       => '',                                  ## Additional email header line(s)
+    "message-format"       => 'normal',                            ## If "raw" is specified the message is sent unmodified
+    "message-charset"      => 'iso-8859-1',                        ## Message character-set
+    "message-content-type" => 'auto',                              ## auto, text, html or an actual string to put into the content-type header.
+    
+    ## Network
+    "timeout"              => 60,                                  ## Default timeout for connects and reads, this is copied to $conf{'alarm'} later.
+    "fqdn"                 => 'changeme',                          ## FQDN of this machine, used during SMTP communication (is updated later in the script).
+    
+    ## eSMTP
+    "username"             => '',                                  ## Username used in SMTP Auth
+    "password"             => '',                                  ## Password used in SMTP Auth
+    "tls"                  => 'auto',                              ## Enable or disable TLS support.  Options: auto, yes, no
+    
+);
+
+## More variables used later in the program
+my $SERVER;
+my $CRLF        = "\015\012";
+my $subject     = '';
+my $header      = '';
+my $message     = '';
+my $from        = '';
+my @to          = ();
+my @cc          = ();
+my @bcc         = ();
+my @attachments = ();
+my @attachments_names = ();
+
+## For printing colors to the console
+my ${colorRed}    = "\033[31;1m";
+my ${colorGreen}  = "\033[32;1m";
+my ${colorCyan}   = "\033[36;1m";
+my ${colorWhite}  = "\033[37;1m";
+my ${colorNormal} = "\033[m";
+my ${colorBold}   = "\033[1m";
+my ${colorNoBold} = "\033[0m";
+
+## Don't use shell escape codes on Windows systems
+if ($^O =~ /win/i) {
+    ${colorRed} = ${colorGreen} = ${colorCyan} = ${colorWhite} = ${colorNormal} = ${colorBold} = ${colorNoBold} = "";
+}
+
+## Load IO::Socket::SSL if it's available
+eval    { require IO::Socket::SSL; };
+if ($@) { $conf{'tls_client'} = 0; }
+else    { $conf{'tls_client'} = 1; }
+
+
+
+
+
+
+#############################
+##                          ##
+##      FUNCTIONS            ##
+##                          ##
+#############################
+
+
+
+
+
+###############################################################################################
+##  Function: initialize ()
+##  
+##  Does all the script startup jibberish.
+##  
+###############################################################################################
+sub initialize {
+
+    ## Set STDOUT to flush immediatly after each print  
+    $| = 1;
+    
+    ## Intercept signals
+    $SIG{'QUIT'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'INT'}   = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'KILL'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'TERM'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+  
+    ## ALARM and HUP signals are not supported in Win32
+    unless ($^O =~ /win/i) {
+        $SIG{'HUP'}   = sub { quit("EXITING: Received SIG$_[0]", 1); };
+        $SIG{'ALRM'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    }
+    
+    ## Fixup $conf{'programName'}
+    $conf{'programName'} =~ s/(.)*[\/,\\]//;
+    $0 = $conf{'programName'} . " " . join(" ", @ARGV);
+    
+    ## Fixup $conf{'hostname'} and $opt{'fqdn'}
+    if ($opt{'fqdn'} eq 'changeme') { $opt{'fqdn'} = get_hostname(1); }
+    if ($conf{'hostname'} eq 'changeme') { $conf{'hostname'} = $opt{'fqdn'}; $conf{'hostname'} =~ s/\..*//; }
+    
+    return(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function: processCommandLine ()
+##  
+##  Processes command line storing important data in global vars (usually %conf)
+##  
+###############################################################################################
+sub processCommandLine {
+    
+    
+    ############################
+    ##  Process command line  ##
+    ############################
+    
+    my @ARGS = @ARGV;  ## This is so later we can re-parse the command line args later if we need to
+    my $numargv = @ARGS;
+    help() unless ($numargv);
+    my $counter = 0;
+    
+    for ($counter = 0; $counter < $numargv; $counter++) {
+  
+        if ($ARGS[$counter] =~ /^-h$/i) {                    ## Help ##
+            help();
+        }
+        
+        elsif ($ARGS[$counter] eq "") {                      ## Ignore null arguments
+            ## Do nothing
+        }
+        
+        elsif ($ARGS[$counter] =~ /^--help/) {               ## Topical Help ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                helpTopic($ARGS[$counter]);
+            }
+            else {
+                help();
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-o$/i) {                 ## Options specified with -o ##
+            $counter++;
+            ## Loop through each option passed after the -o
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                
+                if ($ARGS[$counter] !~ /(\S+)=(\S.*)/) {
+                    printmsg("WARNING => Name/Value pair [$ARGS[$counter]] is not properly formatted", 0);
+                    printmsg("WARNING => Arguments proceeding -o should be in the form of \"name=value\"", 0);
+                }
+                else {
+                    if (exists($opt{$1})) {
+                        if ($1 eq 'message-header') {
+                            $opt{$1} .= $2 . $CRLF;
+                        }
+                        else {
+                            $opt{$1} = $2;
+                        }
+                        printmsg("DEBUG => Assigned \$opt{} key/value: $1 => $2", 3);
+                    }
+                    else {
+                        printmsg("WARNING => Name/Value pair [$ARGS[$counter]] will be ignored: unknown key [$1]", 0);
+                        printmsg("HINT => Try the --help option to find valid command line arguments", 1);
+                    }
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-f$/) {                  ## From ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $from = $ARGS[$counter]; }
+            else { printmsg("WARNING => The argument after -f was not an email address!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-t$/) {                  ## To ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@to, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@to,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-cc$/) {                 ## Cc ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@cc, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@cc,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-bcc$/) {                ## Bcc ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@bcc, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@bcc,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-m$/) {                  ## Message ##
+            $counter++;
+            $message = "";
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                if ($message) { $message .= " "; }
+                $message .= $ARGS[$counter];
+                $counter++;
+            }   $counter--;
+            
+            ## Replace '\n' with $CRLF.
+            ## This allows newlines with messages sent on the command line
+            $message =~ s/\\n/$CRLF/g;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-u$/) {                  ## Subject ##
+            $counter++;
+            $subject = "";
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                if ($subject) { $subject .= " "; }
+                $subject .= $ARGS[$counter];
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-s$/) {                  ## Server ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                $conf{'server'} = $ARGS[$counter];
+                if ($conf{'server'} =~ /:/) {                ## Port ##
+                    ($conf{'server'},$conf{'port'}) = split(":",$conf{'server'});
+                }
+            }
+            else { printmsg("WARNING - The argument after -s was not the server!", 0); $counter--; }
+        }
+
+        elsif ($ARGS[$counter] =~ /^-b$/) {                  ## Bind Address ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                $conf{'bindaddr'} = $ARGS[$counter];
+            }
+            else { printmsg("WARNING - The argument after -b was not the bindaddr!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-a$/) {                  ## Attachments ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                push (@attachments,$ARGS[$counter]);
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-xu$/) {                  ## AuthSMTP Username ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+               $opt{'username'} = $ARGS[$counter];
+            }
+            else {
+                printmsg("WARNING => The argument after -xu was not valid username!", 0);
+                $counter--;
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-xp$/) {                  ## AuthSMTP Password ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+               $opt{'password'} = $ARGS[$counter];
+            }
+            else {
+                printmsg("WARNING => The argument after -xp was not valid password!", 0);
+                $counter--;
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-l$/) {                  ## Logging ##
+            $counter++;
+            $conf{'logging'} = 1;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $conf{'logFile'} = $ARGS[$counter]; }
+            else { printmsg("WARNING - The argument after -l was not the log file!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ s/^-v+//i) {               ## Verbosity ##
+            my $tmp = (length($&) - 1);
+            $conf{'debug'} += $tmp;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-q$/) {                  ## Quiet ##
+            $conf{'stdout'} = 0;
+        }
+        
+        else {
+            printmsg("Error: \"$ARGS[$counter]\" is not a recognized option!", 0);
+            help();
+        }
+        
+    }
+
+
+
+
+
+
+    
+    
+    ###################################################
+    ##  Verify required variables are set correctly  ##
+    ###################################################
+    
+    ## Make sure we have something in $conf{hostname} and $opt{fqdn}
+    if ($opt{'fqdn'} =~ /\./) {
+        $conf{'hostname'} = $opt{'fqdn'};
+        $conf{'hostname'} =~ s/\..*//;
+    }
+    
+    if (!$conf{'server'}) { $conf{'server'} = 'localhost'; }
+    if (!$conf{'port'})   { $conf{'port'} = 25; }
+    if (!$from) {
+        quit("ERROR => You must specify a 'from' field!  Try --help.", 1);
+    }
+    if ( ((scalar(@to)) + (scalar(@cc)) + (scalar(@bcc))) <= 0) {
+        quit("ERROR => You must specify at least one recipient via -t, -cc, or -bcc", 1);
+    }
+    
+    ## Make sure email addresses look OK.
+    foreach my $addr (@to, @cc, @bcc, $from, $opt{'reply-to'}) {
+        if ($addr) {
+            if (!returnAddressParts($addr)) {
+                printmsg("ERROR => Can't use improperly formatted email address: $addr", 0);
+                printmsg("HINT => Try viewing the extended help on addressing with \"--help addressing\"", 1);
+                quit("", 1);
+            }
+        }
+    }
+    
+    ## Make sure all attachments exist.
+    foreach my $file (@attachments) {
+        if ( (! -f $file) or (! -r $file) ) {
+            printmsg("ERROR => The attachment [$file] doesn't exist!", 0);
+            printmsg("HINT => Try specifying the full path to the file or reading extended help with \"--help message\"", 1);
+            quit("", 1);
+        }
+    }
+    
+    if ($conf{'logging'} and (!$conf{'logFile'})) {
+        quit("ERROR => You used -l to enable logging but didn't specify a log file!", 1);
+    }    
+    
+    if ( $opt{'username'} ) {
+        if (!$opt{'password'}) {
+            ## Prompt for a password since one wasn't specified with the -xp option.
+            $SIG{'ALRM'} = sub { quit("ERROR => Timeout waiting for password inpupt", 1); };
+            alarm(60) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+            print "Password: ";
+            $opt{'password'} = <STDIN>; chomp $opt{'password'};
+            if (!$opt{'password'}) {
+                quit("ERROR => A username for SMTP authentication was specified, but no password!", 1);
+            }
+        }
+    }
+    
+    ## Validate the TLS setting
+    $opt{'tls'} = lc($opt{'tls'});
+    if ($opt{'tls'} !~ /^(auto|yes|no)$/) {
+        quit("ERROR => Invalid TLS setting ($opt{'tls'}). Must be one of auto, yes, or no.", 1);
+    }
+    
+    ## If TLS is set to "yes", make sure sendEmail loaded the libraries needed.
+    if ($opt{'tls'} eq 'yes' and $conf{'tls_client'} == 0) {
+        quit("ERROR => No TLS support!  SendEmail can't load required libraries. (try installing Net::SSLeay and IO::Socket::SSL)", 1);
+    }
+    
+    ## Return 0 errors
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## getline($socketRef)
+sub getline {
+    my ($socketRef) = @_;
+    local ($/) = "\r\n";
+    return $$socketRef->getline;
+}
+
+
+
+
+## Receive a (multiline?) SMTP response from ($socketRef)
+sub getResponse {
+    my ($socketRef) = @_;
+    my ($tmp, $reply);
+    local ($/) = "\r\n";
+    return undef unless defined($tmp = getline($socketRef));
+    return("getResponse() socket is not open") unless ($$socketRef->opened);
+    ## Keep reading lines if it's a multi-line response
+    while ($tmp =~ /^\d{3}-/o) {
+        $reply .= $tmp;
+        return undef unless defined($tmp = getline($socketRef));
+    }
+    $reply .= $tmp;
+    $reply =~ s/\r?\n$//o;
+    return $reply;
+}
+
+
+
+
+###############################################################################################
+##  Function:    SMTPchat ( [string $command] )
+##
+##  Description: Sends $command to the SMTP server (on SERVER) and awaits a successful
+##               reply form the server.  If the server returns an error, or does not reply
+##               within $conf{'alarm'} seconds an error is generated.
+##               NOTE: $command is optional, if no command is specified then nothing will
+##               be sent to the server, but a valid response is still required from the server.
+##
+##  Input:       [$command]          A (optional) valid SMTP command (ex. "HELO")
+##  
+##  
+##  Output:      Returns zero on success, or non-zero on error.  
+##               Error messages will be stored in $conf{'error'}
+##               A copy of the last SMTP response is stored in the global variable
+##               $conf{'SMTPchat_response'}
+##               
+##  
+##  Example:     SMTPchat ("HELO mail.isp.net");
+###############################################################################################
+sub SMTPchat {
+    my ($command) = @_;
+    
+    printmsg("INFO => Sending: \t$command", 1) if ($command);
+    
+    ## Send our command
+    print $SERVER "$command$CRLF" if ($command);
+    
+    ## Read a response from the server
+    $SIG{'ALRM'} = sub { $conf{'error'} = "alarm"; $SERVER->close(); };
+    alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+    my $result = $conf{'SMTPchat_response'} = getResponse(\$SERVER); 
+    alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+    
+    ## Generate an alert if we timed out
+    if ($conf{'error'} eq "alarm") {
+        $conf{'error'} = "ERROR => Timeout while reading from $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds.";
+        return(1);
+    }
+    
+    ## Make sure the server actually responded
+    if (!$result) {
+        $conf{'error'} = "ERROR => $conf{'server'}:$conf{'port'} returned a zero byte response to our query.";
+        return(2);
+    }
+    
+    ## Validate the response
+    if (evalSMTPresponse($result)) {
+        ## conf{'error'} will already be set here
+        return(2);
+    }
+    
+    ## Print the success messsage
+    printmsg($conf{'error'}, 1);
+    
+    ## Return Success
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    evalSMTPresponse (string $message )
+##
+##  Description: Searches $message for either an  SMTP success or error code, and returns
+##               0 on success, and the actual error code on error.
+##               
+##
+##  Input:       $message          Data received from a SMTP server (ex. "220 
+##                                
+##  
+##  Output:      Returns zero on success, or non-zero on error.  
+##               Error messages will be stored in $conf{'error'}
+##               
+##  
+##  Example:     SMTPchat ("HELO mail.isp.net");
+###############################################################################################
+sub evalSMTPresponse {
+    my ($message) = @_;
+    
+    ## Validate input
+    if (!$message) { 
+        $conf{'error'} = "ERROR => No message was passed to evalSMTPresponse().  What happened?";
+        return(1)
+    }
+    
+    printmsg("DEBUG => evalSMTPresponse() - Checking for SMTP success or error status in the message: $message ", 3);
+    
+    ## Look for a SMTP success code
+    if ($message =~ /^([23]\d\d)/) {
+        printmsg("DEBUG => evalSMTPresponse() - Found SMTP success code: $1", 2);
+        $conf{'error'} = "SUCCESS => Received: \t$message";
+        return(0);
+    }
+    
+    ## Look for a SMTP error code
+    if ($message =~ /^([45]\d\d)/) {
+        printmsg("DEBUG => evalSMTPresponse() - Found SMTP error code: $1", 2);
+        $conf{'error'} = "ERROR => Received: \t$message";
+        return($1);
+    }
+    
+    ## If no SMTP codes were found return an error of 1
+    $conf{'error'} = "ERROR => Received a message with no success or error code. The message received was: $message";
+    return(2);
+    
+}
+
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: &return_month(0,1,etc)
+#  returns the name of the month that corrosponds
+#  with the number.  returns 0 on error.
+#########################################################
+sub return_month {
+    my $x = $_[0];
+    if ($x == 0)  { return 'Jan'; }
+    if ($x == 1)  { return 'Feb'; }
+    if ($x == 2)  { return 'Mar'; }
+    if ($x == 3)  { return 'Apr'; }
+    if ($x == 4)  { return 'May'; }
+    if ($x == 5)  { return 'Jun'; }
+    if ($x == 6)  { return 'Jul'; }
+    if ($x == 7)  { return 'Aug'; }
+    if ($x == 8)  { return 'Sep'; }
+    if ($x == 9)  { return 'Oct'; }
+    if ($x == 10) { return 'Nov'; }
+    if ($x == 11) { return 'Dec'; }
+    return (0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: &return_day(0,1,etc)
+#  returns the name of the day that corrosponds
+#  with the number.  returns 0 on error.
+#########################################################
+sub return_day {
+    my $x = $_[0];
+    if ($x == 0)  { return 'Sun'; }
+    if ($x == 1)  { return 'Mon'; }
+    if ($x == 2)  { return 'Tue'; }
+    if ($x == 3)  { return 'Wed'; }
+    if ($x == 4)  { return 'Thu'; }
+    if ($x == 5)  { return 'Fri'; }
+    if ($x == 6)  { return 'Sat'; }
+    return (0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    returnAddressParts(string $address)
+##
+##  Description: Returns a two element array containing the "Name" and "Address" parts of 
+##               an email address.
+##  
+## Example:      "Brandon Zehm <caspian@dotconf.net>"
+##               would return: ("Brandon Zehm", "caspian@dotconf.net");
+## 
+##               "caspian@dotconf.net"
+##               would return: ("caspian@dotconf.net", "caspian@dotconf.net")
+###############################################################################################
+sub returnAddressParts {
+    my $input = $_[0];
+    my $name = "";
+    my $address = "";
+    
+    ## Make sure to fail if it looks totally invalid
+    if ($input !~ /(\S+\@\S+)/) {
+        $conf{'error'} = "ERROR => The address [$input] doesn't look like a valid email address, ignoring it";
+        return(undef());
+    }
+    
+    ## Check 1, should find addresses like: "Brandon Zehm <caspian@dotconf.net>"
+    elsif ($input =~ /^\s*(\S(.*\S)?)\s*<(\S+\@\S+)>/o) {
+        ($name, $address) = ($1, $3);
+    }
+    
+    ## Otherwise if that failed, just get the address: <caspian@dotconf.net>
+    elsif ($input =~ /<(\S+\@\S+)>/o) {
+        $name = $address = $1;
+    }
+    
+    ## Or maybe it was formatted this way: caspian@dotconf.net
+    elsif ($input =~ /(\S+\@\S+)/o) {
+        $name = $address = $1;
+    }
+    
+    ## Something stupid happened, just return an error.
+    unless ($name and $address) {
+        printmsg("ERROR => Couldn't parse the address: $input", 0);
+        printmsg("HINT => If you think this should work, consider reporting this as a bug to $conf{'authorEmail'}", 1);
+        return(undef());
+    }
+    
+    ## Make sure there aren't invalid characters in the address, and return it.
+    my $ctrl        = '\000-\037';
+    my $nonASCII    = '\x80-\xff';
+    if ($address =~ /[<> ,;:"'\[\]\\$ctrl$nonASCII]/) {
+        printmsg("WARNING => The address [$address] seems to contain invalid characters: continuing anyway", 0);
+    }
+    return($name, $address);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    base64_encode(string $data, bool $chunk)
+##
+##  Description: Returns $data as a base64 encoded string.
+##               If $chunk is true, the encoded data is returned in 76 character long lines
+##               with the final \CR\LF removed.
+##
+##  Note: This is only used from the smtp auth section of code.
+##        At some point it would be nice to merge the code that encodes attachments and this.
+###############################################################################################
+sub base64_encode {
+    my $data = $_[0];
+    my $chunk = $_[1];
+    my $tmp = '';
+    my $base64 = '';
+    my $CRLF = "\r\n";
+    
+    ###################################
+    ## Convert binary data to base64 ##
+    ###################################
+    while ($data =~ s/(.{45})//s) {        ## Get 45 bytes from the binary string
+        $tmp = substr(pack('u', $&), 1);   ## Convert the binary to uuencoded text
+        chop($tmp);
+        $tmp =~ tr|` -_|AA-Za-z0-9+/|;     ## Translate from uuencode to base64
+        $base64 .= $tmp;
+    }
+    
+    ##########################
+    ## Encode the leftovers ##
+    ##########################
+    my $padding = "";
+    if ( ($data) and (length($data) > 0) ) {
+        $padding = (3 - length($data) % 3) % 3;    ## Set flag if binary data isn't divisible by 3
+        $tmp = substr(pack('u', $data), 1);        ## Convert the binary to uuencoded text
+        chop($tmp);
+        $tmp =~ tr|` -_|AA-Za-z0-9+/|;             ## Translate from uuencode to base64
+        $base64 .= $tmp;
+    }
+    
+    ############################
+    ## Fix padding at the end ##
+    ############################
+    $data = '';
+    $base64 =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set
+    if ($chunk) {
+        while ($base64 =~ s/(.{1,76})//s) {                     ## Put $CRLF after each 76 characters
+            $data .= "$1$CRLF";
+        }
+    }
+    else {
+        $data = $base64;
+    }
+    
+    ## Remove any trailing CRLF's
+    $data =~ s/(\r|\n)*$//s;
+    return($data);
+}
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: send_attachment("/path/filename")
+# Sends the mime headers and base64 encoded file
+# to the email server.
+#########################################################
+sub send_attachment {
+    my ($filename) = @_;                             ## Get filename passed
+    my (@fields, $y, $filename_name, $encoding,      ## Local variables
+        @attachlines, $content_type);
+    my $bin = 1;
+    
+    @fields = split(/\/|\\/, $filename);             ## Get the actual filename without the path  
+    $filename_name = pop(@fields);       
+    push @attachments_names, $filename_name;         ## FIXME: This is only used later for putting in the log file
+    
+    ##########################
+    ## Autodetect Mime Type ##
+    ##########################
+    
+    @fields = split(/\./, $filename_name);
+    $encoding = $fields[$#fields];
+    
+    if ($encoding =~ /txt|text|log|conf|^c$|cpp|^h$|inc|m3u/i) {   $content_type = 'text/plain';                      }
+    elsif ($encoding =~ /html|htm|shtml|shtm|asp|php|cfm/i) {      $content_type = 'text/html';                       }
+    elsif ($encoding =~ /sh$/i) {                                  $content_type = 'application/x-sh';                }
+    elsif ($encoding =~ /tcl/i) {                                  $content_type = 'application/x-tcl';               }
+    elsif ($encoding =~ /pl$/i) {                                  $content_type = 'application/x-perl';              }
+    elsif ($encoding =~ /js$/i) {                                  $content_type = 'application/x-javascript';        }
+    elsif ($encoding =~ /man/i) {                                  $content_type = 'application/x-troff-man';         }
+    elsif ($encoding =~ /gif/i) {                                  $content_type = 'image/gif';                       }
+    elsif ($encoding =~ /jpg|jpeg|jpe|jfif|pjpeg|pjp/i) {          $content_type = 'image/jpeg';                      }
+    elsif ($encoding =~ /tif|tiff/i) {                             $content_type = 'image/tiff';                      }
+    elsif ($encoding =~ /xpm/i) {                                  $content_type = 'image/x-xpixmap';                 }
+    elsif ($encoding =~ /bmp/i) {                                  $content_type = 'image/x-MS-bmp';                  }
+    elsif ($encoding =~ /pcd/i) {                                  $content_type = 'image/x-photo-cd';                }
+    elsif ($encoding =~ /png/i) {                                  $content_type = 'image/png';                       }
+    elsif ($encoding =~ /aif|aiff/i) {                             $content_type = 'audio/x-aiff';                    }
+    elsif ($encoding =~ /wav/i) {                                  $content_type = 'audio/x-wav';                     }
+    elsif ($encoding =~ /mp2|mp3|mpa/i) {                          $content_type = 'audio/x-mpeg';                    }
+    elsif ($encoding =~ /ra$|ram/i) {                              $content_type = 'audio/x-pn-realaudio';            }
+    elsif ($encoding =~ /mpeg|mpg/i) {                             $content_type = 'video/mpeg';                      }
+    elsif ($encoding =~ /mov|qt$/i) {                              $content_type = 'video/quicktime';                 }
+    elsif ($encoding =~ /avi/i) {                                  $content_type = 'video/x-msvideo';                 }
+    elsif ($encoding =~ /zip/i) {                                  $content_type = 'application/x-zip-compressed';    }
+    elsif ($encoding =~ /tar/i) {                                  $content_type = 'application/x-tar';               }
+    elsif ($encoding =~ /jar/i) {                                  $content_type = 'application/java-archive';        }
+    elsif ($encoding =~ /exe|bin/i) {                              $content_type = 'application/octet-stream';        }
+    elsif ($encoding =~ /ppt|pot|ppa|pps|pwz/i) {                  $content_type = 'application/vnd.ms-powerpoint';   }
+    elsif ($encoding =~ /mdb|mda|mde/i) {                          $content_type = 'application/vnd.ms-access';       }
+    elsif ($encoding =~ /xls|xlt|xlm|xld|xla|xlc|xlw|xll/i) {      $content_type = 'application/vnd.ms-excel';        }
+    elsif ($encoding =~ /doc|dot/i) {                              $content_type = 'application/msword';              }
+    elsif ($encoding =~ /rtf/i) {                                  $content_type = 'application/rtf';                 }
+    elsif ($encoding =~ /pdf/i) {                                  $content_type = 'application/pdf';                 }
+    elsif ($encoding =~ /tex/i) {                                  $content_type = 'application/x-tex';               }
+    elsif ($encoding =~ /latex/i) {                                $content_type = 'application/x-latex';             }
+    elsif ($encoding =~ /vcf/i) {                                  $content_type = 'application/x-vcard';             }
+    else { $content_type = 'application/octet-stream';  }
+  
+  
+  ############################
+  ## Process the attachment ##
+  ############################
+    
+    #####################################
+    ## Generate and print MIME headers ##
+    #####################################
+    
+    $y  = "$CRLF--$conf{'delimiter'}$CRLF";
+    $y .= "Content-Type: $content_type;$CRLF";
+    $y .= "        name=\"$filename_name\"$CRLF";
+    $y .= "Content-Transfer-Encoding: base64$CRLF";
+    $y .= "Content-Disposition: attachment; filename=\"$filename_name\"$CRLF";
+    $y .= "$CRLF";
+    print $SERVER $y;
+    
+    
+    ###########################################################
+    ## Convert the file to base64 and print it to the server ##
+    ###########################################################
+    
+    open (FILETOATTACH, $filename) || do {
+        printmsg("ERROR => Opening the file [$filename] for attachment failed with the error: $!", 0);
+        return(1);
+    };
+    binmode(FILETOATTACH);                 ## Hack to make Win32 work
+    
+    my $res = "";
+    my $tmp = "";
+    my $base64 = "";
+    while (<FILETOATTACH>) {               ## Read a line from the (binary) file
+        $res .= $_;
+        
+        ###################################
+        ## Convert binary data to base64 ##
+        ###################################
+        while ($res =~ s/(.{45})//s) {         ## Get 45 bytes from the binary string
+            $tmp = substr(pack('u', $&), 1);   ## Convert the binary to uuencoded text
+            chop($tmp);
+            $tmp =~ tr|` -_|AA-Za-z0-9+/|;     ## Translate from uuencode to base64
+            $base64 .= $tmp;
+        }
+        
+        ################################
+        ## Print chunks to the server ##
+        ################################
+        while ($base64 =~ s/(.{76})//s) {
+            print $SERVER "$1$CRLF";
+        }
+      
+    }
+    
+    ###################################
+    ## Encode and send the leftovers ##
+    ###################################
+    my $padding = "";
+    if ( ($res) and (length($res) >= 1) ) {
+        $padding = (3 - length($res) % 3) % 3;  ## Set flag if binary data isn't divisible by 3
+        $res = substr(pack('u', $res), 1);      ## Convert the binary to uuencoded text
+        chop($res);
+        $res =~ tr|` -_|AA-Za-z0-9+/|;          ## Translate from uuencode to base64
+    }
+    
+    ############################
+    ## Fix padding at the end ##
+    ############################
+    $res = $base64 . $res;                               ## Get left overs from above
+    $res =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set
+    if ($res) {
+        while ($res =~ s/(.{1,76})//s) {                 ## Send it to the email server.
+            print $SERVER "$1$CRLF";
+        }
+    }
+    
+    close (FILETOATTACH) || do {
+        printmsg("ERROR - Closing the filehandle for file [$filename] failed with the error: $!", 0);
+        return(2);
+    };
+    
+    ## Return 0 errors
+    return(0);
+
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    $string = get_hostname (boot $fqdn)
+##  
+##  Description: Tries really hard to returns the short (or FQDN) hostname of the current
+##               system.  Uses techniques and code from the  Sys-Hostname module.
+##  
+##  Input:       $fqdn     A true value (1) will cause this function to return a FQDN hostname
+##                         rather than a short hostname.
+##  
+##  Output:      Returns a string
+###############################################################################################
+sub get_hostname {
+    ## Assign incoming parameters to variables
+    my ( $fqdn ) = @_;
+    my $hostname = "";
+    
+    ## STEP 1: Get short hostname
+    
+    ## Load Sys::Hostname if it's available
+    eval { require Sys::Hostname; };
+    unless ($@) {
+        $hostname = Sys::Hostname::hostname(); 
+    }
+    
+    ## If that didn't get us a hostname, try a few other things
+    else {
+        ## Windows systems
+        if ($^O !~ /win/i) {
+            if ($ENV{'COMPUTERNAME'}) { $hostname = $ENV{'COMPUTERNAME'}; }
+            if (!$hostname) { $hostname = gethostbyname('localhost'); }
+            if (!$hostname) { chomp($hostname = `hostname 2> NUL`) };
+        }
+        
+        ## Unix systems
+        else {
+            local $ENV{PATH} = '/usr/bin:/bin:/usr/sbin:/sbin';  ## Paranoia
+            
+            ## Try the environment first (Help!  What other variables could/should I be checking here?)
+            if ($ENV{'HOSTNAME'}) { $hostname = $ENV{'HOSTNAME'}; }
+            
+            ## Try the hostname command
+            eval { local $SIG{__DIE__}; local $SIG{CHLD}; $hostname = `hostname 2>/dev/null`; chomp($hostname); } ||
+            
+            ## Try POSIX::uname(), which strictly can't be expected to be correct
+            eval { local $SIG{__DIE__}; require POSIX; $hostname = (POSIX::uname())[1]; } ||
+            
+            ## Try the uname command
+            eval { local $SIG{__DIE__}; $hostname = `uname -n 2>/dev/null`; chomp($hostname); };
+            
+        }
+        
+        ## If we can't find anything else, return ""
+        if (!$hostname) {
+            print "WARNING => No hostname could be determined, please specify one with -o fqdn=FQDN option!\n";
+            return("unknown");
+        }
+    }
+    
+    ## Return the short hostname
+    unless ($fqdn) {
+        $hostname =~ s/\..*//;
+        return(lc($hostname));
+    }
+    
+    ## STEP 2: Determine the FQDN
+    
+    ## First, if we already have one return it.
+    if ($hostname =~ /\w\.\w/) { return(lc($hostname)); }
+    
+    ## Next try using 
+    eval { $fqdn = (gethostbyname($hostname))[0]; };
+    if ($fqdn) { return(lc($fqdn)); }
+    return(lc($hostname));
+}
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    printmsg (string $message, int $level)
+##
+##  Description: Handles all messages - printing them to the screen only if the messages
+##               $level is >= the global debug level.  If $conf{'logFile'} is defined it
+##               will also log the message to that file.
+##
+##  Input:       $message          A message to be printed, logged, etc.
+##               $level            The debug level of the message. If
+##                                 not defined 0 will be assumed.  0 is
+##                                 considered a normal message, 1 and 
+##                                 higher is considered a debug message.
+##  
+##  Output:      Prints to STDOUT
+##
+##  Assumptions: $conf{'hostname'} should be the name of the computer we're running on.
+##               $conf{'stdout'} should be set to 1 if you want to print to stdout
+##               $conf{'logFile'} should be a full path to a log file if you want that
+##               $conf{'debug'} should be an integer between 0 and 10.
+##
+##  Example:     printmsg("WARNING: We believe in generic error messages... NOT!", 0);
+###############################################################################################
+sub printmsg {
+    ## Assign incoming parameters to variables
+    my ( $message, $level ) = @_;
+    
+    ## Make sure input is sane
+    $level = 0 if (!defined($level));
+    $message =~ s/\s+$//sgo;
+    $message =~ s/\r?\n/, /sgo;
+    
+    ## Continue only if the debug level of the program is >= message debug level.
+    if ($conf{'debug'} >= $level) {
+        
+        ## Get the date in the format: Dec  3 11:14:04
+        my ($sec, $min, $hour, $mday, $mon) = localtime();
+        $mon = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon];
+        my $date = sprintf("%s %02d %02d:%02d:%02d", $mon, $mday, $hour, $min, $sec);
+        
+        ## Print to STDOUT always if debugging is enabled, or if conf{stdout} is true.
+        if ( ($conf{'debug'} >= 1) or ($conf{'stdout'} == 1) ) {
+            print "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n";
+        }
+        
+        ## Print to the log file if $conf{'logging'} is true
+        if ($conf{'logFile'}) {
+            if (openLogFile($conf{'logFile'})) { $conf{'logFile'} = ""; printmsg("ERROR => Opening the file [$conf{'logFile'}] for appending returned the error: $!", 1); }
+            print LOGFILE "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n";
+        }
+        
+    }
+    
+    ## Return 0 errors
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## FUNCTION:
+##   openLogFile ( $filename )
+## 
+## 
+## DESCRIPTION: 
+##   Opens the file $filename and attaches it to the filehandle "LOGFILE".  Returns 0 on success
+##   and non-zero on failure.  Error codes are listed below, and the error message gets set in
+##   global variable $!.
+##   
+##   
+## Example:
+##   openFile ("/var/log/sendEmail.log");
+##
+###############################################################################################
+sub openLogFile {
+    ## Get the incoming filename
+    my $filename = $_[0];
+    
+    ## Make sure our file exists, and if the file doesn't exist then create it
+    if ( ! -f $filename ) {
+        print STDERR "NOTICE: The log file [$filename] does not exist.  Creating it now with mode [0600].\n" if ($conf{'stdout'});
+        open (LOGFILE, ">>$filename");
+        close LOGFILE;
+        chmod (0600, $filename);
+    }
+    
+    ## Now open the file and attach it to a filehandle
+    open (LOGFILE,">>$filename") or return (1);
+    
+    ## Put the file into non-buffering mode
+    select LOGFILE;
+    $| = 1;
+    select STDOUT;
+    
+    ## Return success
+    return(0);
+}
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    read_file (string $filename)
+##  
+##  Description: Reads the contents of a file and returns a two part array:
+##               ($status, $file-contents)
+##               $status is 0 on success, non-zero on error.
+##               
+##  Example:     ($status, $file) = read_file("/etc/passwd");
+###############################################################################################
+sub read_file {
+    my ( $filename ) = @_;
+    
+    ## If the value specified is a file, load the file's contents
+    if ( (-e $filename and -r $filename) ) {
+        my $FILE;
+        if(!open($FILE, ' ' . $filename)) {
+            return((1, ""));
+        }
+        my $file = '';
+        while (<$FILE>) {
+            $file .= $_;
+        }
+        ## Strip an ending \r\n
+        $file =~ s/\r?\n$//os;
+    }
+    return((1, ""));
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    quit (string $message, int $errorLevel)
+##  
+##  Description: Exits the program, optionally printing $message.  It 
+##               returns an exit error level of $errorLevel to the 
+##               system  (0 means no errors, and is assumed if empty.)
+##
+##  Example:     quit("Exiting program normally", 0);
+###############################################################################################
+sub quit {
+    my ( $message, $errorLevel ) = @_;
+    $errorLevel = 0 if (!defined($errorLevel));
+    
+    ## Print exit message
+    if ($message) { 
+        printmsg($message, 0);
+    }
+    
+    ## Exit
+    exit($errorLevel);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## Function:    help ()
+##
+## Description: For all those newbies ;)
+##              Prints a help message and exits the program.
+##
+###############################################################################################
+sub help {
+exit(1) if (!$conf{'stdout'});
+print <<EOM;
+
+${colorBold}$conf{'programName'}-$conf{'version'} by $conf{'authorName'} <$conf{'authorEmail'}>${colorNoBold}
+
+Synopsis:  $conf{'programName'} -f ADDRESS [options]
+
+  ${colorRed}Required:${colorNormal}
+    -f ADDRESS                from (sender) email address
+    * At least one recipient required via -t, -cc, or -bcc
+    * Message body required via -m, STDIN, or -o message-file=FILE
+
+  ${colorGreen}Common:${colorNormal}
+    -t ADDRESS [ADDR ...]     to email address(es)
+    -u SUBJECT                message subject
+    -m MESSAGE                message body
+    -s SERVER[:PORT]          smtp mail relay, default is $conf{'server'}:$conf{'port'}
+
+  ${colorGreen}Optional:${colorNormal}
+    -a   FILE [FILE ...]      file attachment(s)
+    -cc  ADDRESS [ADDR ...]   cc  email address(es)
+    -bcc ADDRESS [ADDR ...]   bcc email address(es)
+    -xu  USERNAME             username for SMTP authentication
+    -xp  PASSWORD             password for SMTP authentication
+
+  ${colorGreen}Paranormal:${colorNormal}
+    -b BINDADDR[:PORT]        local host bind address
+    -l LOGFILE                log to the specified file
+    -v                        verbosity, use multiple times for greater effect
+    -q                        be quiet (i.e. no STDOUT output)
+    -o NAME=VALUE             advanced options, for details try: --help misc
+        -o message-content-type=<auto|text|html>
+        -o message-file=FILE         -o message-format=raw
+        -o message-header=HEADER     -o message-charset=CHARSET
+        -o reply-to=ADDRESS          -o timeout=SECONDS
+        -o username=USERNAME         -o password=PASSWORD
+        -o tls=<auto|yes|no>         -o fqdn=FQDN
+
+
+  ${colorGreen}Help:${colorNormal}
+    --help                    the helpful overview you're reading now
+    --help addressing         explain addressing and related options
+    --help message            explain message body input and related options
+    --help networking         explain -s, -b, etc
+    --help output             explain logging and other output options
+    --help misc               explain -o options, TLS, SMTP auth, and more
+
+EOM
+exit(1);
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## Function:    helpTopic ($topic)
+##
+## Description: For all those newbies ;) 
+##              Prints a help message and exits the program.
+## 
+###############################################################################################
+sub helpTopic {
+    exit(1) if (!$conf{'stdout'});
+    my ($topic) = @_;
+
+    CASE: {
+
+
+
+
+## ADDRESSING
+        ($topic eq 'addressing') && do {
+            print <<EOM;
+
+${colorBold}ADDRESSING DOCUMENTATION${colorNormal}
+
+${colorGreen}Addressing Options${colorNormal}
+Options related to addressing:
+    -f   ADDRESS
+    -t   ADDRESS [ADDRESS ...]
+    -cc  ADDRESS [ADDRESS ...]
+    -bcc ADDRESS [ADDRESS ...]
+    -o   reply-to=ADDRESS
+    
+-f ADDRESS
+    This required option specifies who the email is from, I.E. the sender's
+    email address.
+    
+-t ADDRESS [ADDRESS ...]
+    This option specifies the primary recipient(s).  At least one recipient
+    address must be specified via the -t, -cc. or -bcc options.
+
+-cc ADDRESS [ADDRESS ...]
+    This option specifies the "carbon copy" recipient(s).  At least one 
+    recipient address must be specified via the -t, -cc. or -bcc options.
+
+-bcc ADDRESS [ADDRESS ...]
+    This option specifies the "blind carbon copy" recipient(s).  At least
+    one recipient address must be specified via the -t, -cc. or -bcc options.
+
+-o reply-to=ADDRESS
+    This option specifies that an optional "Reply-To" address should be
+    written in the email's headers.
+    
+
+${colorGreen}Email Address Syntax${colorNormal}
+Email addresses may be specified in one of two ways:
+    Full Name:     "John Doe <john.doe\@gmail.com>"
+    Just Address:  "john.doe\@gmail.com"
+
+The "Full Name" method is useful if you want a name, rather than a plain
+email address, to be displayed in the recipient's From, To, or Cc fields
+when they view the message.
+    
+
+${colorGreen}Multiple Recipients${colorNormal}
+The -t, -cc, and -bcc options each accept multiple addresses.  They may be
+specified by separating them by either a white space, comma, or semi-colon
+separated list.  You may also specify the -t, -cc, and -bcc options multiple
+times, each occurance will append the new recipients to the respective list.
+
+Examples:
+(I used "-t" in these examples, but it can be "-cc" or "-bcc" as well)
+
+  * Space separated list:
+    -t jane.doe\@yahoo.com "John Doe <john.doe\@gmail.com>"
+    
+  * Semi-colon separated list:
+    -t "jane.doe\@yahoo.com; John Doe <john.doe\@gmail.com>"
+ 
+  * Comma separated list:
+    -t "jane.doe\@yahoo.com, John Doe <john.doe\@gmail.com>"
+  
+  * Multiple -t, -cc, or -bcc options:
+    -t "jane.doe\@yahoo.com" -t "John Doe <john.doe\@gmail.com>"
+  
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## MESSAGE
+        ($topic eq 'message') && do {
+            print <<EOM;
+
+${colorBold}MESSAGE DOCUMENTATION${colorNormal}
+
+${colorGreen}Message Options${colorNormal}
+Options related to the email message body:
+    -u  SUBJECT
+    -m  MESSAGE
+    -o  message-file=FILE
+    -o  message-content-type=<auto|text|html>
+    -o  message-header=EMAIL HEADER
+    -o  message-charset=CHARSET
+    -o  message-format=raw
+    
+-u SUBJECT
+    This option allows you to specify the subject for your email message.
+    It is not required (anymore) that the subject be quoted, although it 
+    is recommended.  The subject will be read until an argument starting
+    with a hyphen (-) is found.  
+    Examples:
+      -u "Contact information while on vacation"
+      -u New Microsoft vulnerability discovered
+
+-m MESSAGE
+    This option is one of three methods that allow you to specify the message
+    body for your email.  The message may be specified on the command line
+    with this -m option, read from a file with the -o message-file=FILE
+    option, or read from STDIN if neither of these options are present.
+    
+    It is not required (anymore) that the message be quoted, although it is
+    recommended.  The message will be read until an argument starting with a
+    hyphen (-) is found.
+    Examples:
+      -m "See you in South Beach, Hawaii.  -Todd"
+      -m Please ensure that you upgrade your systems right away
+    
+    Multi-line message bodies may be specified with the -m option by putting
+    a "\\n" into the message.  Example:
+      -m "This is line 1.\\nAnd this is line 2."
+    
+    HTML messages are supported, simply begin your message with "<html>" and
+    sendEmail will properly label the mime header so MUAs properly render
+    the message.  It is currently not possible without "-o message-format=raw"
+    to send a message with both text and html parts with sendEmail.
+
+-o message-file=FILE
+    This option is one of three methods that allow you to specify the message
+    body for your email.  To use this option simply specify a text file
+    containing the body of your email message. Examples:
+      -o message-file=/root/message.txt
+      -o message-file="C:\\Program Files\\output.txt"
+
+-o message-content-type=<auto|text|html>
+    This option allows you to specify the content-type of the email. If your
+    email message is an html message but is being displayed as a text message
+    just add "-o message-content-type=html" to the command line to force it
+    to display as an html message. This actually just changes the Content-Type:
+    header. Advanced users will be happy to know that if you specify anything
+    other than the three options listed above it will use that as the vaule
+    for the Content-Type header.
+
+-o message-header=EMAIL HEADER
+    This option allows you to specify additional email headers to be included.
+    To add more than one message header simply use this option on the command
+    line more than once.  If you specify a message header that sendEmail would
+    normally generate the one you specified will be used in it's place.
+    Do not use this unless you know what you are doing!
+    Example:
+      To scare a Microsoft Outlook user you may want to try this:
+      -o message-header="X-Message-Flag: Message contains illegal content"
+    Example:
+      To request a read-receipt try this:
+      -o message-header="Disposition-Notification-To: <user\@domain.com>"
+    Example:
+      To set the message priority try this:
+      -o message-header="X-Priority: 1"
+      Priority reference: 1=highest, 2=high, 3=normal, 4=low, 5=lowest
+
+-o message-charset=CHARSET
+    This option allows you to specify the character-set for the message body.
+    The default is iso-8859-1.
+
+-o message-format=raw
+    This option instructs sendEmail to assume the message (specified with -m,
+    read from STDIN, or read from the file specified in -o message-file=FILE)
+    is already a *complete* email message.  SendEmail will not generate any
+    headers and will transmit the message as-is to the remote SMTP server.
+    Due to the nature of this option the following command line options will
+    be ignored when this one is used:
+      -u SUBJECT
+      -o message-header=EMAIL HEADER
+      -o message-charset=CHARSET
+      -a ATTACHMENT
+      
+
+${colorGreen}The Message Body${colorNormal}
+The email message body may be specified in one of three ways:
+ 1) Via the -m MESSAGE command line option.
+    Example:
+      -m "This is the message body"
+      
+ 2) By putting the message body in a file and using the -o message-file=FILE
+    command line option.
+    Example:
+      -o message-file=/root/message.txt
+      
+ 3) By piping the message body to sendEmail when nither of the above command
+    line options were specified.
+    Example:
+      grep "ERROR" /var/log/messages | sendEmail -t you\@domain.com ...
+
+If the message body begins with "<html>" then the message will be treated as
+an HTML message and the MIME headers will be written so that a HTML capable
+email client will display the message in it's HTML form.
+Any of the above methods may be used with the -o message-format=raw option 
+to deliver an already complete email message.
+
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## MISC
+        ($topic eq 'misc') && do {
+            print <<EOM;
+
+${colorBold}MISC DOCUMENTATION${colorNormal}
+
+${colorGreen}Misc Options${colorNormal}
+Options that don't fit anywhere else:
+    -a   ATTACHMENT [ATTACHMENT ...]
+    -xu  USERNAME
+    -xp  PASSWORD
+    -o   username=USERNAME
+    -o   password=PASSWORD
+    -o   tls=<auto|yes|no>
+    -o   timeout=SECONDS
+    -o   fqdn=FQDN
+
+-a   ATTACHMENT [ATTACHMENT ...]
+    This option allows you to attach any number of files to your email message.
+    To specify more than one attachment, simply separate each filename with a
+    space.  Example: -a file1.txt file2.txt file3.txt
+
+-xu  USERNAME
+    Alias for -o username=USERNAME
+
+-xp  PASSWORD
+    Alias for -o password=PASSWORD
+
+-o   username=USERNAME (synonym for -xu)
+    These options allow specification of a username to be used with SMTP
+    servers that require authentication.  If a username is specified but a
+    password is not, you will be prompted to enter one at runtime.
+
+-o   password=PASSWORD (synonym for -xp)
+    These options allow specification of a password to be used with SMTP
+    servers that require authentication.  If a username is specified but a
+    password is not, you will be prompted to enter one at runtime. 
+
+-o   tls=<auto|yes|no>
+    This option allows you to specify if TLS (SSL for SMTP) should be enabled
+    or disabled.  The default, auto, will use TLS automatically if your perl
+    installation has the IO::Socket::SSL and Net::SSLeay modules available,
+    and if the remote SMTP server supports TLS.  To require TLS for message
+    delivery set this to yes.  To disable TLS support set this to no.  A debug
+    level of one or higher will reveal details about the status of TLS.
+
+-o   timeout=SECONDS
+    This option sets the timeout value in seconds used for all network reads,
+    writes, and a few other things.
+
+-o   fqdn=FQDN
+    This option sets the Fully Qualified Domain Name used during the initial
+    SMTP greeting.  Normally this is automatically detected, but in case you
+    need to manually set it for some reason or get a warning about detection
+    failing, you can use this to override the default.
+
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## NETWORKING
+        ($topic eq 'networking') && do {
+            print <<EOM;
+
+${colorBold}NETWORKING DOCUMENTATION${colorNormal}
+
+${colorGreen}Networking Options${colorNormal}
+Options related to networking:
+    -s   SERVER[:PORT]
+    -b   BINDADDR[:PORT]
+    -o   tls=<auto|yes|no>
+    -o   timeout=SECONDS
+
+-s SERVER[:PORT]
+    This option allows you to specify the SMTP server sendEmail should
+    connect to to deliver your email message to.  If this option is not
+    specified sendEmail will try to connect to localhost:25 to deliver
+    the message.  THIS IS MOST LIKELY NOT WHAT YOU WANT, AND WILL LIKELY
+    FAIL unless you have a email server (commonly known as an MTA) running
+    on your computer!
+    Typically you will need to specify your company or ISP's email server.
+    For example, if you use CableOne you will need to specify:
+       -s mail.cableone.net
+    If you have your own email server running on port 300 you would
+    probably use an option like this:
+       -s myserver.mydomain.com:300
+    If you're a GMail user try:
+       -s smtp.gmail.com:587 -xu me\@gmail.com -xp PASSWD
+
+-b BINDADDR[:PORT]
+    This option allows you to specify the local IP address (and optional
+    tcp port number) for sendEmail to bind to when connecting to the remote
+    SMTP server.  This useful for people who need to send an email from a
+    specific network interface or source address and are running sendEmail on
+    a firewall or other host with several network interfaces.
+
+-o   tls=<auto|yes|no>
+    This option allows you to specify if TLS (SSL for SMTP) should be enabled
+    or disabled.  The default, auto, will use TLS automatically if your perl
+    installation has the IO::Socket::SSL and Net::SSLeay modules available,
+    and if the remote SMTP server supports TLS.  To require TLS for message
+    delivery set this to yes.  To disable TLS support set this to no.  A debug
+    level of one or higher will reveal details about the status of TLS.
+
+-o timeout=SECONDS
+    This option sets the timeout value in seconds used for all network reads,
+    writes, and a few other things.
+
+    
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## OUTPUT
+        ($topic eq 'output') && do {
+            print <<EOM;
+
+${colorBold}OUTPUT DOCUMENTATION${colorNormal}
+
+${colorGreen}Output Options${colorNormal}
+Options related to output:
+    -l LOGFILE
+    -v
+    -q
+
+-l LOGFILE
+    This option allows you to specify a log file to append to.  Every message
+    that is displayed to STDOUT is also written to the log file.  This may be
+    used in conjunction with -q and -v.
+
+-q
+    This option tells sendEmail to disable printing to STDOUT.  In other
+    words nothing will be printed to the console.  This does not affect the
+    behavior of the -l or -v options.
+
+-v
+    This option allows you to increase the debug level of sendEmail.  You may
+    either use this option more than once, or specify more than one v at a
+    time to obtain a debug level higher than one.  Examples:
+        Specifies a debug level of 1:  -v
+        Specifies a debug level of 2:  -vv
+        Specifies a debug level of 2:  -v -v
+    A debug level of one is recommended when doing any sort of debugging.  
+    At that level you will see the entire SMTP transaction (except the
+    body of the email message), and hints will be displayed for most
+    warnings and errors.  The highest debug level is three.
+
+
+EOM
+            last CASE;
+        };
+
+        ## Unknown option selected!
+        quit("ERROR => The help topic specified is not valid!", 1);
+    };
+
+exit(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#############################
+##                          ##
+##      MAIN PROGRAM         ##
+##                          ##
+#############################
+
+
+## Initialize
+initialize();
+
+## Process Command Line
+processCommandLine();
+$conf{'alarm'} = $opt{'timeout'};
+
+## Abort program after $conf{'alarm'} seconds to avoid infinite hangs
+alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+
+
+
+
+###################################################
+##  Read $message from STDIN if -m was not used  ##
+###################################################
+
+if (!($message)) {
+    ## Read message body from a file specified with -o message-file=
+    if ($opt{'message-file'}) {
+        if (! -e $opt{'message-file'}) {
+            printmsg("ERROR => Message body file specified [$opt{'message-file'}] does not exist!", 0);
+            printmsg("HINT => 1) check spelling of your file; 2) fully qualify the path; 3) doubble quote it", 1);
+            quit("", 1);
+        }
+        if (! -r $opt{'message-file'}) {
+            printmsg("ERROR => Message body file specified can not be read due to restricted permissions!", 0);
+            printmsg("HINT => Check permissions on file specified to ensure it can be read", 1);
+            quit("", 1);
+        }
+        if (!open(MFILE, "< " . $opt{'message-file'})) {
+            printmsg("ERROR => Error opening message body file [$opt{'message-file'}]: $!", 0);
+            quit("", 1);
+        }
+        while (<MFILE>) {
+            $message .= $_;
+        }
+        close(MFILE);
+    }
+    
+    ## Read message body from STDIN
+    else {
+        alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+        if ($conf{'stdout'}) {
+            print "Reading message body from STDIN because the '-m' option was not used.\n";
+            print "If you are manually typing in a message:\n";
+            print "  - First line must be received within $conf{'alarm'} seconds.\n" if ($^O !~ /win/i);
+            print "  - End manual input with a CTRL-D on its own line.\n\n" if ($^O !~ /win/i);
+            print "  - End manual input with a CTRL-Z on its own line.\n\n" if ($^O =~ /win/i);
+        }
+        while (<STDIN>) {                 ## Read STDIN into $message
+            $message .= $_;
+            alarm(0) if ($^O !~ /win/i);  ## Disable the alarm since at least one line was received
+        }
+        printmsg("Message input complete.", 0);
+    }
+}
+
+## Replace bare LF's with CRLF's (\012 should always have \015 with it)
+$message =~ s/(\015)?(\012|$)/\015\012/g;
+
+## Replace bare CR's with CRLF's (\015 should always have \012 with it)
+$message =~ s/(\015)(\012|$)?/\015\012/g;
+
+## Check message for bare periods and encode them
+$message =~ s/(^|$CRLF)(\.{1})($CRLF|$)/$1.$2$3/g;
+
+## Get the current date for the email header
+my ($sec,$min,$hour,$mday,$mon,$year,$day) = gmtime();
+$year += 1900; $mon = return_month($mon); $day = return_day($day);
+my $date = sprintf("%s, %s %s %d %.2d:%.2d:%.2d %s",$day, $mday, $mon, $year, $hour, $min, $sec, $conf{'timezone'});
+
+
+
+
+##################################
+##  Connect to the SMTP server  ##
+##################################
+printmsg("DEBUG => Connecting to $conf{'server'}:$conf{'port'}", 1);
+$SIG{'ALRM'} = sub { 
+    printmsg("ERROR => Timeout while connecting to $conf{'server'}:$conf{'port'}  There was no response after $conf{'alarm'} seconds.", 0); 
+    printmsg("HINT => Try specifying a different mail relay with the -s option.", 1);
+    quit("", 1);
+};
+alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+$SERVER = IO::Socket::INET->new( PeerAddr  => $conf{'server'},
+                                 PeerPort  => $conf{'port'},
+                                 LocalAddr => $conf{'bindaddr'},
+                                 Proto     => 'tcp',
+                                 Autoflush => 1,
+                                 timeout   => $conf{'alarm'},
+);
+alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+
+## Make sure we got connected
+if ( (!$SERVER) or (!$SERVER->opened()) ) {
+    printmsg("ERROR => Connection attempt to $conf{'server'}:$conf{'port'} failed: $@", 0);
+    printmsg("HINT => Try specifying a different mail relay with the -s option.", 1);
+    quit("", 1);
+}
+
+## Save our IP address for later
+$conf{'ip'} = $SERVER->sockhost();
+printmsg("DEBUG => My IP address is: $conf{'ip'}", 1);
+
+
+
+
+
+
+
+#########################
+##  Do the SMTP Dance  ##
+#########################
+
+## Read initial greeting to make sure we're talking to a live SMTP server
+if (SMTPchat()) { quit($conf{'error'}, 1); }
+
+## We're about to use $opt{'fqdn'}, make sure it isn't empty
+if (!$opt{'fqdn'}) {
+    ## Ok, that means we couldn't get a hostname, how about using the IP address for the HELO instead
+    $opt{'fqdn'} = "[" . $conf{'ip'} . "]";
+}
+
+## EHLO
+if (SMTPchat('EHLO ' . $opt{'fqdn'}))   {
+    printmsg($conf{'error'}, 0);
+    printmsg("NOTICE => EHLO command failed, attempting HELO instead");
+    if (SMTPchat('HELO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); }
+    if ( $opt{'username'} and $opt{'password'} ) {
+        printmsg("WARNING => The mail server does not support SMTP authentication!", 0);
+    }
+}
+else {
+    
+    ## Determin if the server supports TLS
+    if ($conf{'SMTPchat_response'} =~ /STARTTLS/) {
+        $conf{'tls_server'} = 1;
+        printmsg("DEBUG => The remote SMTP server supports TLS :)", 2);
+    }
+    else {
+        $conf{'tls_server'} = 0;
+        printmsg("DEBUG => The remote SMTP server does NOT support TLS :(", 2);
+    }
+    
+    ## Start TLS if possible
+    if ($conf{'tls_server'} == 1 and $conf{'tls_client'} == 1 and $opt{'tls'} =~ /^(yes|auto)$/) {
+        printmsg("DEBUG => Starting TLS", 2);
+        if (SMTPchat('STARTTLS')) { quit($conf{'error'}, 1); }
+        if (! IO::Socket::SSL->start_SSL($SERVER, SSL_version => 'SSLv3 TLSv1')) {
+            quit("ERROR => TLS setup failed: " . IO::Socket::SSL::errstr(), 1);
+        }
+        printmsg("DEBUG => TLS: Using cipher: ". $SERVER->get_cipher(), 3);
+        printmsg("DEBUG => TLS session initialized :)", 1);
+        
+        ## Restart our SMTP session
+        if (SMTPchat('EHLO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); }
+    }
+    elsif ($opt{'tls'} eq 'yes' and $conf{'tls_server'} == 0) {
+        quit("ERROR => TLS not possible! Remote SMTP server, $conf{'server'},  does not support it.", 1);
+    }
+    
+    
+    ## Do SMTP Auth if required
+    if ( $opt{'username'} and $opt{'password'} ) {
+        if ($conf{'SMTPchat_response'} !~ /AUTH\s/) {
+            printmsg("NOTICE => Authentication not supported by the remote SMTP server!", 0);
+        }
+        else {
+            my $auth_succeeded = 0;
+            my $mutual_method = 0;
+            
+            # ## SASL CRAM-MD5 authentication method
+            # if ($conf{'SMTPchat_response'} =~ /\bCRAM-MD5\b/i) {
+            #     printmsg("DEBUG => SMTP-AUTH: Using CRAM-MD5 authentication method", 1);
+            #     if (SMTPchat('AUTH CRAM-MD5')) { quit($conf{'error'}, 1); }
+            #     
+            #     ## FIXME!!
+            #     
+            #     printmsg("DEBUG => User authentication was successful", 1);
+            # }
+            
+            ## SASL LOGIN authentication method
+            if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bLOGIN\b/i) {
+                $mutual_method = 1;
+                printmsg("DEBUG => SMTP-AUTH: Using LOGIN authentication method", 1);
+                if (!SMTPchat('AUTH LOGIN')) {
+                    if (!SMTPchat(base64_encode($opt{'username'}))) {
+                        if (!SMTPchat(base64_encode($opt{'password'}))) {
+                            $auth_succeeded = 1;
+                            printmsg("DEBUG => User authentication was successful (Method: LOGIN)", 1);
+                        }
+                    }
+                }
+                if ($auth_succeeded == 0) {
+                    printmsg("DEBUG => SMTP-AUTH: LOGIN authenticaion failed.", 1);
+                }
+            }
+            
+            ## SASL PLAIN authentication method
+            if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bPLAIN\b/i) {
+                $mutual_method = 1;
+                printmsg("DEBUG => SMTP-AUTH: Using PLAIN authentication method", 1);
+                if (SMTPchat('AUTH PLAIN ' . base64_encode("$opt{'username'}\0$opt{'username'}\0$opt{'password'}"))) {
+                    printmsg("DEBUG => SMTP-AUTH: PLAIN authenticaion failed.", 1);
+                }
+                else {
+                    $auth_succeeded = 1;
+                    printmsg("DEBUG => User authentication was successful (Method: PLAIN)", 1);
+                }
+            }
+            
+            ## If none of the authentication methods supported by sendEmail were supported by the server, let the user know
+            if ($mutual_method == 0) {
+                printmsg("WARNING => SMTP-AUTH: No mutually supported authentication methods available", 0);
+            }
+            
+            ## If we didn't get authenticated, log an error message and exit
+            if ($auth_succeeded == 0) {
+                quit("ERROR => ERROR => SMTP-AUTH: Authentication to $conf{'server'}:$conf{'port'} failed.", 1);
+            }
+        }
+    }
+}
+
+## MAIL FROM
+if (SMTPchat('MAIL FROM:<' .(returnAddressParts($from))[1]. '>')) { quit($conf{'error'}, 1); }
+
+## RCPT TO
+my $oneRcptAccepted = 0;
+foreach my $rcpt (@to, @cc, @bcc) {
+    my ($name, $address) = returnAddressParts($rcpt);
+    if (SMTPchat('RCPT TO:<' . $address . '>')) {
+        printmsg("WARNING => The recipient <$address> was rejected by the mail server, error follows:", 0);
+        $conf{'error'} =~ s/^ERROR/WARNING/o;
+        printmsg($conf{'error'}, 0);
+    }
+    elsif ($oneRcptAccepted == 0) {
+        $oneRcptAccepted = 1;
+    }
+}
+## If no recipients were accepted we need to exit with an error.
+if ($oneRcptAccepted == 0) {
+    quit("ERROR => Exiting. No recipients were accepted for delivery by the mail server.", 1);
+}
+
+## DATA
+if (SMTPchat('DATA')) { quit($conf{'error'}, 1); }
+
+
+###############################
+##  Build and send the body  ##
+###############################
+printmsg("INFO => Sending message body",1);
+
+## If the message-format is raw just send the message as-is.
+if ($opt{'message-format'} =~ /^raw$/i) {
+    print $SERVER $message;
+}
+
+## If the message-format isn't raw, then build and send the message,
+else {
+    
+    ## Message-ID: <MessageID>
+    if ($opt{'message-header'} !~ /^Message-ID:/iom) {
+        $header .= 'Message-ID: <' . $conf{'Message-ID'} . '@' . $conf{'hostname'} . '>' . $CRLF;
+    }
+    
+    ## From: "Name" <address@domain.com> (the pointless test below is just to keep scoping correct)
+    if ($from and $opt{'message-header'} !~ /^From:/iom) {
+        my ($name, $address) = returnAddressParts($from);
+        $header .= 'From: "' . $name . '" <' . $address . '>' . $CRLF;
+    }
+    
+    ## Reply-To: 
+    if ($opt{'reply-to'} and $opt{'message-header'} !~ /^Reply-To:/iom) {
+        my ($name, $address) = returnAddressParts($opt{'reply-to'});
+        $header .= 'Reply-To: "' . $name . '" <' . $address . '>' . $CRLF;
+    }
+    
+    ## To: "Name" <address@domain.com>
+    if ($opt{'message-header'} =~ /^To:/iom) {
+        ## The user put the To: header in via -o message-header - dont do anything
+    }
+    elsif (scalar(@to) > 0) {
+        $header .= "To:";
+        for (my $a = 0; $a < scalar(@to); $a++) {
+            my $msg = "";
+            
+            my ($name, $address) = returnAddressParts($to[$a]);
+            $msg = " \"$name\" <$address>";
+            
+            ## If we're not on the last address add a comma to the end of the line.
+            if (($a + 1) != scalar(@to)) {
+                $msg .= ",";
+            }
+            
+            $header .= $msg . $CRLF;
+        }
+    }
+    ## We always want a To: line so if the only recipients were bcc'd they don't see who it was sent to
+    else {
+        $header .= "To: \"Undisclosed Recipients\" <>$CRLF";
+    }
+    
+    if (scalar(@cc) > 0 and $opt{'message-header'} !~ /^Cc:/iom) {
+        $header .= "Cc:";
+        for (my $a = 0; $a < scalar(@cc); $a++) {
+            my $msg = "";
+            
+            my ($name, $address) = returnAddressParts($cc[$a]);
+            $msg = " \"$name\" <$address>";
+            
+            ## If we're not on the last address add a comma to the end of the line.
+            if (($a + 1) != scalar(@cc)) {
+                $msg .= ",";
+            }
+            
+            $header .= $msg . $CRLF;
+        }
+    }
+    
+    if ($opt{'message-header'} !~ /^Subject:/iom) {
+        $header .= 'Subject: ' . $subject . $CRLF;                   ## Subject
+    }
+    if ($opt{'message-header'} !~ /^Date:/iom) {
+        $header .= 'Date: ' . $date . $CRLF;                         ## Date
+    }
+    if ($opt{'message-header'} !~ /^X-Mailer:/iom) {
+        $header .= 'X-Mailer: sendEmail-'.$conf{'version'}.$CRLF;    ## X-Mailer
+    }
+    ## I wonder if I should put this in by default?
+    # if ($opt{'message-header'} !~ /^X-Originating-IP:/iom) {
+    #     $header .= 'X-Originating-IP: ['.$conf{'ip'}.']'.$CRLF;      ## X-Originating-IP
+    # }
+    
+    ## Encode all messages with MIME.
+    if ($opt{'message-header'} !~ /^MIME-Version:/iom) {
+        $header .=  "MIME-Version: 1.0$CRLF";
+    }
+    if ($opt{'message-header'} !~ /^Content-Type:/iom) {
+        my $content_type = 'multipart/mixed';
+        if (scalar(@attachments) == 0) { $content_type = 'multipart/related'; }
+        $header .= "Content-Type: $content_type; boundary=\"$conf{'delimiter'}\"$CRLF";
+    }
+    
+    ## Send additional message header line(s) if specified
+    if ($opt{'message-header'}) {
+        $header .= $opt{'message-header'};
+    }
+    
+    ## Send the message header to the server
+    print $SERVER $header . $CRLF;
+    
+    ## Start sending the message body to the server
+    print $SERVER "This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.$CRLF";
+    print $SERVER "$CRLF";
+    
+    
+    ## Send message body
+    print $SERVER "--$conf{'delimiter'}$CRLF";
+    ## Send a message content-type header:
+    ## If the message contains HTML...
+    if ($opt{'message-content-type'} eq 'html' or ($opt{'message-content-type'} eq 'auto' and $message =~ /^\s*(<HTML|<!DOCTYPE)/i) ) {
+        printmsg("Setting content-type: text/html", 1);
+        print $SERVER "Content-Type: text/html;$CRLF";
+    }
+    ## Otherwise assume it's plain text...
+    elsif ($opt{'message-content-type'} eq 'text' or $opt{'message-content-type'} eq 'auto') {
+        printmsg("Setting content-type: text/plain", 1);
+        print $SERVER "Content-Type: text/plain;$CRLF";
+    }
+    ## If they've specified their own content-type string...
+    else {
+        printmsg("Setting custom content-type: ".$opt{'message-content-type'}, 1);
+        print $SERVER "Content-Type: ".$opt{'message-content-type'}.";$CRLF";
+    }
+    print $SERVER "        charset=\"" . $opt{'message-charset'} . "\"$CRLF";
+    print $SERVER "Content-Transfer-Encoding: 7bit$CRLF";
+    print $SERVER $CRLF . $message;
+    
+    
+    
+    ## Send Attachemnts
+    if (scalar(@attachments) > 0) {
+        ## Disable the alarm so people on modems can send big attachments
+        alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+        
+        ## Send the attachments
+        foreach my $filename (@attachments) {
+            ## This is check 2, we already checked this above, but just in case...
+            if ( ! -f $filename ) {
+                printmsg("ERROR => The file [$filename] doesn't exist!  Email will be sent, but without that attachment.", 0);
+            }
+            elsif ( ! -r $filename ) {
+                printmsg("ERROR => Couldn't open the file [$filename] for reading: $!   Email will be sent, but without that attachment.", 0);
+            }
+            else {
+                printmsg("DEBUG => Sending the attachment [$filename]", 1);
+                send_attachment($filename);
+            }
+        }
+    }
+    
+    
+    ## End the mime encoded message
+    print $SERVER "$CRLF--$conf{'delimiter'}--$CRLF";  
+}
+
+
+## Tell the server we are done sending the email
+print $SERVER "$CRLF.$CRLF";
+if (SMTPchat()) { quit($conf{'error'}, 1); }
+
+
+
+####################
+#  We are done!!!  #
+####################
+
+## Disconnect from the server (don't SMTPchat(), it breaks when using TLS)
+print $SERVER "QUIT$CRLF";
+close $SERVER;
+
+
+
+
+
+
+#######################################
+##  Generate exit message/log entry  ##
+#######################################
+
+if ($conf{'debug'} or $conf{'logging'}) {
+    printmsg("Generating a detailed exit message", 3);
+    
+    ## Put the message together
+    my $output = "Email was sent successfully!  From: <" . (returnAddressParts($from))[1] . "> ";
+    
+    if (scalar(@to) > 0) {
+        $output .= "To: ";
+        for ($a = 0; $a < scalar(@to); $a++) {
+            $output .= "<" . (returnAddressParts($to[$a]))[1] . "> ";
+        }
+    }
+    if (scalar(@cc) > 0) {
+        $output .= "Cc: ";
+        for ($a = 0; $a < scalar(@cc); $a++) {
+            $output .= "<" . (returnAddressParts($cc[$a]))[1] . "> ";
+        }
+    }
+    if (scalar(@bcc) > 0) {
+        $output .= "Bcc: ";
+        for ($a = 0; $a < scalar(@bcc); $a++) {
+            $output .= "<" . (returnAddressParts($bcc[$a]))[1] . "> ";
+        }
+    }
+    $output .= "Subject: [$subject] " if ($subject);
+    if (scalar(@attachments_names) > 0) { 
+        $output .= "Attachment(s): ";
+        foreach(@attachments_names) {
+            $output .= "[$_] ";
+        }
+    }
+    $output .= "Server: [$conf{'server'}:$conf{'port'}]";
+    
+    
+######################
+#  Exit the program  #
+######################
+    
+    ## Print / Log the detailed message
+    quit($output, 0);
+}
+else {
+    ## Or the standard message
+    quit("Email was sent successfully!", 0);
+}
+
diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php
index d6824f45871bce57692142efe313491b11ca6781..44075b1906ec9b7f738e48ddcfff1fdf60739ee7 100644
--- a/extension/qfq/qfq/Constants.php
+++ b/extension/qfq/qfq/Constants.php
@@ -112,7 +112,7 @@ const ERROR_SIP_INVALID = 1006;
 const ERROR_MISSING_RECORD_ID = 1007;
 const ERROR_IN_SQL_STATEMENT = 1008;
 const ERROR_MISSING_REQUIRED_PARAMETER = 1009;
-const ERROR_MISSING_SESSIONNAME = 1010;
+
 const ERROR_BROKEN_PARAMETER = 1011;
 const ERROR_FE_USER_UID_CHANGED = 1012;
 const ERROR_SIP_NOT_FOUND = 1013;
@@ -122,8 +122,9 @@ const ERROR_SIP_EXIST_BUT_OTHER_PARAM_GIVEN_BY_CLIENT = 1016;
 const ERROR_USER_NOT_LOGGED_IN = 1017;
 const ERROR_USER_LOGGED_IN = 1018;
 const ERROR_FORM_FORBIDDEN = 1019;
-const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 10120;
+const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 1020;
 const ERROR_MULTI_SQL_MISSING = 1021;
+
 const ERROR_RECURSION_TOO_DEEP = 1023;
 const ERROR_CHECKBOXMODE_UNKNOWN = 1024;
 const ERROR_MISSING_SQL1 = 1025;
@@ -131,12 +132,16 @@ const ERROR_CHECKBOX_EQUAL = 1026;
 const ERROR_MISSING_ITEM_LIST = 1027;
 const ERROR_UNKNOWN_FORM_RENDER = 1028;
 const ERROR_NAME_LABEL_EMPTY = 1029;
+
 const ERROR_DEBUG = 1031;
 const ERROR_UNKNOWN_MODE = 1032;
 const ERROR_NOT_IMPLEMENTED = 1033;
 const ERROR_RESERVED_KEY_NAME = 1034;
+
 const ERROR_UNKNOWN_FORWARD_MODE = 1036;
+
 const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1038;
+
 const ERROR_MISSING_MIN_MAX = 1040;
 const ERROR_MIN_MAX_VIOLATION = 1041;
 const ERROR_UNKNOWN_CHECKTYPE = 1042;
@@ -146,7 +151,6 @@ const ERROR_LOG_NOT_WRITABLE = 1045;
 const ERROR_UNNOWN_STORE = 1046;
 const ERROR_GET_STORE_ZERO = 1047;
 const ERROR_SET_STORE_ZERO = 1048;
-const ERROR_QFQ_SESSION_MISSING = 1049;
 const ERROR_INVALID_OR_MISSING_PARAMETER = 1050;
 const ERROR_UNKNOWN_SQL_LOG_MODE = 1051;
 const ERROR_FORM_NOT_FOUND = 1052;
@@ -155,7 +159,7 @@ const ERROR_SANATIZE_INVALID_VALUE = 1054;
 const ERROR_REQUIRED_VALUE_EMPTY = 1055;
 const ERROR_DATE_UNEXPECTED_FORMAT = 1056;
 const ERROR_UNEXPECTED_TYPE = 1057;
-const ERROR_NOT_APPLICABLE = 108;
+const ERROR_NOT_APPLICABLE = 1058;
 const ERROR_FORMELEMENT_TYPE = 1059;
 const ERROR_MISSING_OPEN_DELIMITER = 1060;
 const ERROR_MISSING_CLOSE_DELIMITER = 1061;
@@ -269,6 +273,10 @@ const ERROR_DIRTY_RECORD_MODIFIED = 2205;
 // Language
 const ERROR_LANGUAGE_NOT_CONFIGURED_IN_QFQ = 2300;
 
+// Session
+const ERROR_MISSING_SESSIONNAME = 2400;
+const ERROR_QFQ_SESSION_MISSING = 2401;
+const ERROR_SESSION_BROKEN_SCRIPT_PATH = 2402;
 
 //
 // Store Names: Identifier
@@ -370,6 +378,8 @@ const SYSTEM_SQL_LOG_FILE = '../../sql.log';
 const SYSTEM_SQL_LOG_MODE = 'SQL_LOG_MODE'; // Mode, which statements to log.
 const SYSTEM_DATE_FORMAT = 'DATE_FORMAT';
 const SYSTEM_REDIRECT_ALL_MAIL_TO = 'REDIRECT_ALL_MAIL_TO';
+const SYSTEM_MAIL_LOG = 'MAIL_LOG';
+const SYSTEM_MAIL_LOG_FILE = '../../mail.log';
 
 const SYSTEM_SHOW_DEBUG_INFO = 'SHOW_DEBUG_INFO';
 const SYSTEM_SHOW_DEBUG_INFO_YES = 'yes';
@@ -401,6 +411,9 @@ const SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS = 'FORM_BUTTON_ON_CHANGE_CLASS';
 const SYSTEM_BASE_URL_PRINT = 'BASE_URL_PRINT';
 const SYSTEM_WKHTMLTOPDF = 'WKHTMLTOPDF';
 
+const SYSTEM_SEND_E_MAIL = 'sendEmail';
+const SYSTEM_SEND_E_MAIL_OPTIONS = 'SEND_E_MAIL_OPTIONS';
+
 const SYSTEM_EDIT_FORM_PAGE = 'EDIT_FORM_PAGE';
 
 // computed automatically during runtime
@@ -455,8 +468,12 @@ const SYSTEM_DB_UPDATE_AUTO = 'auto';
 const SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS = 'RECORD_LOCK_TIMEOUT_SECONDS';
 const SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS_DEFAULT = 900; // 15 mins
 
+// Deprecated, replaced by SYSTEM_FILL_STORE_SYSTEM_BY_SQ
 const SYSTEM_VAR_ADD_BY_SQL = 'VAR_ADD_BY_SQL';
-const SYSTEM_VAR_ADD_BY_SQL_DEFAULT = 'SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1';
+//const SYSTEM_VAR_ADD_BY_SQL_DEFAULT = 'SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1';
+
+const SYSTEM_FILL_STORE_SYSTEM_BY_SQL = 'FILL_STORE_SYSTEM_BY_SQL';
+const SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG = 'FILL_STORE_SYSTEM_ERROR_MSG';
 
 const SYSTEM_FORM_LANGUAGE = 'FORM_LANGUAGE';
 
@@ -571,26 +588,6 @@ const TOKEN_FOUND_AS_DEFAULT = 'default';
 
 const RANDOM_LENGTH = 32;
 
-// Report, BodyText
-const TOKEN_SQL = 'sql';
-const TOKEN_HEAD = 'head';
-const TOKEN_ALT_HEAD = 'althead';
-const TOKEN_TAIL = 'tail';
-const TOKEN_RBEG = 'rbeg';
-const TOKEN_REND = 'rend';
-const TOKEN_RENR = 'renr';
-const TOKEN_RSEP = 'rsep';
-const TOKEN_FBEG = 'fbeg';
-const TOKEN_FEND = 'fend';
-const TOKEN_FSEP = 'fsep';
-const TOKEN_RBGD = 'rbgd';
-const TOKEN_DEBUG = 'debug';
-const TOKEN_FORM = CLIENT_FORM;
-const TOKEN_RECORD_ID = CLIENT_RECORD_ID;
-const TOKEN_DEBUG_BODYTEXT = TYPO3_DEBUG_SHOW_BODY_TEXT;
-
-const TOKEN_VALID_LIST = 'sql|head|althead|tail|rbeg|rend|renr|rsep|fbeg|fend|fsep|rbgd|debug|form|r|debugShowBodyText|sqlLog|sqlLogMode';
-
 // FORM - copy from table 'form' of processed form
 //const DEF_FORM_NAME = CLIENT_FORM;
 
@@ -885,6 +882,8 @@ const FE_SENDMAIL_REPLY_TO = 'sendMailReplyTo'; // Reply to email address
 const FE_SENDMAIL_FLAG_AUTO_SUBMIT = 'sendMailFlagAutoSubmit'; // on|off - if 'on', suppresses OoO answers from receivers.
 const FE_SENDMAIL_GR_ID = 'sendMailGrId'; // gr_id: used to classify mail log entries ind table mailLog
 const FE_SENDMAIL_X_ID = 'sendMailXId'; // x_id: used to classify mail log entries ind table mailLog
+const FE_SENDMAIL_X_ID2 = 'sendMailXId2'; // x_id: used to classify mail log entries ind table mailLog
+const FE_SENDMAIL_X_ID3 = 'sendMailXId3'; // x_id: used to classify mail log entries ind table mailLog
 const FE_AUTOFOCUS = 'autofocus'; // value: <none>|0|1  , <none>==1, this element becomes the focus during form load.
 const FE_RETYPE = 'retype'; // value: <none>|0|1  , <none>==1, this element becomes the focus during form load.
 const FE_RETYPE_LABEL = 'retypeLabel'; // value: label text for retype FormElement
@@ -1076,7 +1075,52 @@ const SENDMAIL_IDX_GR_ID = 6;
 const SENDMAIL_IDX_X_ID = 7;
 const SENDMAIL_IDX_RECEIVER_CC = 8;
 const SENDMAIL_IDX_RECEIVER_BCC = 9;
-const SENDMAIL_IDX_SRC = 10;
+const SENDMAIL_IDX_ATTACHMENT = 10;
+const SENDMAIL_IDX_HEADER = 11;
+const SENDMAIL_IDX_X_ID2 = 12;
+const SENDMAIL_IDX_X_ID3 = 13;
+const SENDMAIL_IDX_SRC = 14;
+
+const SENDMAIL_TOKEN_RECEIVER = 't';
+const SENDMAIL_TOKEN_SENDER = 'f';
+const SENDMAIL_TOKEN_SUBJECT = 's';
+const SENDMAIL_TOKEN_BODY = 'b';
+const SENDMAIL_TOKEN_REPLY_TO = 'r';
+const SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT = 'A';
+const SENDMAIL_TOKEN_GR_ID = 'g';
+const SENDMAIL_TOKEN_X_ID = 'x';
+const SENDMAIL_TOKEN_RECEIVER_CC = 'c';
+const SENDMAIL_TOKEN_RECEIVER_BCC = 'B';
+const SENDMAIL_TOKEN_ATTACHMENT = 'a';
+const SENDMAIL_TOKEN_HEADER = 'h';
+const SENDMAIL_TOKEN_X_ID2 = 'y';
+const SENDMAIL_TOKEN_X_ID3 = 'z';
+const SENDMAIL_TOKEN_SRC = 'S';
+
+// Report, BodyText
+const TOKEN_SQL = 'sql';
+const TOKEN_HEAD = 'head';
+const TOKEN_ALT_HEAD = 'althead';
+const TOKEN_ALT_SQL = 'altsql';
+const TOKEN_TAIL = 'tail';
+const TOKEN_SHEAD = 'shead';
+const TOKEN_STAIL = 'stail';
+const TOKEN_RBEG = 'rbeg';
+const TOKEN_REND = 'rend';
+const TOKEN_RENR = 'renr';
+const TOKEN_RSEP = 'rsep';
+const TOKEN_FBEG = 'fbeg';
+const TOKEN_FEND = 'fend';
+const TOKEN_FSEP = 'fsep';
+const TOKEN_RBGD = 'rbgd';
+const TOKEN_DEBUG = 'debug';
+const TOKEN_FORM = CLIENT_FORM;
+const TOKEN_RECORD_ID = CLIENT_RECORD_ID;
+const TOKEN_DEBUG_BODYTEXT = TYPO3_DEBUG_SHOW_BODY_TEXT;
+
+const TOKEN_VALID_LIST = 'sql|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|rbgd|debug|form|r|debugShowBodyText|sqlLog|sqlLogMode';
+
+const TOKEN_COLUMN_CTRL = '_';
 
 //Report: Column Token
 const COLUMN_PPAGE = "Page";
diff --git a/extension/qfq/qfq/Evaluate.php b/extension/qfq/qfq/Evaluate.php
index 44e11c3f76b54c4ec258bdec66b61f6304098384..f4c6114314ef245a43e9d42ca5d09e8a97cac1f9 100644
--- a/extension/qfq/qfq/Evaluate.php
+++ b/extension/qfq/qfq/Evaluate.php
@@ -196,15 +196,14 @@ class Evaluate {
 
         $token = trim($token);
 
-        // just to extract the first token: check if this is a SQL Statement
-        $arr = explode(' ', $token, 2);
-
         if ($token[0] === '!') {
-            $token = substr($token, 1);
-            $arr[0] = substr($arr[0], 1);
+            $token = trim(substr($token, 1));
             $sqlMode = ROW_REGULAR;
         }
 
+        // just to extract the first token: check if this is a SQL Statement
+        $arr = explode(' ', $token, 2);
+
         // SQL Statement?
         if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
             $foundInStore = TOKEN_FOUND_IN_STORE_QUERY;
@@ -215,10 +214,8 @@ class Evaluate {
         // explode for: <key>:<store priority>:<sanitize class>:<escape>:<default>
         $arr = explode(':', $token, 5);
         $arr = array_merge($arr, [null, null, null, null, null]); // fake isset()
-        $escapeTypes = $arr[3];
-        if ($escapeTypes == '') {
-            $escapeTypes = $this->escapeTypeDefault;
-        }
+
+        $escapeTypes = (empty($arr[3])) ? $this->escapeTypeDefault : $arr[3];
 
         // search for value in stores
         $value = $this->store->getVar($arr[0], $arr[1], $arr[2], $foundInStore);
diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php
index d5a52f9eb5955d71e11bc37e9a9aa43ecf7910aa..e73ce75d888eb7f8ea1958a22087347571c4187b 100644
--- a/extension/qfq/qfq/QuickFormQuery.php
+++ b/extension/qfq/qfq/QuickFormQuery.php
@@ -166,7 +166,7 @@ class QuickFormQuery {
         $updateDb = new DatabaseUpdate($this->dbArray[$this->dbIndexQfq]);
         $updateDb->checkNupdate($dbUpdate);
 
-        $this->store->systemStoreUpdate(); // Do this after the DB-update
+        $this->store->StoreSystemUpdate(); // Do this after the DB-update
     }
 
     /**
diff --git a/extension/qfq/qfq/database/Database.php b/extension/qfq/qfq/database/Database.php
index 9ceedcc6e4d63cf92f282ce2870f58c5700c2b04..69725c19d578830a8ec65ef0b518c207bb60193f 100644
--- a/extension/qfq/qfq/database/Database.php
+++ b/extension/qfq/qfq/database/Database.php
@@ -211,7 +211,7 @@ class Database {
                     } elseif ($count === 0) {
                         $result = array();
                     } else
-                        throw new DbException($specificMessage . "Expected no record, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
+                        throw new DbException($specificMessage . "Expected zero or one record, got $count records: $sql", ERROR_DB_TOO_MANY_ROWS);
                     break;
                 case ROW_EXPECT_GE_1:
                     if ($count > 0) {
diff --git a/extension/qfq/qfq/database/DatabaseUpdateData.php b/extension/qfq/qfq/database/DatabaseUpdateData.php
index 247a4e61f6b1d3d32848fccd9cd2325712e7eeb6..e00226bd11123617145abb16460c0aa075439ef2 100644
--- a/extension/qfq/qfq/database/DatabaseUpdateData.php
+++ b/extension/qfq/qfq/database/DatabaseUpdateData.php
@@ -87,8 +87,11 @@ $UPDATE_ARRAY = array(
     ],
 
     '0.25.3' => [
-        "CREATE TABLE `Split` (`id`           INT(11)      NOT NULL  AUTO_INCREMENT,  `tableName`    VARCHAR(255) NOT NULL,  `xId`          INT(11)      NOT NULL,  `pathFileName` VARCHAR(255) NOT NULL,   `modified`     TIMESTAMP    NOT NULL  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  `created`      DATETIME     NOT NULL  DEFAULT '0000-00-00 00:00:00',   PRIMARY KEY (`id`))  ENGINE = InnoDB  AUTO_INCREMENT = 0  DEFAULT CHARSET = utf8;"
+        "ALTER TABLE `MailLog` ADD `xId2` INT NOT NULL AFTER `xId`, ADD `xId3` INT NOT NULL AFTER `xId2`",
     ],
+
+
 );
 
 
+
diff --git a/extension/qfq/qfq/form/FormAction.php b/extension/qfq/qfq/form/FormAction.php
index 3fd502547e7f245ac75344fe56332cec5ba0a1ec..e08d7b6258c55f8a669570696a7ad7f5cb6d74f1 100644
--- a/extension/qfq/qfq/form/FormAction.php
+++ b/extension/qfq/qfq/form/FormAction.php
@@ -252,6 +252,8 @@ class FormAction {
         $mail[SENDMAIL_IDX_RECEIVER_CC] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_CC]);
         $mail[SENDMAIL_IDX_RECEIVER_BCC] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_BCC]);
         $mail[SENDMAIL_IDX_SRC] = "FormId: " . $feSpecAction[FE_FORM_ID] . ", FormElementId: " . $feSpecAction['id'];
+        $mail[SENDMAIL_IDX_X_ID2] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_X_ID2]);
+        $mail[SENDMAIL_IDX_X_ID3] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_X_ID3]);
 
         // Mail: send
         new Sendmail($mail);
diff --git a/extension/qfq/qfq/helper/OnArray.php b/extension/qfq/qfq/helper/OnArray.php
index e8a1b1de8d7b8acd091d13a2504fe87f3c1531b7..07fdf7f98468fde6eadd65a0d0277a5c77a593fc 100644
--- a/extension/qfq/qfq/helper/OnArray.php
+++ b/extension/qfq/qfq/helper/OnArray.php
@@ -196,6 +196,28 @@ class OnArray {
         return $new;
     }
 
+    /**
+     * Remove from all keynames an optional '_'.
+     *
+     * @param array $arr
+     *
+     * @return array
+     */
+    public static function keyNameRemoveLeadingUnderscore(array $arr) {
+
+        foreach ($arr as $key => $value) {
+            if ($key[0] == TOKEN_COLUMN_CTRL) {
+                $newKey = substr($key, 1);
+                if (!empty($newKey)) {
+                    $arr[$newKey] = $value;
+                    unset($arr[$key]);
+                }
+            }
+        }
+
+        return $arr;
+    }
+
     /**
      * Search in array $dest for all $keyNames if they exist. If not, check if they exist in $src. If yes, copy.
      *
diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php
index 460db77fc7beb9962e5fd473c0ff0b2c72940635..2852e41a32f59575694b4f30475b332c39c05832 100644
--- a/extension/qfq/qfq/helper/Support.php
+++ b/extension/qfq/qfq/helper/Support.php
@@ -1021,5 +1021,24 @@ class Support {
         }
     }
 
+    /**
+     * Strips the first char $c from $data if the first char is equal to $c.
+     * Example: with $c='_' the $data='_pId' becomes 'pId'
+     *
+     * @param $c
+     * @param $data
+     * @return string
+     */
+    public static function stripFirstCharIf($c, $data) {
 
+        if (empty($data)) {
+            return $data;
+        }
+
+        if ($data[0] == $c) {
+            $data = substr($data, 1);
+        }
+
+        return $data;
+    }
 }
\ No newline at end of file
diff --git a/extension/qfq/qfq/report/Report.php b/extension/qfq/qfq/report/Report.php
index cc4499838aa36b489507f3dc61671a3cfa179f50..142524660ba54b23551e46108af7f420c51e743c 100644
--- a/extension/qfq/qfq/report/Report.php
+++ b/extension/qfq/qfq/report/Report.php
@@ -357,14 +357,14 @@ class Report {
     }
 
     /**
-     * Executes the queries recursive. This Method is called for each Sublevel.
+     * Executes the queries recursive. This Method is called for each sublevel.
      *
      * ROOTLEVEL
      * This method is called once from the main method.
      * For the first call the method executes the rootlevels
      *
      * SUBLEVEL
-     * For each rootlevel the method calls it self whith the levelmode 0
+     * For each rootlevel the method calls it self with the level mode 0
      * If the next Level is a Sublevel it will be executed and $this->counter will be added by 1
      * The sublevel calls the method again for a following sublevel
      *
@@ -426,6 +426,7 @@ class Report {
             // Prepare SQL: replace variables. Actual 'line.total' or 'line.count' will recalculated: don't replace them now!
             unset($this->variables->resultArray[$full_level . ".line."]["total"]);
             unset($this->variables->resultArray[$full_level . ".line."]["count"]);
+
             $sql = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SQL]);
 
             $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
@@ -440,26 +441,28 @@ class Report {
             $rowTotal = isset($stat[DB_NUM_ROWS]) ? $stat[DB_NUM_ROWS] : $stat[DB_AFFECTED_ROWS];
 
             $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
-            if (isset($stat[DB_INSERT_ID])) {
-                $this->variables->resultArray[$full_level . ".line."]["insertId"] = $stat[DB_INSERT_ID];
-            }
+            $this->variables->resultArray[$full_level . ".line."]["count"] = is_array($result) ? 1 : 0;
+            $this->variables->resultArray[$full_level . ".line."]["insertId"] = isset($stat[DB_INSERT_ID]) ? $stat[DB_INSERT_ID] : 0;
 
+            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SHEAD]);
             // HEAD: If there is at least one record, do 'head'.
             if ($rowTotal > 0) {
                 $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_HEAD]);
             }
 
-            // Prepare row alteration
-            $arrRbgd = explode("|", $this->frArray[$full_level . "." . TOKEN_RBGD]);
-            if (count($arrRbgd) < 2) {
-                $arrRbgd[] = '';
-                $arrRbgd[] = '';
-            }
 
             if (is_array($result)) {
+
+                // Prepare row alteration
+                $arrRbgd = explode("|", $this->frArray[$full_level . "." . TOKEN_RBGD], 2);
+                if (count($arrRbgd) < 2) {
+                    $arrRbgd[] = '';
+                    $arrRbgd[] = '';
+                }
+
                 //---------------------------------
                 // Process each row of resultset
-                $columnValueSeperator = "";
+                $columnValueSeparator = "";
                 $rowIndex = 0;
                 foreach ($result as $row) {
                     // record number counter
@@ -471,12 +474,15 @@ class Report {
                         $row[$ii] = str_replace("{{" . $full_level . ".line.total}}", $rowTotal, $row[$ii]);
                     }
 
-                    // SEP set seperator (empty on first run)
-                    $content .= $columnValueSeperator;
-                    $columnValueSeperator = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RSEP]);
+                    // SEP set separator (empty on first run)
+                    $content .= $columnValueSeparator;
+                    $columnValueSeparator = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RSEP]);
+
+                    // RBEG
+                    $rbeg = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RBEG]);
 
                     // RBGD: even/odd rows
-                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $this->frArray[$full_level . "." . TOKEN_RBEG]);
+                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $rbeg);
 
                     //-----------------------------
                     // COLUMNS: Collect all columns
@@ -494,12 +500,23 @@ class Report {
                 }
             }
 
-            //Print althead or tail
             if ($rowTotal > 0) {
+                // tail
                 $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_TAIL]);
             } else {
+                // althead
                 $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_HEAD]);
+                // altsql
+                $sql = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_SQL]);
+                if (!empty($sql)) {
+                    $result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
+                    foreach ($result as $row) {
+                        $rowIndex = 0;
+                        $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);
+                    }
+                }
             }
+            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_STAIL]);
 
             ++$counter;
             if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
@@ -556,16 +573,24 @@ class Report {
      */
     private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
         $content = "";
+        $assoc = array();
 
         $fsep = '';
         for ($ii = 0; $ii < count($keys); $ii++) {
 
+            // Debugging
             $this->store->setVar(SYSTEM_REPORT_COLUMN_INDEX, $ii + 1, STORE_SYSTEM);
             $this->store->setVar(SYSTEM_REPORT_COLUMN_NAME, $keys[$ii], STORE_SYSTEM);
             $this->store->setVar(SYSTEM_REPORT_COLUMN_VALUE, $row[$ii], STORE_SYSTEM);
 
             $flagOutput = false;
             $renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
+
+            $keyAssoc = Support::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii]);
+            if ($keyAssoc != '') {
+                $assoc[$keyAssoc] = $row[$ii];
+            }
+
             if ($flagOutput) {
                 //prints
                 $content .= $this->variables->doVariables($fsep);
@@ -576,6 +601,8 @@ class Report {
             }
         }
 
+        $this->store->appendToStore(STORE_RECORD, $assoc);
+
         return ($content);
     }
 
@@ -598,7 +625,7 @@ class Report {
         $dummy = false;
 
         // Empty columnnames are allowed: check with isset
-        if (isset($columnName[0]) && $columnName[0] === "_") {
+        if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
             $flagControl = true;
             $columnName = substr($columnName, 1);
         }
@@ -696,36 +723,36 @@ class Report {
                     break;
                 }
 
-                $mailarr = explode("|", $columnValue, 3);
+                $mailConfig = explode("|", $columnValue, 3);
 
                 // Fake values for tmp[1], tmp[2] to suppress access errors.
-                $mailarr[] = '';
-                $mailarr[] = '';
+                $mailConfig[] = '';
+                $mailConfig[] = '';
 
-                if (empty($mailarr[0])) {
+                if (empty($mailConfig[0])) {
                     break;
                 }
-                $attribute = Support::doAttribute('src', $mailarr[0]);
-                $attribute .= Support::doAttribute('alt', $mailarr[1]);
+                $attribute = Support::doAttribute('src', $mailConfig[0]);
+                $attribute .= Support::doAttribute('alt', $mailConfig[1]);
 
-                $content .= '<img ' . $attribute . '>' . $mailarr[2];
+                $content .= '<img ' . $attribute . '>' . $mailConfig[2];
                 break;
 
             case "mailto":
                 // "<email address>|[Real Name]"  renders to (encrypted via JS): <a href="mailto://<email address>"><email address></a> OR <a href="mailto://<email address>">[Real Name]</a>
-                $mailarr = explode("|", $columnValue, 2);
-                if (empty($mailarr[0])) {
+                $mailConfig = explode("|", $columnValue, 2);
+                if (empty($mailConfig[0])) {
                     break;
                 }
 
-                $t1 = explode("@", $mailarr[0], 2);
+                $t1 = explode("@", $mailConfig[0], 2);
                 $content .= "<script language=javascript><!--" . chr(10);
-                if (empty($mailarr[1])) {
-                    $mailarr[1] = $mailarr[0];
+                if (empty($mailConfig[1])) {
+                    $mailConfig[1] = $mailConfig[0];
                 }
 
-                $content .= 'var contact = "' . substr($mailarr[1], 0, 2) . '"' . chr(10);
-                $content .= 'var contact1 = "' . substr($mailarr[1], 2) . '"' . chr(10);
+                $content .= 'var contact = "' . substr($mailConfig[1], 0, 2) . '"' . chr(10);
+                $content .= 'var contact1 = "' . substr($mailConfig[1], 2) . '"' . chr(10);
                 $content .= 'var email = "' . $t1[0] . '"' . chr(10);
                 $content .= 'var emailHost = "' . $t1[1] . '"' . chr(10);
 
@@ -736,44 +763,57 @@ class Report {
 
             case "sendmail":
                 // '<receiver1>,<receiver2>,...|<sender>|<subject>|<body>|<reply-to>|<flag autosubmit: on /off>'
-                $mailarr = explode("|", $columnValue);
-                if (count($mailarr) < 4) {
+//                $mailarr = explode("|", $columnValue);
+                $mailConfig = $this->sendmailConvertToken($columnValue);
+                if (count($mailConfig) < 4) {
                     throw new SyntaxReportException ("Too few parameter for sendmail: $columnValue", ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL, null, __FILE__, __LINE__, $this->fr_error);
                 }
 
-                if (!isset($mailarr[SENDMAIL_IDX_REPLY_TO])) {
-                    $mailarr[SENDMAIL_IDX_REPLY_TO] = '';
-                }
-
-                if (!isset($mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT])) {
-                    $mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] = 'on';
-                }
-
-                if (!isset($mailarr[SENDMAIL_IDX_GR_ID])) {
-                    $mailarr[SENDMAIL_IDX_GR_ID] = '0';
-                }
-
-                if (!isset($mailarr[SENDMAIL_IDX_X_ID])) {
-                    $mailarr[SENDMAIL_IDX_X_ID] = '0';
-                }
-
-                if (!isset($mailarr[SENDMAIL_IDX_RECEIVER_CC])) {
-                    $mailarr[SENDMAIL_IDX_RECEIVER_CC] = '';
-                }
-
-                if (!isset($mailarr[SENDMAIL_IDX_RECEIVER_BCC])) {
-                    $mailarr[SENDMAIL_IDX_RECEIVER_BCC] = '';
-                }
-
-                $mailarr[SENDMAIL_IDX_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
+//                if (!isset($mailarr[SENDMAIL_IDX_REPLY_TO])) {
+//                    $mailarr[SENDMAIL_IDX_REPLY_TO] = '';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT])) {
+//                    $mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] = 'on';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_GR_ID])) {
+//                    $mailarr[SENDMAIL_IDX_GR_ID] = '0';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_X_ID])) {
+//                    $mailarr[SENDMAIL_IDX_X_ID] = '0';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_RECEIVER_CC])) {
+//                    $mailarr[SENDMAIL_IDX_RECEIVER_CC] = '';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_RECEIVER_BCC])) {
+//                    $mailarr[SENDMAIL_IDX_RECEIVER_BCC] = '';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_X_ID])) {
+//                    $mailarr[SENDMAIL_IDX_X_ID] = '0';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_X_ID2])) {
+//                    $mailarr[SENDMAIL_IDX_X_ID] = '0';
+//                }
+//
+//                if (!isset($mailarr[SENDMAIL_IDX_X_ID3])) {
+//                    $mailarr[SENDMAIL_IDX_X_ID] = '0';
+//                }
+
+                $mailConfig[SENDMAIL_IDX_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
                     ", T3 ttcontentId=" . $this->store->getVar('ttcontentUid', STORE_TYPO3) .
                     ", Level=" . $full_level;
 
-                new Sendmail($mailarr);
+                new Sendmail($mailConfig);
                 break;
 
             case "vertical":
-                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and heigth needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
+                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and height needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
                 $arr = explode("|", $columnValue, 5);
 
                 # angle
@@ -781,11 +821,11 @@ class Report {
 
                 # width
                 $width = $arr[2] ? $arr[2] : "1em";
-                $mailarr = "width:$width; ";
+                $extra = "width:$width; ";
 
                 # height
                 if ($arr[3]) {
-                    $mailarr .= "height:" . $arr[3] . "; ";
+                    $extra .= "height:" . $arr[3] . "; ";
                 }
 
                 # tag
@@ -841,7 +881,7 @@ class Report {
 
         exec($cmd, $arr, $rc);
 
-        $output = implode('<BR>', $arr);
+        $output = implode('<br>', $arr);
         if ($rc != 0) {
             $output = $rc . " - " . $output;
         }
@@ -1220,4 +1260,49 @@ class Report {
 
         return false;
     }
+
+    private function sendmailConvertToken($data) {
+
+        $sendmailToken = [SENDMAIL_TOKEN_RECEIVER => SENDMAIL_IDX_RECEIVER,
+            SENDMAIL_TOKEN_SENDER => SENDMAIL_IDX_SENDER,
+            SENDMAIL_TOKEN_SUBJECT => SENDMAIL_IDX_SUBJECT,
+            SENDMAIL_TOKEN_BODY => SENDMAIL_IDX_BODY,
+            SENDMAIL_TOKEN_REPLY_TO => SENDMAIL_IDX_REPLY_TO,
+            SENDMAIL_TOKEN_RECEIVER_CC => SENDMAIL_IDX_RECEIVER_CC,
+            SENDMAIL_TOKEN_RECEIVER_BCC => SENDMAIL_IDX_RECEIVER_BCC,
+            SENDMAIL_TOKEN_HEADER => SENDMAIL_IDX_HEADER,
+            SENDMAIL_TOKEN_ATTACHMENT => SENDMAIL_IDX_ATTACHMENT,
+            SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT => SENDMAIL_IDX_FLAG_AUTO_SUBMIT,
+            SENDMAIL_TOKEN_GR_ID => SENDMAIL_IDX_GR_ID,
+            SENDMAIL_TOKEN_X_ID => SENDMAIL_IDX_X_ID,
+            SENDMAIL_TOKEN_SRC => SENDMAIL_IDX_SRC,
+            SENDMAIL_TOKEN_X_ID2 => SENDMAIL_IDX_X_ID2,
+            SENDMAIL_TOKEN_X_ID3 => SENDMAIL_IDX_X_ID3
+        ];
+
+
+        if (!isset($data[1]) || $data[1] != ':') {
+            return explode('|', $data);
+        }
+
+        $segments = explode('|', $data);
+        $arr = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
+        foreach ($segments AS $line) {
+            $piece = explode(':', $line, 2);
+
+            if (empty($piece[0])) {
+                throw new UserReportException ("Missing token in sendmail", ERROR_UNKNOWN_TOKEN);
+            }
+
+            if (!isset($sendmailToken[$piece[0]])) {
+                throw new UserReportException ("Unknown token in sendmail: $piece[0]", ERROR_UNKNOWN_TOKEN);
+            }
+
+            $idx = $sendmailToken[$piece[0]];
+            $arr[$idx] = empty($piece[1]) ? '' : $piece[1];
+        }
+
+        return ($arr);
+    }
+
 }
diff --git a/extension/qfq/qfq/report/Sendmail.php b/extension/qfq/qfq/report/Sendmail.php
index 6d434ea69cc77af954fa54361adb2b596a1856b6..384e026d8e48a1697c7dc17ea3420750465355b0 100644
--- a/extension/qfq/qfq/report/Sendmail.php
+++ b/extension/qfq/qfq/report/Sendmail.php
@@ -11,6 +11,11 @@ require_once(__DIR__ . '/../store/Store.php');
 
 class Sendmail {
 
+    /**
+     * @var Store
+     */
+    private $store = null;
+
     /**
      * Sends a mail as specified in $mailarr.
      * If there is no receiver specified as 'TO': no mail is sent. This is ok and no error.
@@ -28,71 +33,169 @@ class Sendmail {
      *    SENDMAIL_IDX_GR_ID                optional: integer
      *    SENDMAIL_IDX_X_ID                 optional: integer
      *
-     * @param $mailarr
+     * @param $mailConfig
      *
      * @throws UserFormException
      */
-    public function __construct(array $mailarr) {
-
-        $addBody = '';
+    public function __construct(array $mailConfig) {
 
         // If there is no 'Receiver': do not send a mail.
-        if (!isset($mailarr[SENDMAIL_IDX_RECEIVER]) || $mailarr[SENDMAIL_IDX_RECEIVER] === '') {
+        if (!isset($mailConfig[SENDMAIL_IDX_RECEIVER]) || $mailConfig[SENDMAIL_IDX_RECEIVER] === '') {
             return;
         }
 
-        if (count($mailarr) < 4 || $mailarr[SENDMAIL_IDX_SENDER] === '' || $mailarr[SENDMAIL_IDX_SUBJECT] === '' || $mailarr[SENDMAIL_IDX_BODY] === '') {
+        if (count($mailConfig) < 4 || $mailConfig[SENDMAIL_IDX_SENDER] === '' || $mailConfig[SENDMAIL_IDX_SUBJECT] === '' || $mailConfig[SENDMAIL_IDX_BODY] === '') {
             throw new UserFormException("Error sendmail missing one of: receiver, sender, subject or body", ERROR_SENDMAIL_MISSING_VALUE);
         }
 
-        $store = Store::getInstance('');
-        $redirectAllMail = $store->getVar(SYSTEM_REDIRECT_ALL_MAIL_TO, STORE_SYSTEM);
+        $this->store = Store::getInstance('');
+
+        $redirectAllMail = $this->store->getVar(SYSTEM_REDIRECT_ALL_MAIL_TO, STORE_SYSTEM);
+
         if ($redirectAllMail !== false) {
-            $addBody .= "All QFQ outgoing mails are catched and redirected to you." . PHP_EOL . "Original receiver:" . PHP_EOL;
-            $addBody .= 'TO: ' . $mailarr[SENDMAIL_IDX_RECEIVER] . PHP_EOL;
-            $addBody .= 'CC: ' . $mailarr[SENDMAIL_IDX_RECEIVER_CC] . PHP_EOL;
-            $addBody .= 'BCC: ' . $mailarr[SENDMAIL_IDX_RECEIVER_BCC] . PHP_EOL;
+            $addBody = "All QFQ outgoing mails are catched and redirected to you." . PHP_EOL . "Original receiver:" . PHP_EOL;
+            $addBody .= 'TO: ' . $mailConfig[SENDMAIL_IDX_RECEIVER] . PHP_EOL;
+            $addBody .= 'CC: ' . $mailConfig[SENDMAIL_IDX_RECEIVER_CC] . PHP_EOL;
+            $addBody .= 'BCC: ' . $mailConfig[SENDMAIL_IDX_RECEIVER_BCC] . PHP_EOL;
             $addBody .= PHP_EOL . "==========================================" . PHP_EOL . PHP_EOL;
 
-            $mailarr[SENDMAIL_IDX_RECEIVER] = $redirectAllMail;
-            $mailarr[SENDMAIL_IDX_RECEIVER_CC] = '';
-            $mailarr[SENDMAIL_IDX_RECEIVER_BCC] = '';
+            $mailConfig[SENDMAIL_IDX_BODY] = $addBody . $mailConfig[SENDMAIL_IDX_BODY];
+
+            $mailConfig[SENDMAIL_IDX_RECEIVER] = $redirectAllMail;
+            $mailConfig[SENDMAIL_IDX_RECEIVER_CC] = '';
+            $mailConfig[SENDMAIL_IDX_RECEIVER_BCC] = '';
+        }
+
+        if (empty($mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT]) || $mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] === '') {
+            $mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] = 'on';
         }
 
-        $header = $this->buildHeader($mailarr);
+//        $header = $this->buildHeader($mailarr);
+//        if (!(mb_send_mail($mailarr[SENDMAIL_IDX_RECEIVER], $mailarr[SENDMAIL_IDX_SUBJECT], $addBody . $mailarr[SENDMAIL_IDX_BODY], $header, "-f " . $mailarr[SENDMAIL_IDX_SENDER]))) {
+//            throw new UserFormException("Error sendmail failed.", ERROR_SENDMAIL);
+//        }
 
-        if (!(mb_send_mail($mailarr[SENDMAIL_IDX_RECEIVER], $mailarr[SENDMAIL_IDX_SUBJECT], $addBody . $mailarr[SENDMAIL_IDX_BODY], $header, "-f " . $mailarr[SENDMAIL_IDX_SENDER]))) {
-            throw new UserFormException("Error sendmail failed.", ERROR_SENDMAIL);
+        $this->sendEmail($mailConfig);
+        $this->mailLog($mailConfig);
+    }
+
+    /**
+     * Use the programm 'sendEmail' - http://caspian.dotconf.net/menu/Software/SendEmail
+     *
+     * @param array $mailConfig
+     * @throws CodeException
+     * @throws UserFormException
+     */
+    private function sendEmail(array $mailConfig) {
+        $args = array();
+
+        foreach ($mailConfig as $key => $value) {
+            $mailConfig[$key] = Support::escapeDoubleTick($value);
         }
 
-        $this->mailLog($mailarr, $header);
+        $args[] = '-f "' . $mailConfig[SENDMAIL_IDX_SENDER] . '"';
+        $args[] = '-t "' . $mailConfig[SENDMAIL_IDX_RECEIVER] . '"';
+        $args[] = '-o message-charset="utf-8"';
+        $args[] = '-q ';
+
+        $logFile = $this->store->getVar(SYSTEM_MAIL_LOG, STORE_SYSTEM);
+        if ($logFile != '' && $logFile !== false) {
+            $args[] = '-l "' . $logFile . '"';;
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_RECEIVER_CC])) {
+            $args[] = '-cc "' . $mailConfig[SENDMAIL_IDX_RECEIVER_CC] . '"';;
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_RECEIVER_BCC])) {
+            $args[] = '-bcc "' . $mailConfig[SENDMAIL_IDX_RECEIVER_BCC];
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_SUBJECT])) {
+            // The subject needs to be encoded to UTF-8 separately - https://stackoverflow.com/questions/4389676/email-from-php-has-broken-subject-header-encoding/27648245#27648245
+            $preferences = ["scheme" => "Q", "input-charset" => "UTF-8", "output-charset" => "UTF-8"];
+            $encodedSubject = iconv_mime_encode("Subject", $mailConfig[SENDMAIL_IDX_SUBJECT], $preferences);
+            $encodedSubject = substr($encodedSubject, 9); // remove 'Subject: '
+
+            $args[] = '-u "' . $encodedSubject . '"';;
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_BODY])) {
+            $args[] = '-m "' . $mailConfig[SENDMAIL_IDX_BODY] . '"';;
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_REPLY_TO])) {
+            $args[] = '-o reply-to="' . $mailConfig[SENDMAIL_IDX_REPLY_TO] . '"';;
+        }
+
+        if ($mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] === 'on') {
+            $args[] = '-o message-header="Auto-Submitted: auto-send"';
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_ATTACHMENT])) {
+            $pieces = explode(',', $mailConfig[SENDMAIL_IDX_ATTACHMENT]);
+            foreach ($pieces as $piece) {
+                $args[] = '-a ' . $piece;
+            }
+        }
+
+        if (!empty($mailConfig[SENDMAIL_IDX_HEADER])) {
+            $args[] = '-o message-header="' . $mailConfig[SENDMAIL_IDX_HEADER] . '"';
+        }
+
+        $sendEmail = $this->store->getVar(SYSTEM_SEND_E_MAIL, STORE_SYSTEM);
+        if (empty($sendEmail)) {
+            throw new UserFormException("Missing 'sendEmail'", ERROR_SENDMAIL);
+        }
+
+        $sendEmailOptions = $this->store->getVar(SYSTEM_SEND_E_MAIL_OPTIONS, STORE_SYSTEM);
+        if (!empty($sendEmailOptions)) {
+            $args[] = $sendEmailOptions;
+        }
+
+        $cmd = $sendEmail . ' ' . implode(' ', $args);
+
+        exec($cmd, $arr, $rc);
+        if ($rc != 0) {
+            // After first installation of QFQ extension, the PERL script is not executable: is this the case?
+            $perms = fileperms($sendEmail);
+            if (!($perms & 0x0040)) {
+                chmod($sendEmail, 0755);
+                exec($cmd, $arr, $rc); // Give it a second try.
+            }
+
+            if ($rc != 0) {
+                $output = $rc . " - " . implode('<br>', $arr);
+                throw new UserFormException("Error sendmail failed: " . $output, ERROR_SENDMAIL);
+            }
+        }
     }
 
     /**
-     * @param array $mailarr
+     * @param array $mailConfig
      *
      * @return string
      */
-    private function buildHeader(array $mailarr) {
+    private function buildHeader(array $mailConfig) {
 
         // "\r\n" needs to be enclosed in double ticks to correctly converted to 0x0d 0x0a,
-        $header = "From: " . $mailarr[SENDMAIL_IDX_SENDER] . "\r\n";
+        $header = "From: " . $mailConfig[SENDMAIL_IDX_SENDER] . "\r\n";
 
-        if (isset($mailarr[SENDMAIL_IDX_REPLY_TO]) && $mailarr[SENDMAIL_IDX_REPLY_TO] != '') {
-            $header .= "Reply-To: " . $mailarr[SENDMAIL_IDX_REPLY_TO] . "\r\n";
+        if (isset($mailConfig[SENDMAIL_IDX_REPLY_TO]) && $mailConfig[SENDMAIL_IDX_REPLY_TO] != '') {
+            $header .= "Reply-To: " . $mailConfig[SENDMAIL_IDX_REPLY_TO] . "\r\n";
         }
 
         // By default 'on'
-        if (!isset($mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT]) || $mailarr[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] === 'on') {
+        if (!isset($mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT]) || $mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] === 'on') {
             $header .= "Auto-Submitted: auto-send\r\n";
         }
 
-        if (isset($mailarr[SENDMAIL_IDX_RECEIVER_CC]) && $mailarr[SENDMAIL_IDX_RECEIVER_CC] != '') {
-            $header .= "Cc: " . $mailarr[SENDMAIL_IDX_RECEIVER_CC] . "\r\n";
+        if (isset($mailConfig[SENDMAIL_IDX_RECEIVER_CC]) && $mailConfig[SENDMAIL_IDX_RECEIVER_CC] != '') {
+            $header .= "Cc: " . $mailConfig[SENDMAIL_IDX_RECEIVER_CC] . "\r\n";
         }
 
-        if (isset($mailarr[SENDMAIL_IDX_RECEIVER_BCC]) && $mailarr[SENDMAIL_IDX_RECEIVER_BCC] != '') {
-            $header .= "Bcc: " . $mailarr[SENDMAIL_IDX_RECEIVER_BCC] . "\r\n";
+        if (isset($mailConfig[SENDMAIL_IDX_RECEIVER_BCC]) && $mailConfig[SENDMAIL_IDX_RECEIVER_BCC] != '') {
+            $header .= "Bcc: " . $mailConfig[SENDMAIL_IDX_RECEIVER_BCC] . "\r\n";
         }
 
         return $header;
@@ -101,28 +204,37 @@ class Sendmail {
     /**
      * Creates a new MailLog Record based on $mailArr / $header.
      *
-     * @param array $mailarr
-     * @param       $header
+     * @param array $mailConfig
      *
      * @throws CodeException
      * @throws DbException
      */
-    private function mailLog(array $mailarr, $header) {
+    private function mailLog(array $mailConfig) {
 
         $log = array();
 
+        $header = 'OoO:' . $mailConfig[SENDMAIL_IDX_FLAG_AUTO_SUBMIT];
+        if (!empty($mailConfig[SENDMAIL_IDX_HEADER])) {
+            $header .= PHP_EOL . 'Custom: ' . $mailConfig[SENDMAIL_IDX_HEADER];
+        }
+        if (!empty($mailConfig[SENDMAIL_IDX_ATTACHMENT])) {
+            $header .= PHP_EOL . 'Attachment: ' . $mailConfig[SENDMAIL_IDX_ATTACHMENT];
+        }
+
         // Log
-        $log[SENDMAIL_IDX_RECEIVER] = $mailarr[SENDMAIL_IDX_RECEIVER];
-        $log[SENDMAIL_IDX_SENDER] = $mailarr[SENDMAIL_IDX_SENDER];
-        $log[SENDMAIL_IDX_SUBJECT] = $mailarr[SENDMAIL_IDX_SUBJECT];
-        $log[SENDMAIL_IDX_BODY] = $mailarr[SENDMAIL_IDX_BODY];
-        $log[4] = $header;
-        $log[5] = empty($mailarr[SENDMAIL_IDX_GR_ID]) ? 0 : $mailarr[SENDMAIL_IDX_GR_ID];
-        $log[6] = empty($mailarr[SENDMAIL_IDX_X_ID]) ? 0 : $mailarr[SENDMAIL_IDX_X_ID];
-        $log[7] = empty($mailarr[SENDMAIL_IDX_SRC]) ? 0 : $mailarr[SENDMAIL_IDX_SRC];
+        $log[] = $mailConfig[SENDMAIL_IDX_RECEIVER];
+        $log[] = $mailConfig[SENDMAIL_IDX_SENDER];
+        $log[] = $mailConfig[SENDMAIL_IDX_SUBJECT];
+        $log[] = $mailConfig[SENDMAIL_IDX_BODY];
+        $log[] = $header;
+        $log[] = empty($mailConfig[SENDMAIL_IDX_GR_ID]) ? 0 : $mailConfig[SENDMAIL_IDX_GR_ID];
+        $log[] = empty($mailConfig[SENDMAIL_IDX_X_ID]) ? 0 : $mailConfig[SENDMAIL_IDX_X_ID];
+        $log[] = empty($mailConfig[SENDMAIL_IDX_X_ID2]) ? 0 : $mailConfig[SENDMAIL_IDX_X_ID2];
+        $log[] = empty($mailConfig[SENDMAIL_IDX_X_ID3]) ? 0 : $mailConfig[SENDMAIL_IDX_X_ID3];
+        $log[] = empty($mailConfig[SENDMAIL_IDX_SRC]) ? 0 : $mailConfig[SENDMAIL_IDX_SRC];
 
         $db = new Database();
-        $db->sql('INSERT INTO MailLog (`receiver`, `sender`, `subject`, `body`, `header`, `grId`, `xId`, `src`, `modified`, `created`) VALUES ( ?, ? ,?, ?, ? ,?, ?, ?, NOW(), NOW() )', ROW_REGULAR, $log);
+        $db->sql('INSERT INTO MailLog (`receiver`, `sender`, `subject`, `body`, `header`, `grId`, `xId`, `xId2`, `xId3`, `src`, `modified`, `created`) VALUES ( ?, ? , ? , ? ,?, ?, ? ,?, ?, ?, NOW(), NOW() )', ROW_REGULAR, $log);
 
     }
 }
diff --git a/extension/qfq/qfq/report/Variables.php b/extension/qfq/qfq/report/Variables.php
index d382759bf36b8d4703b122c2c1f879dccaf58828..355dca4c66da02994b01a66cbcbd2fd89d349e44 100644
--- a/extension/qfq/qfq/report/Variables.php
+++ b/extension/qfq/qfq/report/Variables.php
@@ -59,8 +59,18 @@ class Variables {
      * @return mixed
      */
     public function doVariables($text) {
+
+        if ($text == '') {
+            return '';
+        }
+
 //        $str = preg_replace_callback("/(~([a-zA-Z0-9._])*)/", array($this, 'replaceVariables'), $text);
-        $str = preg_replace_callback("/{{(([a-zA-Z0-9.:_])*)}}/", array($this, 'replaceVariables'), $text);
+//        $str = preg_replace_callback("/{{(([a-zA-Z0-9.:_])*)}}/", array($this, 'replaceVariables'), $text);
+        // Process all {{x[.x].name}}
+        $str = preg_replace_callback('/{{\s*(([0-9]+.)+[a-zA-Z0-9_.]+)\s*}}/', array($this, 'replaceVariables'), $text);
+
+        // Try the Stores
+        $str = $this->eval->parse($str);
 
         return $str;
     }
@@ -93,11 +103,11 @@ class Variables {
         }
 
         // If not replaced, try the Stores
-        if ($data === $matches[0]) {
-            $dataTmp = $this->eval->parse($data);
-            if ($dataTmp !== false)
-                $data = $dataTmp;
-        }
+//        if ($data === $matches[0]) {
+//            $dataTmp = $this->eval->parse($data);
+//            if ($dataTmp !== false)
+//                $data = $dataTmp;
+//        }
 
         return $data;
     }
diff --git a/extension/qfq/qfq/store/Config.php b/extension/qfq/qfq/store/Config.php
index 51c10d244e5543867f4ffb2cadb4e8344ae05efe..42c465197b25652f73590c69059fda7a6b59dd4c 100644
--- a/extension/qfq/qfq/store/Config.php
+++ b/extension/qfq/qfq/store/Config.php
@@ -43,12 +43,31 @@ class Config {
 
         $config = self::renameConfigElements($config);
         $config = self::setDefaults($config);
-
+        self::checkDeprecated($config);
         self::checkForAttack($config);
 
         return $config;
     }
 
+    /**
+     * Checks for deprecated options.
+     */
+    private static function checkDeprecated(array $config) {
+
+        foreach ([SYSTEM_VAR_ADD_BY_SQL] as $key) {
+
+            if (isset($config[$key])) {
+                $msg = '';
+                switch ($key) {
+                    case SYSTEM_VAR_ADD_BY_SQL:
+                        $msg = 'Replaced by: ' . SYSTEM_FILL_STORE_SYSTEM_BY_SQL . '_1|2|3';
+                }
+                throw new qfq\UserFormException ("Deprecated option in " . CONFIG_INI . ": " . SYSTEM_VAR_ADD_BY_SQL . " - " . $msg);
+            }
+        }
+    }
+
+
     /**
      * @param array $config
      */
@@ -144,6 +163,7 @@ class Config {
         Support::setIfNotSet($config, SYSTEM_SHOW_DEBUG_INFO, SYSTEM_SHOW_DEBUG_INFO_AUTO);
         Support::setIfNotSet($config, SYSTEM_SQL_LOG, SYSTEM_SQL_LOG_FILE);
         Support::setIfNotSet($config, SYSTEM_SQL_LOG_MODE, SQL_LOG_MODE_NONE, ''); // do not worry: parse_ini_file() will replace 'none' and 'off' by ''. Set it here again.
+        Support::setIfNotSet($config, SYSTEM_MAIL_LOG, SYSTEM_MAIL_LOG_FILE);
         Support::setIfNotSet($config, F_BS_COLUMNS, '12');
         Support::setIfNotSet($config, F_BS_LABEL_COLUMNS, '3');
         Support::setIfNotSet($config, F_BS_INPUT_COLUMNS, '6');
@@ -187,7 +207,7 @@ class Config {
 
         Support::setIfNotSet($config, DOCUMENTATION_QFQ, DOCUMENTATION_QFQ_URL);
 
-        Support::setIfNotSet($config, SYSTEM_VAR_ADD_BY_SQL, SYSTEM_VAR_ADD_BY_SQL_DEFAULT);
+//        Support::setIfNotSet($config, SYSTEM_FILL_STORE_SYSTEM_BY_SQL, SYSTEM_VAR_ADD_BY_SQL_DEFAULT);
 
         return $config;
     }
diff --git a/extension/qfq/qfq/store/Session.php b/extension/qfq/qfq/store/Session.php
index 1841affef937e9e2339c4a2145a8ef541f9e2687..c0ef096bdfbc1fb2889ebe4c6a7956c5f5483a3d 100644
--- a/extension/qfq/qfq/store/Session.php
+++ b/extension/qfq/qfq/store/Session.php
@@ -33,8 +33,13 @@ class Session {
         } else {
             ini_set('session.cookie_httponly', 1);
 
+            $path = $this->getSitePath();
+            session_set_cookie_params(0, $path);
+
             session_name(SESSION_NAME);
+
             session_start();
+
             self::$sessionId = session_id();
         }
 
@@ -44,6 +49,38 @@ class Session {
 
     }
 
+    /**
+     * Extract the SitePath of the current T3 installation.
+     *
+     * return: <path>  with a trailing '/'
+     */
+    private static function getSitePath() {
+
+        if (empty($_SERVER['SCRIPT_NAME'])) {
+            throw new CodeException('Missing _SERVER[SCRIPT_NAME]', ERROR_SESSION_BROKEN_SCRIPT_PATH);
+        }
+
+        $path = $_SERVER['SCRIPT_NAME'];
+        $pos = strrpos($path, '/');
+        if ($pos === false) {
+            throw new CodeException("Broken _SERVER[SCRIPT_NAME]: $path", ERROR_SESSION_BROKEN_SCRIPT_PATH);
+        }
+        // Remove PHP script
+        $path = substr($path, 0, $pos + 1);
+
+        // QFQ might be called by API - justify to the SitePath
+        $pos = strpos($path, 'typo3conf/ext/qfq/api');
+        if ($pos !== false) {
+            $path = substr($path, 0, $pos);
+        }
+
+        if (empty($path)) {
+            throw new CodeException("Broken _SERVER[SCRIPT_NAME]: $path", ERROR_SESSION_BROKEN_SCRIPT_PATH);
+        }
+
+        return $path;
+    }
+
     /**
      * Free a lock on the current session
      */
diff --git a/extension/qfq/qfq/store/Store.php b/extension/qfq/qfq/store/Store.php
index 2192a836ed7e5091bdeafb66987dc8e49d6e81f8..4d8f7931dc9dc22d21bc2922a8af37a3e5d1c408 100644
--- a/extension/qfq/qfq/store/Store.php
+++ b/extension/qfq/qfq/store/Store.php
@@ -168,9 +168,9 @@ class Store {
             STORE_ADDITIONAL_FORM_ELEMENTS => false,
         ];
 
+        self::fillStoreTypo3($bodytext);  // should be filled before fillStoreSystem() to offer T3 variables
+        self::fillStoreClient();  // should be filled before fillStoreSystem() to offer Client variables
         self::fillStoreSystem($fileConfigIni);
-        self::fillStoreTypo3($bodytext);
-        self::fillStoreClient();
         self::fillStoreSip();
         self::fillStoreExtra();
 
@@ -247,6 +247,13 @@ class Store {
             $config[SYSTEM_SQL_LOG] = $config[SYSTEM_PATH_EXT] . '/' . $config[SYSTEM_SQL_LOG];
         }
 
+        // make SQL PATH absolute. This is necessary to work in different directories correctly.
+        if (!empty($config[SYSTEM_MAIL_LOG]) && $config[SYSTEM_MAIL_LOG][0] !== '/') {
+            $config[SYSTEM_MAIL_LOG] = $config[SYSTEM_PATH_EXT] . '/' . $config[SYSTEM_MAIL_LOG];
+        }
+
+        $config[SYSTEM_SEND_E_MAIL] = $config[SYSTEM_PATH_EXT] . '/qfq/external/sendEmail';
+
         // In case the database credentials are given in the old style: copy them to the new style
         if (!isset($config[SYSTEM_DB_1_USER]) && isset($config[SYSTEM_DB_USER])) {
             $config[SYSTEM_DB_1_USER] = $config[SYSTEM_DB_USER];
@@ -819,26 +826,47 @@ class Store {
     }
 
     /**
-     * Read SYSTEM_VARIABLES_GET_FROM_DB from SYSTEM_STORE and if set:
+     * Read SYSTEM_FILL_STORE_SYSTEM_BY_SQL_1|2|3 from SYSTEM_STORE and if set:
      * a) fire the SQL
-     * b) merge all columns to SYSTEM_STORE
+     * b) merge all columns to STORE_SYSTEM
      */
-    public static function systemStoreUpdate() {
+    public static function StoreSystemUpdate() {
 
+        $db = null;
+        $storeSystemAdd = array();
         $storeSystem = self::getStore(STORE_SYSTEM);
-        if (!empty($storeSystem[SYSTEM_VAR_ADD_BY_SQL])) {
-            $db = new qfq\Database();
 
-            $arr = $db->sql($storeSystem[SYSTEM_VAR_ADD_BY_SQL], ROW_EXPECT_0_1);
-            if (!empty($arr)) {
-                $storeSystem = array_merge($storeSystem, $arr);
-                self::setStore($storeSystem, STORE_SYSTEM, true);
+        for ($ii = 1; $ii <= 3; $ii++) {
+            if (empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii"])) {
+                continue;
+            }
+
+            if ($db == null) {
+                $db = new qfq\Database();
             }
+
+            $errMsg = "More than 1 record found. " . CONFIG_INI . ": " . SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii";
+            $mode = ROW_EXPECT_0_1;
+
+            // If there is an error message defined, this means there should be exactly one record.
+            if (!empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "_$ii"])) {
+                $mode = ROW_EXPECT_1;
+                $errMsg = $storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "_$ii"];
+            }
+
+            $storeSystemAdd = $db->sql($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii"], $mode, array(), $errMsg);
+            $storeSystemAdd = OnArray::keyNameRemoveLeadingUnderscore($storeSystemAdd);
+            $storeSystem = array_merge($storeSystem, $storeSystemAdd);
+
+        }
+
+        if (!empty($storeSystem)) {
+            self::setStore($storeSystem, STORE_SYSTEM, true);
         }
     }
 
     /**
-     * Append an array or the first row of array of arrays to store $storeName.
+     * Append an array (in case of 'array of array': the first row of array) to store $storeName.
      * Existing values will be overwritten.
      *
      * @param $storeName
diff --git a/extension/qfq/sql/formEditor.sql b/extension/qfq/sql/formEditor.sql
index 7a78e5d16b3c774fbff706cc32af3181463ec071..c8ed7679bc903ab0de7caa406af0b199565b6862 100644
--- a/extension/qfq/sql/formEditor.sql
+++ b/extension/qfq/sql/formEditor.sql
@@ -360,6 +360,8 @@ CREATE TABLE IF NOT EXISTS `MailLog` (
   `id`       INT(11)      NOT NULL  AUTO_INCREMENT,
   `grId`     INT(11)      NOT NULL  DEFAULT '0',
   `xId`      INT(11)      NOT NULL  DEFAULT '0',
+  `xId2` INT(11) NOT NULL  DEFAULT '0',
+  `xId3` INT(11) NOT NULL  DEFAULT '0',
   `receiver` TEXT         NOT NULL,
   `sender`   VARCHAR(255) NOT NULL  DEFAULT '',
   `subject`  VARCHAR(255) NOT NULL  DEFAULT '',
diff --git a/extension/qfq/tests/phpunit/DatabaseTest.php b/extension/qfq/tests/phpunit/DatabaseTest.php
index 5482cae452f4b75d8e6872daae0f49867efed254..f241027edec72bbcd99489bdf98f0420c0aaa131 100644
--- a/extension/qfq/tests/phpunit/DatabaseTest.php
+++ b/extension/qfq/tests/phpunit/DatabaseTest.php
@@ -255,7 +255,12 @@ class DatabaseTest extends AbstractDatabaseTest {
         $rc = $this->dbArray[DB_INDEX_DATA_DEFAULT]->sql($sql, ROW_REGULAR, $dummy, 'fake', $dummy, $stat);
 
         // DB_NUM_ROWS | DB_INSERT_ID | DB_AFFECTED_ROWS
-        $this->assertEquals(10, $stat[DB_NUM_ROWS]);
+        $all = '';
+        foreach ($rc as $val) {
+            $all .= '-' . implode('-', $val);
+        }
+
+        $this->assertEquals(3, $stat[DB_NUM_ROWS]);
     }
 
     /**
@@ -467,6 +472,13 @@ class DatabaseTest extends AbstractDatabaseTest {
     protected function setUp() {
         parent::setUp();
 
+        // remove existing tables
+        $allTables = $this->dbArray[DB_INDEX_DATA_DEFAULT]->sql("SHOW TABLES", ROW_REGULAR);
+        foreach ($allTables AS $val) {
+            $table = current($val);
+            $this->dbArray[DB_INDEX_DATA_DEFAULT]->sql("DROP TABLE $table");
+        }
+
         $this->executeSQLFile(__DIR__ . '/fixtures/Generic.sql', true);
     }
 }
diff --git a/extension/qfq/tests/phpunit/EvaluateTest.php b/extension/qfq/tests/phpunit/EvaluateTest.php
index 820f719d755d29292fffdc67cd57a47a15ce147b..b72df041b0d0ca2359b6c79b089b9ac23933d789 100644
--- a/extension/qfq/tests/phpunit/EvaluateTest.php
+++ b/extension/qfq/tests/phpunit/EvaluateTest.php
@@ -124,7 +124,7 @@ class EvaluateTest extends \AbstractDatabaseTest {
         $this->assertEquals($expected2, $eval->parseArray($data, ['formName', 'title']));
 
         $expected2 = $expected;
-        $data['formName'] = "SELECT FROM unknown garbage WITH missing parameter";
+        $data['formName'] = 'SELECT FROM unknown garbage WITH missing parameter';
         $expected2['formName'] = $data['formName'];
 
         $expected2['title'] = $data['title'];
@@ -347,16 +347,16 @@ class EvaluateTest extends \AbstractDatabaseTest {
 //        LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
 //                LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#'),
         $this->store->setVar('a', ' hello world ', STORE_FORM, true);
-        $this->assertEquals('\20hello world\20', $eval->substitute('a:F:all:L', $foundInStore));
-//        $this->assertEquals(' hello world ', $eval->substitute('a:F:all:L', $foundInStore));
+//        $this->assertEquals('\20hello world\20', $eval->substitute('a:F:all:L', $foundInStore));
+        $this->assertEquals(' hello world ', $eval->substitute('a:F:all:L', $foundInStore));
 
         $this->store->setVar('a', 'h\e,l=l+o< >w;o"r#ld', STORE_FORM, true);
         $this->assertEquals('h\5ce\2cl\3dl\2bo\3c \3ew\3bo\22r\23ld', $eval->substitute('a:F:all:L', $foundInStore));
 
 
         $this->store->setVar('a', ' hel;lo world ', STORE_FORM, true);
-        $this->assertEquals('\20hel\3blo world\20', $eval->substitute('a:F:all:sL', $foundInStore));
-//        $this->assertEquals(' hel\3blo world ', $eval->substitute('a:F:all:sL', $foundInStore));
+//        $this->assertEquals('\20hel\3blo world\20', $eval->substitute('a:F:all:sL', $foundInStore));
+        $this->assertEquals(' hel\3blo world ', $eval->substitute('a:F:all:sL', $foundInStore));
 
     }
 
diff --git a/extension/qfq/tests/phpunit/OnArrayTest.php b/extension/qfq/tests/phpunit/OnArrayTest.php
index ef9500fb608472b4bd8b3fe5cd84035e35f8ff00..f8fa40680ed268d90833944d8210b464019f0448 100644
--- a/extension/qfq/tests/phpunit/OnArrayTest.php
+++ b/extension/qfq/tests/phpunit/OnArrayTest.php
@@ -151,8 +151,8 @@ class OnArrayTest extends \PHPUnit_Framework_TestCase {
         $this->assertEquals(array(), OnArray::getArrayItemKeyNameStartWith(array(), ''));
         $this->assertEquals(['a' => 'hello'], OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], ''));
         $this->assertEquals(array(), OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], 'b'));
-        $this->assertEquals([0 => 'hello'], OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], 'a'));
-//        $this->assertEquals(['' => 'hello'], OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], 'a'));
+//        $this->assertEquals([0 => 'hello'], OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], 'a'));
+        $this->assertEquals(['' => 'hello'], OnArray::getArrayItemKeyNameStartWith(['a' => 'hello'], 'a'));
         $this->assertEquals(['b' => 'hello'], OnArray::getArrayItemKeyNameStartWith(['ab' => 'hello'], 'a'));
         $this->assertEquals(array(), OnArray::getArrayItemKeyNameStartWith(['ba' => 'hello'], 'a'));
         $this->assertEquals(['a' => 'is', 'b' => 'john'], OnArray::getArrayItemKeyNameStartWith(['1_a' => 'my', '1_b' => 'name', '2_a' => 'is', '2_b' => 'john'], '2_'));
@@ -167,4 +167,15 @@ class OnArrayTest extends \PHPUnit_Framework_TestCase {
         $this->assertEquals(['name' => "'john'", 'surname' => "'doe'"], OnArray::arrayEscapeshellarg(['name' => 'john', 'surname' => 'doe']));
         $this->assertEquals(['name' => "'john'", 'sub' => ['surname' => "'doe'"]], OnArray::arrayEscapeshellarg(['name' => 'john', 'sub' => ['surname' => 'doe']]));
     }
+
+    public function testArrayKeyNameRemoveLeadingUnderscore() {
+        $this->assertEquals(array(), OnArray::keyNameRemoveLeadingUnderscore(array()));
+        $this->assertEquals(['name' => 'john'], OnArray::keyNameRemoveLeadingUnderscore(['name' => 'john']));
+        $this->assertEquals(['name' => 'john', 'surname' => 'doe'], OnArray::keyNameRemoveLeadingUnderscore(['name' => 'john', 'surname' => 'doe']));
+        $this->assertEquals(['name' => '_john'], OnArray::keyNameRemoveLeadingUnderscore(['_name' => '_john']));
+        $this->assertEquals(['name' => 'john', 'surname' => 'doe'], OnArray::keyNameRemoveLeadingUnderscore(['_name' => 'john', 'surname' => 'doe']));
+        $this->assertEquals(['name' => 'john', 'surname' => 'doe'], OnArray::keyNameRemoveLeadingUnderscore(['name' => 'john', '_surname' => 'doe']));
+        $this->assertEquals(['name' => 'john', 'surname' => 'doe'], OnArray::keyNameRemoveLeadingUnderscore(['_name' => 'john', '_surname' => 'doe']));
+
+    }
 }
diff --git a/extension/qfq/tests/phpunit/ReportTest.php b/extension/qfq/tests/phpunit/ReportTest.php
index a2b7fe06718c816320c536741a9e810fc1b97750..fb2d0badd2a5936f3dce5b658144ad70c500892a 100644
--- a/extension/qfq/tests/phpunit/ReportTest.php
+++ b/extension/qfq/tests/phpunit/ReportTest.php
@@ -10,6 +10,8 @@ require_once(__DIR__ . '/../../qfq/report/Report.php');
 require_once(__DIR__ . '/../../qfq/Evaluate.php');
 require_once(__DIR__ . '/../../qfq/store/Session.php');
 
+const TOTAL_COUNT_PERSON_GENERIC_SQL = 2;
+
 /**
  * Created by PhpStorm.
  * User: crose
@@ -942,19 +944,133 @@ EOF;
         }
     }
 
+
+    /**
+     *
+     */
+    public function testReportPageWrapper() {
+
+        $line = <<<EOF
+10.sql = SELECT firstname FROM Person ORDER BY id LIMIT 2
+10.head = <table>
+10.tail = </table>
+10.rbeg = <tr>
+10.rend = <br>
+10.renr = </tr>
+10.fbeg = <td>
+10.fend = </td>
+10.rsep = --
+10.fsep = ++
+
+10.10.sql = SELECT 'nested' FROM (SELECT '') AS fake WHERE '{{10.line.count}}'='1'
+10.10.shead = Static head
+10.10.stail = Static tail
+10.10.head = Dynamic head
+10.10.tail = Dynamic tail
+10.10.althead = No record found
+10.10.altsql = SELECT 'alt sql fired'
+
+EOF;
+
+        $result = $this->report->process($line);
+        $expect = "<table><tr><td>John</td><br>Static headDynamic headnestedDynamic tailStatic tail</tr>--<tr><td>Jane</td><br>Static headNo record foundalt sql firedStatic tail</tr></table>";
+        $this->assertEquals($expect, $result);
+    }
+
+    /**
+     *
+     */
+    public function testReportVariables() {
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1");
+        $this->assertEquals("normal text ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{10.hidden}}'");
+        $this->assertEquals("normal text hidden", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{10.unknown}}'");
+        $this->assertEquals("normal text {{10.unknown}}", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{fake}}'");
+        $this->assertEquals("normal text {{fake}}", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{fake:V}}'");
+        $this->assertEquals("normal text {{fake:V}}", $result);
+
+        $this->store->setVar('fake', 'hello world ', STORE_VAR);
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden ' AS _hidden, 'text ' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{fake:V}}'");
+        $this->assertEquals("normal text hello world ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fake:V}}'");
+        $this->assertEquals("normal text hello world normal text hello world ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}} '");
+        $this->assertEquals("normal text hello world -1-2-0 normal text hello world -2-2-0 ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fake:V}}-{{10.line.count}}-{{10.line.total}} '");
+        $this->assertEquals("normal text hello world -1-2 normal text hello world -2-2 ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.10.line.count}}-{{10.10.line.total}} '");
+        $this->assertEquals("normal text hello world -1-2-1-1 normal text hello world -2-2-1-1 ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fake:V:::not found}} '");
+        $this->assertEquals("normal text hello world  normal text hello world  ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fakeDontExist:V:::not found}} '");
+        $this->assertEquals("normal text not found normal text not found ", $result);
+
+        $result = $this->report->process("10.sql = SELECT 'normal ', 'hidden' AS _hidden, 'text ' FROM Person ORDER BY id\n10.10.sql = SELECT '{{fakeDontExist:V:::{{EDIT_FORM_PAGE:Y}}}} '");
+        $this->assertEquals("normal text form normal text form ", $result);
+
+
+//        store various
+//        store default
+        // head.tail,rbeg,.seop mit variabeln
+//        5.sql im 10 head abragen
+    }
+
     /**
      *
      */
-    public function testReportSurpress() {
+    public function testReportPageVariables() {
+
+        $line = <<<EOF
+10.sql = SELECT name, firstname FROM Person ORDER BY id LIMIT 2
+10.head = h:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.tail = t:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.rbeg = rb:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.rend = re:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.renr = rr:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.fbeg = -fb:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.fend = -fe:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.rsep = rs:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.fsep = fs:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+EOF;
 
-        $result = $this->report->process("10.sql = SELECT 'normal', 'hidden' AS _hidden, 'text' FROM Person ORDER BY id LIMIT 1");
-        $this->assertEquals("normaltext", $result);
+        $this->store->setVar('fake', 'hello world', STORE_VAR);
+        $result = $this->report->process($line);
+        $expect = "h:hello world-1-2-0,rb:hello world-1-2-0,-fb:hello world-1-2-0,Doe-fe:hello world-1-2-0,fs:hello world-1-2-0,-fb:hello world-1-2-0,John-fe:hello world-1-2-0,re:hello world-1-2-0,rr:hello world-1-2-0,rs:hello world-1-2-0,rb:hello world-2-2-0,-fb:hello world-2-2-0,Smith-fe:hello world-2-2-0,fs:hello world-2-2-0,-fb:hello world-2-2-0,Jane-fe:hello world-2-2-0,re:hello world-2-2-0,rr:hello world-2-2-0,t:hello world-2-2-0,";
+        $this->assertEquals($expect, $result);
+
+        $line = <<<EOF
+10.sql = SELECT name, firstname FROM Person ORDER BY id LIMIT 2
+10.10.sql = SELECT ' blue '
+10.10.head = h:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.tail = t:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.rbeg = rb:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.rend = re:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.renr = rr:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.fbeg = -fb:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.fend = -fe:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.rsep = rs:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+10.10.fsep = fs:{{fake:V}}-{{10.line.count}}-{{10.line.total}}-{{10.line.insertId}},
+EOF;
 
-        $result = $this->report->process("10.sql = SELECT 'normal', 'hidden' AS _hidden, 'text' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{10.hidden}}'");
-        $this->assertEquals("normaltexthidden", $result);
+        $this->store->setVar('fake', 'hello world', STORE_VAR);
+        $result = $this->report->process($line);
+        $expect = "DoeJohnh:hello world-1-2-0,rb:hello world-1-2-0,-fb:hello world-1-2-0, blue -fe:hello world-1-2-0,re:hello world-1-2-0,rr:hello world-1-2-0,t:hello world-1-2-0,SmithJaneh:hello world-2-2-0,rb:hello world-2-2-0,-fb:hello world-2-2-0, blue -fe:hello world-2-2-0,re:hello world-2-2-0,rr:hello world-2-2-0,t:hello world-2-2-0,";
+        $this->assertEquals($expect, $result);
 
-        $result = $this->report->process("10.sql = SELECT 'normal', 'hidden' AS _hidden, 'text' FROM Person ORDER BY id LIMIT 1\n10.10.sql = SELECT '{{10.unknown}}'");
-        $this->assertEquals("normaltext{{10.unknown}}", $result);
     }
 
     /**
diff --git a/extension/qfq/tests/phpunit/StoreTest.php b/extension/qfq/tests/phpunit/StoreTest.php
index 564401fd9d44a5343ded8ff283ad6f91210710e3..061dd4c9e61b06a2fa0e9fee5b84b23039b25557 100644
--- a/extension/qfq/tests/phpunit/StoreTest.php
+++ b/extension/qfq/tests/phpunit/StoreTest.php
@@ -252,11 +252,12 @@ EOT;
             SYSTEM_DB_INIT => 'set names utf8',
             SYSTEM_SQL_LOG_MODE => 'modify',
 
-            SYSTEM_DB_INDEX_DATA => 1,
-            SYSTEM_DB_INDEX_QFQ => 1,
+            SYSTEM_DB_INDEX_DATA => '1',
+            SYSTEM_DB_INDEX_QFQ => '1',
 
             SYSTEM_DATE_FORMAT => 'yyyy-mm-dd',
             SYSTEM_SHOW_DEBUG_INFO => SYSTEM_SHOW_DEBUG_INFO_NO,
+
             F_BS_COLUMNS => '12',
             F_BS_LABEL_COLUMNS => '3',
             F_BS_INPUT_COLUMNS => '6',
@@ -287,9 +288,9 @@ EOT;
             F_BUTTON_ON_CHANGE_CLASS => 'btn-info alert-info',
             SYSTEM_EDIT_FORM_PAGE => 'form',
             SYSTEM_SECURITY_VARS_HONEYPOT => 'email,username,password',
-            SYSTEM_SECURITY_ATTACK_DELAY => '5',
+            SYSTEM_SECURITY_ATTACK_DELAY => 5,
             SYSTEM_SECURITY_SHOW_MESSAGE => '0',
-            SYSTEM_SECURITY_GET_MAX_LENGTH => '50',
+            SYSTEM_SECURITY_GET_MAX_LENGTH => 50,
             SYSTEM_ESCAPE_TYPE_DEFAULT => 'm',
             SYSTEM_GFX_EXTRA_BUTTON_INFO_INLINE => '<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>',
             SYSTEM_GFX_EXTRA_BUTTON_INFO_BELOW => '<span class="glyphicon glyphicon-info-sign text-info" aria-hidden="true"></span>',
@@ -299,8 +300,6 @@ EOT;
             SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS => SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS_DEFAULT,
 
             DOCUMENTATION_QFQ => DOCUMENTATION_QFQ_URL,
-            SYSTEM_VAR_ADD_BY_SQL => 'SELECT id AS periodId FROM Period WHERE start<=NOW() ORDER BY start DESC LIMIT 1',
-
         ];
 
         $fileName = $this->createFile($body);
@@ -312,6 +311,8 @@ EOT;
         unset($value[SYSTEM_SQL_LOG]);
         unset($value[SYSTEM_PATH_EXT]);
         unset($value[SYSTEM_SITE_PATH]);
+        unset($value[SYSTEM_MAIL_LOG]);
+        unset($value[SYSTEM_SEND_E_MAIL]);
 
         // check default values
         $this->assertEquals($expect, $value, "Retrieve system store.");
@@ -348,6 +349,8 @@ EOT;
         unset($value[SYSTEM_SQL_LOG]);
         unset($value[SYSTEM_PATH_EXT]);
         unset($value[SYSTEM_SITE_PATH]);
+        unset($value[SYSTEM_MAIL_LOG]);
+        unset($value[SYSTEM_SEND_E_MAIL]);
 
         // check default values
         $this->assertEquals($expect, $value, "Check explizit defined values.");
diff --git a/version b/version
index 166c9e29b70b1e6a84168e2e608430f62da4254a..35aa2f3c0799c4bc021a1ebe62d38c5c58582d63 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.25.2
+0.25.4