From fa26ecec19ff3a39f5c87ca90513f458708caed3 Mon Sep 17 00:00:00 2001
From: Carsten  Rose <carsten.rose@math.uzh.ch>
Date: Fri, 5 Feb 2016 08:53:36 +0100
Subject: [PATCH] Report*: initially copied files to QFQ. Report is not
 running.

---
 qfq/report/Db.php        |  373 +++++++++++++
 qfq/report/Define.php    |   64 +++
 qfq/report/Error.php     |  181 +++++++
 qfq/report/Link.php      |  638 ++++++++++++++++++++++
 qfq/report/Log.php       |  214 ++++++++
 qfq/report/Report.php    | 1081 ++++++++++++++++++++++++++++++++++++++
 qfq/report/Sendmail.php  |   62 +++
 qfq/report/Utils.php     |  324 ++++++++++++
 qfq/report/Variables.php |  159 ++++++
 9 files changed, 3096 insertions(+)
 create mode 100644 qfq/report/Db.php
 create mode 100644 qfq/report/Define.php
 create mode 100644 qfq/report/Error.php
 create mode 100644 qfq/report/Link.php
 create mode 100644 qfq/report/Log.php
 create mode 100644 qfq/report/Report.php
 create mode 100644 qfq/report/Sendmail.php
 create mode 100644 qfq/report/Utils.php
 create mode 100644 qfq/report/Variables.php

diff --git a/qfq/report/Db.php b/qfq/report/Db.php
new file mode 100644
index 000000000..596b0caff
--- /dev/null
+++ b/qfq/report/Db.php
@@ -0,0 +1,373 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+require_once(__DIR__ . '/Define.php');
+require_once(__DIR__ . '/Error.php');
+require_once(__DIR__ . '/Log.php');
+
+
+class Db {
+
+    public $t3_typo_db_host = "";
+    /**
+     * @var string
+     */
+    private $lastUsedDB = "";
+    /**
+     * @var array
+     */
+    private $arrDB = array();
+    /**
+     * @var
+     */
+    private $t3_typo_db_username, $t3_typo_db, $t3_typo_db_password;
+    /**
+     * @var Log
+     */
+    private $log;
+
+    // Emulate global variable: will be set much earlier in other functions. Will be shown in error messages.
+    private $fr_error;
+
+
+    /**
+     * Constructor:
+     *
+     * @param    Log   fully created for logging.
+     *
+     */
+
+    public function __construct($log) {
+        // CR 25.4.11: require_once does not work here. No idea why
+        require(PATH_typo3conf . 'localconf.php');
+        $this->t3_typo_db_host = $typo_db_host;
+        $this->t3_typo_db_username = $typo_db_username;
+        $this->t3_typo_db = $typo_db;
+        $this->t3_typo_db_password = $typo_db_password;
+
+        $this->log = $log;
+    }
+
+    /**
+     * Set Array fr_error: setter function to set most recent values, especially fr_erro['row'].
+     * Will be shown in error messages.
+     *
+     * @param array $fr_error uid, pid, row, column_idx, full_level
+     */
+    public function set_fr_error(array $fr_error) {
+        $this->fr_error = $fr_error;
+    }
+
+    /**
+     * doQueryKeys: See doQuery
+     *                Difference: fake Array for $keys
+     *
+     * @param string $dbAlias
+     * @param string $sql
+     * @param array $result
+     * @param string string $expect
+     * @param string $merge
+     * @return bool
+     * @throws CodeReportException
+     * @throws SqlReportException
+     * @throws SyntaxReportException
+     */
+    public function doQuery($dbAlias, $sql, array &$result, $expect = EXPECT_GE_0, $merge = MERGE_NONE) {
+        return ($this->doQueryKeys($dbAlias, $sql, $result, $fake, $expect, $merge, MYSQL_ASSOC));
+    }
+
+    /**
+     * doQueryKeys: fires a show, select, insert, update or delete and collects result.
+     *            insert, update and delete will produce a log entry.
+     *            If: $expect==EXPECT_SQL_OR_STRING, '$sql' can be anything which won't be fired if it's not a SQL statement.
+     *
+     * @param string $dbAlias Name of Database to be used.
+     * @param string $sql Select Query
+     * @param array $result content depends on $sql.
+     *                                    $sql='insert ...': mysql_last_insert_id will be returned in $result.
+     *                                    $sql='update ...' or 'delete ----': mysql_affected_rows will be returned in $result.
+     *                                $sql='select ...': all selected rows will be returned in $result.
+     *                                $result will be formatted like specified in $merge.
+     *                                    Attention: with EXPECT_1|EXPECT_0_1 '$result' is a one dimensional array, else a two dimensional array.
+     * @param array $keys
+     * @param string $expect
+     * @param string $merge Applies different modes of merging - MERGE_NONE, MERGE_ROW, MERGE_ALL
+     * @param int $arrayMode
+     * @return bool                     true: all ok
+     *                                  false:    a) Number of rows don't match $expect
+     *                                        b) $expect==EXPECT_SQL_OR_STRING and $sql is not an SQL expression (instead it's a regular string) - this is not bad, just to indicate that there was no query.
+     * @throws CodeReportException
+     * @throws SqlReportException
+     * @throws SyntaxReportException
+     */
+    public function doQueryKeys($dbAlias, $sql, array &$result, array &$keys, $expect = EXPECT_GE_0, $merge = MERGE_NONE, $arrayMode = MYSQL_NUM) {
+        $result = "";
+        $tmp = "";
+        $action = "";
+
+        $this->selectDB($dbAlias);
+
+        if ($this->fr_error["debug_level"] >= DEBUG_SQL) {
+            // T3 function: debug()
+//            debug(array('SQL' => $sql));
+        }
+
+        // Extract first parameter to check if it is a SQL statement
+        $tmp = explode(" ", trim($sql), 2);
+        $action = strtolower($tmp[0]);
+        switch ($action) {
+            case "show"  :
+            case "select":
+            case "insert":
+            case "update":
+            case "delete":
+                break;  // SQL Statement: go further
+            default:
+                if ($expect == EXPECT_SQL_OR_STRING) {
+                    $result = $sql;
+                    return (FALSE);    // nothing bad, just to indicate $sql was not a SQL statement.
+                } else
+                    throw new SyntaxReportException ("Unexpected SQL Statement: '$action'", "", __FILE__, __LINE__, array("DB:$dbAlias", "SQL:$sql"), $this->fr_error);
+        }
+
+        // Fire SQL statement
+        if (!($res = mysql_query($sql))) {
+            // Escape query if in AJAX mode
+            $sql = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') ? addslashes($sql) : $sql;
+            throw new SqlReportException ("Did not get result for query.", $sql, __FILE__, __LINE__, $this->fr_error);
+        }
+
+
+        switch ($action) {
+            case "show"  :
+            case "select":
+                $action = QUERY; // aggregate 'SLELECT' and 'SHOW' and ...
+                $num_rows = mysql_num_rows($res);
+                break;
+
+            case "insert":
+                $num_rows = mysql_affected_rows();
+                $result = mysql_insert_id();
+                break;
+            case "update":
+            case "delete":
+                $num_rows = mysql_affected_rows();
+                $result = $num_rows;
+                break;
+
+            default:
+                throw new CodeReportException ("This error should be catched 20 lines above.", __FILE__, __LINE__);  // can't be happen, should already be detected earlier.
+        }
+
+        // Logging
+        if ($action != QUERY) {
+            // Not a query: write log and go home.
+            $this->log->log_sql('db', '', $num_rows, $result, $sql);
+            return (TRUE);
+        } else {
+            // Logging if localconf log_level >=D2
+            $this->log->log_do('error', 'D2', '-', $sql);
+        }
+
+        // Check $expect against real result.
+        switch ($expect) {
+            case EXPECT_0:
+                return ($num_rows == 0);
+                break;
+            case EXPECT_1:
+                if ($num_rows != 1) return (FALSE);
+                break;
+            case EXPECT_0_1:
+                if ($num_rows > 1) return (FALSE);
+                break;
+            case EXPECT_GE_0:
+                break;
+            case EXPECT_GE_1:
+                if ($num_rows == 0) return (FALSE);
+                break;
+            case EXPECT_SQL_OR_STRING:
+                break;
+            default:
+                throw new CodeReportException ("Unknown 'expect' qualifier: $expect", __FILE__, __LINE__);
+                break;
+        }
+
+        // Preparation to fetch all rows
+        $tmp = "";
+        $fieldCount = 0;
+
+        // Fetch all rows:
+        while ($row = mysql_fetch_array($res, $arrayMode)) {
+            foreach ($row as $key => $value) {
+                $row[$key] = stripslashes($value);
+            }
+
+            switch ($merge) {
+                case MERGE_NONE:
+                    $tmp[] = $row;
+                    break;
+                case MERGE_ROW:
+                    $tmp[] = implode($row);
+                    break;
+                case MERGE_ALL:
+                    $tmp .= implode($row);
+                    break;
+                default:
+                    throw new CodeReportException ("Unknown 'merge' qualifier: $merge", __FILE__, __LINE__);
+                    break;
+            }
+        }
+
+        // Collect 'keys'
+        if ($merge == MERGE_NONE) {
+            $keys = array();
+            $numberfields = mysql_num_fields($res);
+
+            for ($i = 0; $i < $numberfields; $i++) {
+                $keys[] = mysql_field_name($res, $i);
+            }
+        }
+
+        // adjust Result Array: one or two dimensions.
+        switch ($expect) {
+            case EXPECT_0:
+                break;
+            case EXPECT_1:
+            case EXPECT_0_1:
+            case EXPECT_SQL_OR_STRING:
+                $result = $tmp[0];
+                break;
+            default:
+                $result = $tmp;
+        }
+
+        if ($merge == MERGE_ALL)
+            $result = $tmp;
+
+        mysql_free_result($res);
+
+        return (TRUE);
+    } // doQueryKeys()
+
+    /**
+     * select DB
+     *
+     * @param    string $dbAlias : Name of the dbname
+     *
+     * @return    bool        TRUE if ok, else exception.
+     */
+    public function selectDB($dbAlias) {
+
+        if (!$dbAlias)
+            throw new CodeReportException ("Failed: empty dbAlias", __FILE__, __LINE__);
+
+        // If the db is still selected: do nothing.
+        if ($dbAlias == $this->lastUsedDB)
+            return true;
+
+        // if the db is already open - just select it.
+        if (isset($this->arrDB[$dbAlias]['link'])) {
+            if (!mysql_select_db($this->arrDB[$dbAlias]['db']))
+                throw new SqlReportException ("Failed: mysql_select_db($this->arrDB[$dbAlias]['db'])", "", __FILE__, __LINE__, $this->fr_error);
+            return true;
+        }
+
+        $this->openDB($dbAlias);
+
+        return true;
+    } // openDB()
+
+    /**
+     * Open specified DB
+     *
+     * @param string $dbAlias Name of database to be opened
+     * @throws SqlReportException
+     */
+    public function openDB($dbAlias) {
+        // TYPO3 globale Variablen
+// 		global $typo_db_host,$typo_db_username,$typo_db,$typo_db_password;
+//		Du sollst kein global verwenden!!
+
+        if ($dbAlias == T3) {
+            $host = $this->t3_typo_db_host;
+            $username = $this->t3_typo_db_username;
+            $db = $this->t3_typo_db;
+            $password = $this->t3_typo_db_password;
+        } else {
+//			require(PATH_typo3conf.'ext/formreport/ext_localconf.php');
+            $host = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT][$dbAlias]['host'];
+            $username = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT][$dbAlias]['username'];
+            $db = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT][$dbAlias]['name'];
+            $password = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT][$dbAlias]['password'];
+
+        }
+
+        // If 't3' is specified or the custom DB is not fully specified, take credentials from localconf.php
+#		$host     = $host     ? $host 		: $typo_db_host;
+#		$username = $username ? $username	: $typo_db_username;
+#		$db       = $db       ? $db 		: $typo_db;
+#		$password = $password ? $password 	: $typo_db_password;
+
+
+        // MySQL Connect
+        if (!($link = mysql_connect($host, $username, $password)))
+            throw new SqlReportException ("mysql_connect($host, $username)", "", __FILE__, __LINE__, $this->fr_error);
+
+        // Set connection charset
+        if (!mysql_set_charset('utf8', $link))
+            throw new SqlReportException ("mysql_set_charset('utf8', $link)", "", __FILE__, __LINE__, $this->fr_error);
+
+        // MySQL select
+        if (!mysql_select_db($db, $link))
+            throw new SqlReportException ("mysql_select_db($db)", "", __FILE__, __LINE__, $this->fr_error);
+
+        // Remember that this DB has been opened.
+        $this->arrDB[$dbAlias]['link'] = $link;
+        $this->arrDB[$dbAlias]['db'] = $db;
+
+        // Remember the new DB as 'last used'
+        $this->lastUsedDB = $dbAlias;
+
+    } // selectDB()
+
+    /**
+     * closeAllDB
+     *
+     * @return    void
+     */
+    public function closeAllDB() {
+
+        foreach ($this->arrDB as $key => $value) {
+            if ($key != T3) {
+                mysql_close($value);
+                $arrDB[$key] = null;
+            }
+        }
+
+    } // closeAllDB()
+}
diff --git a/qfq/report/Define.php b/qfq/report/Define.php
new file mode 100644
index 000000000..592315f8c
--- /dev/null
+++ b/qfq/report/Define.php
@@ -0,0 +1,64 @@
+<?php
+
+define("EXTKEY", "formreport");
+define("FORMREPORT", "formreport");
+define("LENGTH_HASH", 32);
+
+// dbalias of Extension DB
+define("DB", "db");
+define("T3", "t3");
+
+// Constants for tx_form_pi1.php
+define("FR_FORM", "tx_formreport_form");
+define("FR_FORMELEMENT", "tx_formreport_formelement");
+define("FR_LOCK", "tx_formreport_lock");
+
+define("URL_HASH", "S_hash");
+define("URL_RECORD_ID", "N_r");
+define("URL_FORMNAME", "S_form");
+define("URL_FORM", "URL");
+
+
+// Definitions for doQuery()
+define("EXPECT_0", "expect_0");
+define("EXPECT_1", "expect_1");
+define("EXPECT_0_1", "expect_0_1");
+define("EXPECT_GE_0", "expect_ge_0");
+define("EXPECT_GE_1", "expect_ge_1");
+define("EXPECT_SQL_OR_STRING", "expect_sql_or_string");
+
+define("MERGE_NONE", "merge_none");
+define("MERGE_ROW", "merge_row");
+define("MERGE_ALL", "merge_all");
+
+define("QUERY", "query");
+
+// Definitions for sanatize() strip_tags
+define("TAGS_NONE", "none");
+define("TAGS_MARKUP", "markup");
+define("TAGS_CUSTOM", "custom");
+define("TAGS_ALL", "all");
+
+// Definitions for sanatize() reaction on changed variables by sanatize them
+define("TAGS_EXCEPTION", "exception");
+define("TAGS_SANATIZE", "sanatize");
+define("TAGS_IGNORE", "ignore");
+
+// define("LIST_MARKUP_TAGS","<br><p><em><strong><code><samp><kbd><var><cite><dfn><abbr><acronym><q>");
+define("LIST_MARKUP_TAGS", "<br><p><em><strong><font><b><u><i><span><div><ol><ul><li><code><samp><kbd><var><cite><dfn><abbr><acronym><q>");
+
+define("DEBUG_SQL", "1");
+define("DEBUG_BASIC", "2");
+define("DEBUG_VERBOSE", "3");
+define("DEBUG_EXTREME", "4");
+
+// Set default values - overridden by ext_localconf.php
+define("LOCK_RECORDS_INTERVAL", "300");
+define("DFLT_UPLOAD_BASE_DIR", "fileadmin");
+define("DFLT_UPLOAD_TMP_DIR", "fileadmin/tempfiles");
+define("DFLT_UPLOAD_TMP_TTL", "300");
+
+define("PATH_ICONS", "typo3conf/ext/" . FORMREPORT . "/icons/");
+// Definitions to allow successfull include of ext_localconf.
+//define( 'TYPO3_MODE', '1' );
+//define( 'FORMREPORT', '1' );
diff --git a/qfq/report/Error.php b/qfq/report/Error.php
new file mode 100644
index 000000000..0dcbe8524
--- /dev/null
+++ b/qfq/report/Error.php
@@ -0,0 +1,181 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+/**
+ * syntaxException: Webmaster made a mistake in tt_content record or form definition
+ *
+ * @param    string $message
+ * @param    string $code
+ * @param    string $file
+ * @param    string $line
+ * @param    string|array $customMessage =array()   a) string: "key: value",  b) array("key1: value1","key2: value2", ...) - all items will be displayed on an own line in the error message.
+ */
+class SyntaxReportException extends \Exception {
+    private $fr_error;
+
+    public function __construct($message, $code, $file, $line, $customMessage = array(), $fr_error = array()) {
+        parent::__construct($message);
+        $this->code = $code;
+        $this->file = $file;
+        $this->line = $line;
+        $this->customMessage = $customMessage;
+        $this->fr_error = $fr_error;
+
+    }
+
+    public function errorMessage() {
+//		global $BE_USER; TA: Du sollst kein global verwenden!!
+        // If '$this->customMessage' is a string, convert it to an array.
+        if ($this->customMessage && !is_array($this->customMessage)) $this->customMessage = array($this->customMessage);
+
+        //error message
+//		if ($BE_USER->user["uid"]>0) {		TA: Du sollst kein global verwenden!!
+        if ($GLOBALS['BE_USER']->user["uid"] > 0 || $_SESSION[FORMREPORT]['be_user_uid'] > 0) {
+            $errorMsg = nl2br("<hr />Error: <strong>" . htmlentities($this->getMessage()) . "</strong><br />");
+            $errorMsg .= "Formreport statement: <strong>" . htmlentities($this->fr_error["row"]) . "</strong><br />";
+
+            // Print custom messages.
+            foreach ($this->customMessage as $value) {
+                $tmparr = explode(":", $value, 2);
+                $errorMsg .= $tmparr[0] . ":<strong>" . htmlentities($tmparr[1]) . "</strong><br />";
+            }
+            $errorMsg .= "T3 Page pid: <strong>" . $this->fr_error["pid"] . "</strong><br />";
+            $errorMsg .= "ttcontent record uid: <strong>" . $this->fr_error["uid"] . "</strong><br />";
+            $errorMsg .= "File:  <strong>" . $this->file . "</strong><br />Line:  <strong>" . $this->line . "</strong><br />MSG: <strong>" . $this->code . "</strong><br />";
+            $errorMsg .= "StackTrace<pre>" . nl2br($this->getTraceAsString()) . "</pre><hr />";
+        } else {
+            $errorMsg = "<hr />Error: <strong>" . htmlentities($this->getMessage()) . "</strong><hr />";
+        }
+        return $errorMsg;
+    }  // errorMessage()
+} // class syntaxException
+
+/**
+ * sqlException: An SQL-Query returned an unexpected result
+ *
+ * @param    string $message
+ * @param    string $sql
+ * @param    string $file
+ * @param    string $line
+ */
+class SqlReportException extends \Exception {
+    private $fr_error;
+
+    public function __construct($message, $sql, $file, $line, $fr_error = array()) {
+        parent::__construct($message);
+        $this->file = $file;
+        $this->line = $line;
+        $this->sql = $sql;
+        $this->fr_error = $fr_error;
+    }
+
+    public function errorMessage() {
+//		global $BE_USER; TA: Du sollst kein global verwenden!!
+
+        //error message
+//		if ($BE_USER->user["uid"]>0) {		TA: Du sollst kein global verwenden!!
+        if ($GLOBALS['BE_USER']->user["uid"] > 0 || $_SESSION[FORMREPORT]['be_user_uid'] > 0) {
+            $errorMsg = nl2br("<hr />Error: <strong>" . htmlentities($this->getMessage()) . "</strong><br />MySQL:  <strong>" . mysql_error() . "</strong><hr />");
+            $errorMsg .= "SQL: <strong>" . htmlentities($this->sql) . "</strong><hr />";
+            $errorMsg .= "Formreport: <strong>" . $this->fr_error["row"] . "</strong><hr />";
+            $errorMsg .= "T3 Page pid: <strong>" . $this->fr_error["pid"] . "</strong><br>";
+            $errorMsg .= "ttcontent record uid: <strong>" . $this->fr_error["uid"] . "</strong><br />";
+            $errorMsg .= "Line: <strong>" . $this->line . "</strong><br />File: <strong>" . $this->file . "</strong><hr />";
+
+            $errorMsg .= "StackTrace<pre>" . nl2br($this->getTraceAsString()) . "</pre><hr />";
+        } else {
+            $errorMsg = "<hr />Error: <strong>" . htmlentities($this->getMessage()) . "</strong><hr />";
+        }
+        return $errorMsg;
+    }  // errorMessage()
+} // class sqlException
+
+/**
+ * codeException: Error in formreport code
+ *
+ * @param    string $message
+ * @param    string $file
+ * @param    string $line
+ */
+class CodeReportException extends \Exception {
+
+    protected $file;
+    protected $line;
+
+    public function __construct($message, $file = "undefined", $line = "undefined") {
+        parent::__construct($message);
+        $this->file = $file;
+        $this->line = $line;
+    }
+
+    public function errorMessage() {
+//		global $BE_USER; TA: Du sollst kein global verwenden!!
+
+//		if ($BE_USER->user["uid"]>0) {		TA: Du sollst kein global verwenden!!
+        if ($GLOBALS['BE_USER']->user["uid"] > 0 || $_SESSION[FORMREPORT]['be_user_uid'] > 0) {
+            $errorMsg = nl2br("<hr />Error: <strong>" . $this->getMessage() . "</strong><br />File:  <strong>" . $this->file . "</strong><br />Line:  <strong>" . $this->line . "</strong><hr />");
+            $errorMsg .= "StackTrace<pre>" . nl2br($this->getTraceAsString()) . "</pre><hr />";
+        } else {
+            $errorMsg = "<hr />Error: <strong>" . $this->getMessage() . "</strong><hr />";
+        }
+
+        return $errorMsg;
+    }  // errorMessage()
+} // class codeException
+
+
+/**
+ * userException: Exception on user-level: Session expired, invalid form submission, no authorization, invalid request, etc.
+ *
+ * @param    string $message
+ * @param    string $file
+ * @param    string $line
+ */
+class UserReportException extends \Exception {
+
+    protected $file;
+    protected $line;
+
+    public function __construct($message, $file = "undefined", $line = "undefined") {
+        parent::__construct($message);
+        $this->file = $file;
+        $this->line = $line;
+    }
+
+    public function errorMessage() {
+        if ($GLOBALS['BE_USER']->user["uid"] > 0 || $_SESSION[FORMREPORT]['be_user_uid'] > 0) {
+            $errorMsg = nl2br("<hr />Error: <strong>" . $this->getMessage() . "</strong><br />File:  <strong>" . $this->file . "</strong><br />Line:  <strong>" . $this->line . "</strong><hr />");
+            $errorMsg .= "StackTrace<pre>" . nl2br($this->getTraceAsString()) . "</pre><hr />";
+        } else {
+            $errorMsg = "<hr />Error: <strong>" . $this->getMessage() . "</strong><hr />";
+        }
+
+        return $errorMsg;
+    }  // errorMessage()
+} // class userException
+
diff --git a/qfq/report/Link.php b/qfq/report/Link.php
new file mode 100644
index 000000000..783a5d111
--- /dev/null
+++ b/qfq/report/Link.php
@@ -0,0 +1,638 @@
+<?PHP
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+require_once(__DIR__ . '/Define.php');
+require_once(__DIR__ . '/Utils.php');
+
+/*
+ * u:url
+ * m:mailto
+ * p:page
+ * 
+ * t:text
+ * a:AltText
+ * o:ToolTip
+ * 
+ * r:render   
+ * g:target
+ * c:class  [n|i|e]:<class>
+ * q:question  <text>
+ * e:encryption 0|1
+ * 
+ * E:edit
+ * N:new
+ * D:delete
+ * H:Help
+ * I:information
+ * M:Magnifier
+ * B:bullet
+ * P:picture       [file]
+ * C:checkbox    [name]
+ * R:right 
+ * h:hash  
+ * f:formname
+ * i:recordId
+ * T:tablename
+ * 
+ * A:    A:[u:p:m]
+ * G:    G:[N|..]
+ */
+
+class Link {
+
+    private $mailto = "", $url = "", $image = "", $text = "", $altText = "", $imageTitle = "", $question = "", $target = "", $formName = "", $additionalUrlParams = "";
+    private $toolTip = array(), $modeRender = 0, $picturePosition = "l", $modeHash = FALSE, $recordId = -1, $encryption = 1;
+    private $linkClass = "", $defaultLinkClass = "";
+    private $linkClassSelector = array("i" => "internal", "e" => "external");
+    private $renderControl, $link = "", $tableName = "";
+
+    // Simulate global variable: will be set much earlier in other functions. Will be shown in error messages.
+    private $fr_error;
+
+    private $exceptionBody = "";
+    /**
+     * @var Utils
+     */
+    private $utils;
+    /**
+     * @var string
+     */
+    private $pageAlias = '';
+
+    /**
+     * __construct
+     *
+     * @param string $fullLevel Recent processed level, f.e.: '10.20.sql'
+     * @param string $sql Recent processed query, f.e.: SELECT p.name FROM person AS p
+     * @param string $columnValue Definition of link
+     * @param int $count
+     * @param int $columnIndex
+     * @param string $fr_error
+     * @param string $dbAlias
+     */
+    public function __construct($fullLevel, $sql, $columnValue, $count, $columnIndex, $fr_error, $dbAlias = '') {
+        $this->exceptionBody = "Level: " . $fullLevel . ".sql = " . $sql . "\nRow: " . $count . " , Column: " . $columnIndex . " , Bad link: " . $columnValue . "\n";
+        $this->fr_error = $fr_error;
+        $this->dbAlias = $dbAlias;
+        $this->initRenderControl();
+
+    } // __construct
+
+    /**
+     * Initializes RenderControl Array
+     *
+     * @param    void
+     * @return    void
+     */
+    private function initRenderControl() {
+
+        $this->utils = new Utils();
+
+        /*
+         * mode:
+         * 0: no output
+         * 1: text
+         * 2: url
+         * 3: <a href=url>url</a>
+         * 4: <a href=url>Text</a>
+         *
+         *  r=render mode, u=url, t:text und/oder bild
+         *
+         *                  [r][u][t] = mode
+         */
+
+        $this->renderControl[0][0][0] = 0;
+        $this->renderControl[0][0][1] = 0;
+        $this->renderControl[0][1][0] = 3;
+        $this->renderControl[0][1][1] = 4;
+
+        $this->renderControl[1][0][0] = 0;
+        $this->renderControl[1][0][1] = 1;
+        $this->renderControl[1][1][0] = 3;
+        $this->renderControl[1][1][1] = 4;
+
+        $this->renderControl[2][0][0] = 0;
+        $this->renderControl[2][0][1] = 0;
+        $this->renderControl[2][1][0] = 0;
+        $this->renderControl[2][1][1] = 4;
+
+        $this->renderControl[3][0][0] = 0;
+        $this->renderControl[3][0][1] = 1;
+        $this->renderControl[3][1][0] = 2;
+        $this->renderControl[3][1][1] = 1;
+
+        $this->renderControl[4][0][0] = 0;
+        $this->renderControl[4][0][1] = 1;
+        $this->renderControl[4][1][0] = 2;
+        $this->renderControl[4][1][1] = 2;
+
+        $this->renderControl[5][0][0] = 0;
+        $this->renderControl[5][0][1] = 0;
+        $this->renderControl[5][1][0] = 0;
+        $this->renderControl[5][1][1] = 0;
+    } // initRenderControl()
+
+    /**
+     * Build the whole link
+     *
+     * @param    string $str :  Qualifier with params. 'report'-syntax. F.e.:  A:u:www.example.com|G:P:home.gif|t:Home"
+     * @param    string $count
+     * @param    string &$hash hash used by the link, and maybe created for the link if count=1
+     *
+     * @return    string      The complete Link
+     */
+    public function renderLink($str, $count, &$hash) {
+
+        // fill control array
+//		$this->initRenderControl();
+
+        // str="u:http://www.example.com|c:i|t:delete"
+        $parm = explode("|", $str);
+
+        // Parse all parameter, fill variables
+        foreach ($parm as $item) {
+            $arr = explode(":", $item, 2);
+            $this->parseItem($arr[0], $arr[1]);
+        }
+
+        // if there is no url or mailto definition: ~global.pageId
+        if (!$this->url && !$this->mailto) $this->url = "?" . $GLOBALS["TSFE"]->id;
+
+        $this->doCssClass();
+
+        $htmlUrl = $this->doUrl($count, $hash);
+        $htmlImage = $this->doImage();
+
+        // One space distance
+        if ($this->text) $distance = " ";
+
+        // Compose Image & Text
+        $distance = ' ';
+        $this->text = ($this->picturePosition == "l") ? $htmlImage . $distance . $this->text : $this->text . $distance . $htmlImage;
+
+        // ToolTip
+        $extraSpan = array();
+        if ($this->toolTip) {
+            $extraSpan[0] = "<span " . $this->toolTip[0] . ">" . $this->toolTip[1];
+            $extraSpan[1] = "</span>";
+        }
+
+        // Create 'fake' modes for encrypted 'mailto'
+        $prefix = "";
+        if ($this->mailto) {
+            $prefix = "1";
+            $this->url = "dummy";
+        }
+        // Create 'fake' mode for ajax delete
+        if ($this->delete) {
+            $prefix = "2";
+        }
+        // Compose URL
+        // get Render Mode via Array renderControl
+        $mode = $prefix . $this->renderControl[$this->modeRender][$this->url ? 1 : 0][$this->text ? 1 : 0];
+        // 0-4 URL, plain email
+        // 10-14 encrypted email
+        switch ($mode) {
+            // 0: No Output
+            case '0':
+            case '10':
+            case '20':
+                $this->link = "";
+                break;
+
+            // 1: 'text'
+            case '1':
+                $this->link = $extraSpan[0] . $this->text . $extraSpan[1];
+                break;
+            case '11':
+                $this->link = $extraSpan[0] . $this->encryptMailtoJS($this->mailto, $this->text, $this->linkClass, FALSE) . $extraSpan[1];
+                break;
+
+            // 2: 'url'
+            case '2':
+                $this->link = $extraSpan[0] . $this->url . $extraSpan[1];
+                break;
+            case '12':
+                $this->link = $extraSpan[0] . $this->encryptMailtoJS($this->mailto, $this->text, $this->linkClass, FALSE) . $extraSpan[1];
+                break;
+
+            // 3: <a href=url>url</a>
+            case '3':
+                $this->link = $htmlUrl . $this->url . '</a>' . $this->toolTip[1];
+                break;
+            case '13':
+                $this->link = $this->encryptMailtoJS($this->mailto, $this->mailto, $this->linkClass, TRUE);
+                break;
+
+            // 4: <a href=url>Text</a>
+            case '4':
+                $this->link = $htmlUrl . $this->text . '</a>' . $this->toolTip[1];
+                break;
+            case '14':
+                $this->link = $this->encryptMailtoJS($this->mailto, $this->text, $this->linkClass, TRUE);
+                break;
+            case '21':
+            case '22':
+            case '23':
+            case '24':
+                $this->link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'" . $count . "',hash:'" . $hash . "',forward:'" . $this->pageAlias . "'});\" " . $this->linkClass . ">" . $this->text . "</a>";
+        }
+
+        return $this->link;
+
+    } // buildHash()
+
+    /**
+     * Parse Item of link string, fill class global variables.
+     *
+     * @param    string $key :    F.e.: 'u'
+     * @param    string $value :  F.e.: 'http://www.nzz.ch'
+     * @throws SyntaxReportException
+     */
+    private function parseItem($key, $value) {
+        global $TYPO3_CONF_VARS;
+
+        $_EXTKEY = FORMREPORT;
+
+        switch ($key) {
+            // URL
+            case "u":
+                if ($this->url || $this->mailto) throw new SyntaxReportException ("Multiple URL, PAGE or MAILTO defined : '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->url = $value;
+                $this->defaultLinkClass = $TYPO3_CONF_VARS[$_EXTKEY]['css_class']['external'];
+                break;
+            case "m":
+                if ($this->url || $this->mailto) throw new SyntaxReportException ("Multiple URL, PAGE or MAILTO defined : '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->mailto = $value;
+                $this->defaultLinkClass = $TYPO3_CONF_VARS[$_EXTKEY]['css_class']['external'];
+                break;
+            case "p":
+                if ($this->url || $this->mailto) throw new SyntaxReportException ("Multiple URL, PAGE or MAILTO defined : '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->pageAlias = $value ? $value : $GLOBALS["TSFE"]->id; // If no pageid|pagealias ist defined, take current page
+                $this->url = "?" . $this->pageAlias;
+                $this->defaultLinkClass = $TYPO3_CONF_VARS[$_EXTKEY]['css_class']['internal'];
+                break;
+            // Text
+            case "t":
+                $this->text = $value;
+                break;
+            case "a":
+                $this->altText = $value;
+                break;
+            case "o":
+                $this->toolTip = $this->utils->createToolTip($value);
+                break;
+
+            // Image
+            case "P":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = $value;
+                $this->AltText = "Grafic: " . $value;
+                $this->imageTitle = $value;
+                break;
+            case "B":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . "bullet-" . ($value ? $value : "green") . '.gif';
+                $this->defaultAltText = "Bullet: " . $value;
+                $this->imageTitle = $value;
+                break;
+            case "C":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . "checked-" . ($value ? $value : "green") . '.gif';
+                $this->defaultAltText = "Checked: " . $value;
+                $this->imageTitle = $value;
+                break;
+            case "D":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'delete.gif';
+                $this->defaultAltText = "Delete";
+                $this->imageTitle = "Delete";
+                $this->delete = true;
+                // Include Extjs library
+                $this->utils->loadJSlib($this->fr_error);
+                break;
+            case "E":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'edit.gif';
+                $this->defaultAltText = "Edit";
+                $this->imageTitle = "Edit";
+                break;
+            case "H":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'help.gif';
+                $this->defaultAltText = "Help";
+                $this->imageTitle = "Help";
+                break;
+            case "I":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'info.gif';
+                $this->defaultAltText = "Information";
+                $this->imageTitle = "Information";
+                break;
+            case "N":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'new.gif';
+                $this->defaultAltText = "New";
+                $this->imageTitle = "New";
+                break;
+            case "S":
+                if ($this->image) throw new SyntaxReportException ("Multiple images defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->image = PATH_ICONS . 'show.gif';
+                $this->defaultAltText = "Details";
+                $this->imageTitle = "Details";
+                break;
+
+            // Misc
+            case "r":
+                if ($this->modeRender) throw new SyntaxReportException ("Multiple render modes defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->modeRender = $value;
+                break;
+            case "g":
+                if ($this->target) throw new SyntaxReportException ("Multiple 'target' defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->target = $value;
+                break;
+            case "c":
+                if ($this->linkClass) throw new SyntaxReportException ("Multiple linkClass defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->linkClass = $value;
+                break;
+            case "q":
+                if ($this->question) throw new SyntaxReportException ("Multiple question defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->question = $value ? $value : "Please confirm";
+                break;
+            case "e":
+                if ($this->encryption != 1) throw new SyntaxReportException ("Multiple encryption defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->encryption = $value ? $value : "1";
+                break;
+            case "h":
+                if ($this->modeHash) throw new SyntaxReportException ("Multiple modeHash defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->modeHash = TRUE;
+                break;
+            case "f":
+                if ($this->formName) throw new SyntaxReportException ("Multiple formName defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->formName = $value;
+                break;
+            case "U":    //if($this->additionalUrlParams) throw new syntaxException ( "Multiple additionalUrlParams defined: '$key'","",__FILE__,__LINE__, "Column: " . $fthis->r_error["columnIndex"], $this->fr_error);
+                //$this->additionalUrlParams = $value;
+
+                // Save url params as array!
+                $param = explode('=', $value);
+                $this->additionalUrlParams[$param[0]] = $param[1];
+                break;
+            case "i":
+                if ($this->recordId != -1) throw new SyntaxReportException ("Multiple recordId defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->recordId = $value;
+                break;
+            case "R":
+                if ($this->picturePosition != "l") throw new SyntaxReportException ("Multiple picturePosition defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->picturePosition = "r";
+                break;
+            case "T":
+                if ($this->tableName) throw new SyntaxReportException ("Multiple tableName defined: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+                $this->tableName = $value;
+                break;
+
+            // A,G
+            case "G":
+            case "A":
+                $arr = explode(":", $value, 2);
+                $this->parseItem($arr[0], $arr[1]);
+                break;
+            case "" :
+                break;
+            default:
+                throw new SyntaxReportException ("Unknown link qualifier: '$key'", "", __FILE__, __LINE__, "Column: " . $this->fr_error["columnIndex"], $this->fr_error);
+
+        }
+
+    } // splitAndAddDelimter()
+
+    /**
+     * Parse CSS Class Settings
+     *
+     * @param    void
+     *
+     * @return    void
+     */
+    private function doCssClass() {
+
+        // class: i|e|n|<custom name>
+        if ($this->linkClass != 'n') {
+            if (!$this->linkClass) $this->linkClass = $this->defaultLinkClass;
+
+            if ($this->linkClassSelector[$this->linkClass])
+                $this->linkClass = $this->linkClassSelector[$this->linkClass];
+
+            $this->linkClass = ' class="' . $this->linkClass . '"';
+        } else {
+            $this->linkClass = "";
+        }
+    } // encryptMailtoJS()
+
+    /**
+     * Build URL
+     *
+     * @param int $count Row count of current query. $count=1 means 'generate new hash values', everything else means 'take existing hash values'.
+     * @param string $hash RC for hash if count=1, else 'hash' to use.
+     * @return string
+     */
+    private function doUrl($count, &$hash) {
+        // build URL
+        $htmlUrl = "";
+
+        // If there is no encryption: handle the mailto as an ordinary URL
+        if ($this->mailto && $this->encryption == 0) {
+            $this->url = "mailto:" . $this->mailto;
+            $this->mailto = "";
+        }
+
+        if ($this->url) {
+            $param = array();
+
+            if ($this->modeHash) {
+                $param[] = $this->buildHash($count, $hash);
+            } else {
+                if ($this->formName) $param[] = URL_FORMNAME . '=' . $this->formName;
+                if ($this->recordId != -1) $param[] = URL_RECORD_ID . '=' . $this->recordId;
+                if ($this->additionalUrlParam) $param[] = $this->additionalUrlParams;
+            }
+
+            if ($param) {
+                // Append '&' or '?' depending if there is already a '?'
+                $this->url .= (strpos($this->url, '?') >= 0) ? '&' : '?';
+                // Append all additional params.
+                $this->url .= implode('&', $param);
+            }
+
+            // target
+            if ($this->target) $this->target = ' target="' . $this->target . '"';
+
+            // question
+            if ($this->question) $this->question = ' onclick="return confirm(\'' . $this->question . '\')"';
+
+            $htmlUrl = '<a href="' . $this->url . '"' . $this->linkClass . $this->target . $this->question . $this->toolTip[0] . '>';
+        }
+        return ($htmlUrl);
+    } // doCssClass()
+
+    /**
+     * Build the URL and/or Hash
+     *
+     * @return    string The complete Link
+     */
+    private function buildHash($count, &$hash) {
+
+        if ($count == 1) {
+            $hash = $this->utils->randomAlphaNumUnique();
+
+            $_SESSION[FORMREPORT][$hash]['formName'][0] = $this->formName;
+            $_SESSION[FORMREPORT][$hash]['referrer'] = $_SERVER['REQUEST_URI'];
+            // Delete Links: tablename mandatory
+            if ($this->tableName) {
+                $_SESSION[FORMREPORT][$hash]['tableName'] = $this->tableName;
+                $_SESSION[FORMREPORT][$hash]['dbAlias'] = $this->dbAlias;
+            }
+        }
+
+        $_SESSION[FORMREPORT][$hash]['idMap'][$count]['recordId'] = $this->recordId;
+        $_SESSION[FORMREPORT][$hash]['idMap'][$count]['param'] = $this->additionalUrlParams;
+
+        return (URL_HASH . '=' . $hash . '&' . URL_RECORD_ID . '=' . $count);
+    } // doUrl()
+
+    /**
+     * Create Image HTML Tag
+     * @return string $htmlImage
+     * @internal param $void
+     *
+     */
+    private function doImage() {
+        $htmlImage = "";
+
+        // Build Image
+        if ($this->image) {
+
+            if ($this->altText) $this->altText = ' alttext="' . $this->altText . '"';
+
+            if ($this->image) $this->image = ' src="' . $this->image . '"';
+
+            if ($this->imageTitle) $this->imageTitle = ' title="' . $this->imageTitle . '"';
+
+            $htmlImage = '<img' . $this->image . $this->altText . $this->imageTitle . ' />';
+        }
+
+        return ($htmlImage);
+    } // doImage()
+
+    /**
+     *  Encrypt the mailto address via JS.
+     *  Email address protected against email crawler (as long as they don't interpret JS).
+     *
+     *    <script language=javascript><!--
+     *    var mm1 = name
+     *    var mm2 = @domain.
+     *    var ... = tld
+     *    document.write("<a href=" + "mail" + "to:" + mm1 + mm2 + ... + ">" + name + "</a>")
+     *    document.write("<a href=" + "mail" + "to:" + mm1 + mm2 + ... + ">" + @domain. + "</a>")
+     *    document.write("<a href=" + "mail" + "to:" + mm1 + mm2 + ... + ">" + tld + "</a>")
+     *    //--></script>';
+     *
+     * @param    string $mailto Email address to encrypt.
+     * @param    string $text Text to wrap <a href..>, </a> around.
+     * @param    string $class Optional class definition
+     * @param    bool $href TRUE: create a '<a>',   FALSE: just encrypt or show the email, no link.
+     *
+     * @return    string The complete Link
+     */
+    private function encryptMailtoJS($mailto, $text, $class = "", $href = TRUE) {
+
+        // Prepare CSS Definition
+        if ($class) $class = str_replace('"', "'", $class);
+
+        // Split $mailto
+        $tmp = $this->splitAndAddDelimter($mailto, "@");
+        $arr = array_merge($this->splitAndAddDelimter($tmp[0], "."), $this->splitAndAddDelimter($tmp[1], "."));
+
+        $tt = "<script language=javascript><!--" . chr(10);
+        $ii = 0;
+        if ($href) {
+            $dw = 'document.write("<a href=" + "mail" + "to:"';
+            foreach ($arr as $value) {
+                // Setup JS Variables
+                $tt .= 'var mm' . $ii . '= "' . $value . '"' . chr(10);
+                // Compose $dw (documentwrite statement)
+                $dw .= ' + mm' . $ii++;
+            }
+            $dw .= ' + "' . $class . str_replace('"', '\\"', $this->toolTip[0]) . '>"';
+            $closeDw = '"</a>")';
+        } else {
+            $dw = 'document.write(';
+            $closeDw = ')';
+        }
+
+        // Wrap mailto around text
+        if ($mailto == $text) {
+            // Text is an email: wrap every single part of mailto.
+            $ii = 0;
+            foreach ($arr as $value) {
+                $tt .= $dw . " + mm" . $ii++ . ' + ' . $closeDw . chr(10);
+            }
+        } else {
+            // Single wrap text
+            $tt .= $dw . ' + "' . $text . '" + ' . $closeDw . chr(10);
+        }
+
+        $tt .= '//--></script>';
+        if ($href) $tt .= $this->toolTip[1];
+
+        return ($tt);
+    } //renderLink()
+
+    /**
+     * Split a string around the $delimiter.
+     *
+     * Append the delimiter to each part except the last one.
+     *
+     * @param $mailto
+     * @param $delimiter
+     * @return array
+     */
+    private function splitAndAddDelimter($mailto, $delimiter) {
+        $value = '';
+
+        $arr = explode($delimiter, $mailto);            // split string
+
+        foreach ($arr as $key => $value) {
+            $arr[$key] = $value . $delimiter;
+        }
+
+        if (isset($key))
+            $arr[$key] = $value;
+
+        return ($arr);
+    } // parseItem
+}
\ No newline at end of file
diff --git a/qfq/report/Log.php b/qfq/report/Log.php
new file mode 100644
index 000000000..8d2e28224
--- /dev/null
+++ b/qfq/report/Log.php
@@ -0,0 +1,214 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/*
+ * Howto
+ * 
+ * 1) Specify Logfiles (incl. path) in ext_localconf.php in $TYPO3_CONF_VARS[$_EXTKEY]['log'][<name>].
+ *    <name> means a class of 'error', 'mail', 'sql', 'browser', ...
+ * 2) Call 'log_do($class, $status, $origin, $message)' to write one entry. 
+ *    Every entry will start with: [<status>|<origin>|<date/time>|<IP>|<fe.uid>|<fe.username>|<URL>].
+ *    Specify $message string or $message array.
+ * 3) For 'sql' and 'mail' log entries, use the wrapper log_sql() or log_mail().   
+ */
+
+namespace qfq;
+
+//use qfq;
+
+class Log {
+    /**
+     * @var array
+     */
+    private $config;    // filled in __construct, Definition: tx_fr_variables->collectGlobalVariables
+
+    /**
+     * @var array   order to compare loglevel
+     */
+    private $config_level = array('E' => 0, 'W' => 1, 'I' => 2, 'D1' => 3, 'D2' => 4, 'D3' => 5);
+
+    /**
+     * @var array Emulate global variable: will be set much earlier in other functions. Will be shown in error messages.
+     */
+    private $fr_error = array();
+
+    /**
+     * Initializes class
+     *
+     * @param    array $tmp_config :    Part of array 'fr_array': 'global.'
+     *
+     * @throws \Exception
+     */
+    public function __construct(array $tmp_config) {
+        global $TYPO3_CONF_VARS;
+        $this->prepare_log_file_dir($TYPO3_CONF_VARS[EXTKEY]['log']['sql']);
+        $this->prepare_log_file_dir($TYPO3_CONF_VARS[EXTKEY]['log']['mail']);
+        $this->prepare_log_file_dir($TYPO3_CONF_VARS[EXTKEY]['log']['error']);
+        $this->prepare_log_file_dir($TYPO3_CONF_VARS[EXTKEY]['log']['browser']);
+
+        $this->config = $tmp_config;
+
+    } // __construct()
+
+    /**
+     * Check if path of logfile exists. No: create it.
+     * Check if path of logfile is writeable.
+     *
+     * @param string $filename Name of logfile
+     * @throws \Exception
+     */
+    private function prepare_log_file_dir($filename) {
+        $this->logFile = $filename;
+
+        // extract optional path component
+        $path = dirname($filename);
+
+        // If there is a path: check if directories really exist and are writeable.
+        if ($path) {
+            if (file_exists($path)) {
+                // Directory already exists: check if it is writeable.
+                if (!is_writable($path)) throw new \Exception ("Failed: directory '$path' of Logfile '$filename' is not writeable.");
+            } else {
+                // Directoy doesn't exist: create it.
+                if (!mkdir($path, 0775, true))
+                    throw new \Exception ("Failed to create directory '$path' for Logfile '$filename'");
+            }
+        }
+    }
+
+    /**
+     * Set Array fr_error: setter function to set most recent values.
+     * Will be shown in log messages.
+     *
+     * @param    array $fr_error :   uid, pid, row, column_idx, full_level
+     *
+     * @return    void
+     */
+    public function set_fr_error(array $fr_error) {
+        $this->fr_error = $fr_error;
+    } // prepare_log_file_dir()
+
+    /**
+     * log_sql: wrapper for log_do() for sql logging
+     *
+     * @param    string $origin :    Sender of the logmessage. F.e.: 'form', 'save', 'report', 'extjs', ...
+     * @param    string $msg : Only filled if there was an error
+     * @param    int $affected_rows : Number of rows inserted, updated or deleted.
+     * @param    int $new_id : last_insert_id()
+     * @param    string $sql : SQL statement fired
+     *
+     * @return    void
+     */
+    public function log_sql($origin, $msg, $affected_rows, $new_id, $sql) {
+
+        if ($msg) {
+            $status = 'E';
+        } else {
+            $status = 'I';
+            $msg = 'OK';
+        }
+
+        $text = "[$msg]";
+        $text .= "[$affected_rows]";
+        $text .= "[$new_id]";
+        $text .= "[$sql]";
+
+        $this->log_do('sql', $status, $origin, $text);
+    } // log_do()
+
+    /**
+     * log_do: format log entries, build fix part, append dynamic part, write.
+     *
+     * @param    string $class :        'error', 'mail', 'sql', 'browser'. New classes has to be defined in ext_localconf.php
+     * @param    string $status :    'E' (0:error), 'W' (1:warning), 'I' (2:information), 'D1' (3:debug verbose), 'D2' (4:debug very verbose), 'D3' (5:debug very very verbose)
+     * @param    string $orign :        Sender of the logmessage. F.e.: 'form', 'save', 'report', 'extjs', ...
+     * @param  string /array        $message:    skalar or one dimensal array with logmessage(s).
+     * @throws CodeReportException
+     * @throws SyntaxReportException
+     */
+    public function log_do($class, $status, $origin, $message) {
+        global $TYPO3_CONF_VARS;
+        $tmp = '';
+
+        // Check if loglevel should be respected and if 'yes' if loglevel is greater than this message: break
+        if (($class == 'error' || $class == 'browser') && $this->config_level[$status] > $TYPO3_CONF_VARS[EXTKEY]['log']['level'])
+            return;
+
+        // Get filename
+        $filename = $TYPO3_CONF_VARS[EXTKEY]['log'][$class];
+        if (!$filename)
+            throw new SyntaxReportException ("Missing logfile definition: Undefind TYPO3_CONF_VARS[" . EXTKEY . "]['log']['$class']", "", __FILE__, __LINE__);
+
+        if ($this->fr_error['pid']) $tmp = '|' . $this->fr_error['pid'];
+        if ($this->fr_error['uid']) $tmp .= '|' . $this->fr_error['uid'];
+        if ($this->fr_error['full_level']) $tmp .= '|' . $this->fr_error['full_level'];
+
+        // F.e.: [I|Form|2012.01.31-19:59:33|IP|fe.uid|fe.username|URL]
+        $text = '[' . $status . '|' . $origin . '|' . date('Y.m.d-H:i:s') . '|' . $this->config["REMOTE_ADDR"] . '|' . $this->config["fe_user_uid"] . '|' . $this->config["fe_user"] . $tmp . '|' . $this->config["url"] . ']';
+
+        // If 'message' is an array, wrap every element (but not the last) with '[', ']'.
+        if ($message && is_array($message)) {
+            $last = array_pop($message);
+            if ($last && $message) {
+                $text .= '[' . implode('][', $message) . ']';
+            }
+            $message = $last;
+        }
+        $text .= $message;
+
+        // Write whole entry
+        $this->log_write($filename, $text);
+    } // log_sql()
+
+    /**
+     *    Logs every email (failed or successfull)
+     *
+     * @param    string $orign :        Sender of the logmessage. F.e.: 'form', 'save', 'report', 'extjs', ...
+     * @param    string $status :    'E' (0:error), 'W' (1:warning), 'I' (2:information), 'D1' (3:debug verbose), 'D2' (4:debug very verbose), 'D3' (5:debug very very verbose)
+     * @param    string $msg :        Message
+     * @param    array $mailarr :    Details of the mail
+     *
+     * $mailarr['sender']      Absender
+     * $mailarr['receiver']    Empfaenger, mehrere mit Komma getrennt
+     * $mailarr['subject']     Betreff
+     * $mailarr['body']        Mailinhalt
+     * $mailarr['src']         level oder form_element.id um herauszufinden wer die Mail gesendet hat
+     *
+     * @param    string $mailarr : data to log
+     * @return    The content that is displayed on the website
+     */
+
+    public function log_mail($origin, $status, $msg, $mailarr) {
+
+        $text = '[' . $msg . "]";
+        $text .= '[' . $mailarr["sender"] . ']';
+        $text .= '[' . $mailarr["receiver"] . ']';
+        $text .= '[' . $mailarr["subject"] . ']';
+        $text .= '[' . $mailarr["body"] . ']';
+
+        $this->log_do('mail', $status, $origin, $text);
+
+    } // log_mail()
+
+}
diff --git a/qfq/report/Report.php b/qfq/report/Report.php
new file mode 100644
index 000000000..2986fd4fe
--- /dev/null
+++ b/qfq/report/Report.php
@@ -0,0 +1,1081 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+
+require_once(__DIR__ . '/Define.php');
+require_once(__DIR__ . '/Utils.php');
+require_once(__DIR__ . '/Variables.php');
+require_once(__DIR__ . '/Error.php');
+require_once(__DIR__ . '/Db.php');
+require_once(__DIR__ . '/Link.php');
+require_once(__DIR__ . '/Sendmail.php');
+
+
+class cObj {
+    public $data = array();
+}
+
+/**
+ * Plugin 'FormReport' for the 'formreport' extension.
+ *
+ * @author    Glowbase GmbH <support@glowbase.com>
+ * @package    TYPO3
+ * @subpackage    tx_formreport
+ */
+class Report {
+//    var $prefixId = 'tx_formreport_pi1';        // Same as class name
+//    var $scriptRelPath = 'pi1/class.tx_formreport_pi1.php';    // Path to this script relative to the extension dir.
+//    var $pi_checkCHash = true;
+
+    /**
+     * @var string
+     */
+    private $dbAlias = '';
+    /**
+     * @var Log
+     */
+    private $log = null;
+
+    // frArray[10.50.5.sql][select ...]
+    private $frArray = array();
+    // $indexArray[10][50][5]   one entry per 'sql' statement
+    private $indexArray = array();
+    // TODO to explain
+//	private $resultArray = array();
+    private $levelCount = 0;
+    //private $counter = 0;
+    /**
+     * @var Variables
+     */
+    private $variables = null;
+//    private $saveFile;
+    //class with util functions
+    /**
+     * @var Utils
+     */
+    private $utils = null;
+
+    //TODO: wo wird die initialisiert? Im Original suchen.
+    /**
+     * @var Db
+     */
+    private $db = null;
+    /**
+     * @var Sendmail
+     */
+    private $sendmail = null;
+
+    private $page_control = array();
+
+    // Emulate global variable: will be set much earlier in other functions. Will be shown in error messages.
+    private $fr_error = array('uid' => '', 'pid' => '', 'row' => '', 'debug_level' => '0', 'full_level' => '');
+
+    //TODO:here: FAKE. Known inside Typo3 and should be transfered to Report somehow
+    private $cObj;
+
+    /**
+     * __construct
+     *
+     */
+    public function __construct() {
+
+        //TODO: Fake
+        $this->cObj = new cObj();
+        $this->cObj->data['uid'] = 1;
+
+        $this->page_control["msgbox"]["pagec"] = "Please confirm!";
+
+        $this->page_control["hash"]["paged"] = "h";
+        $this->page_control["hash"]["pagee"] = "h";
+        $this->page_control["hash"]["pagen"] = "h";
+
+        $this->page_control["icon"]["paged"] = "D";
+        $this->page_control["icon"]["pagee"] = "E";
+        $this->page_control["icon"]["pageh"] = "H";
+        $this->page_control["icon"]["pagei"] = "I";
+        $this->page_control["icon"]["pagen"] = "N";
+        $this->page_control["icon"]["pages"] = "S";
+
+    } // __construct
+
+    /**
+     * @param $content
+     * @param $conf
+     * @return string Rendered code of the queries
+     * @throws userException
+     */
+    public function process($bodytext) {
+
+        try {
+            $this->utils = new Utils();
+
+            $this->variables = new Variables($this->cObj->data["uid"]);
+
+            // Sanitize function for POST and GET Parameters.
+            // Merged URL-Parameter (key1, id etc...) in resultArray.
+            $this->variables->resultArray = array_merge($this->variables->resultArray, array("url." => $this->utils->sanitize()), array("global." => $this->variables->collectGlobalVariables()));
+
+            // Set static values, which won't change during this run.
+//            $this->fr_error["pid"] = $this->variables->resultArray['global.']['page_id'];
+            $this->fr_error["pid"] = $this->variables->get('resultArray', 'global.', 'page_id');
+            $this->fr_error["uid"] = $this->cObj->data["uid"];
+            $this->fr_error["debug_level"] = 0;
+
+            // Create Logclass.
+//            $this->log = new Log($this->variables->resultArray['global.']);
+            $this->log = new Log($this->variables->get('resultArray', 'global.'));
+
+            // Create DB Class. Take care to prepare a fr_log instance.
+            $this->db = new Db($this->log);
+
+            // Create sendmail Class. Take care to prepare a fr_log instance.
+            $this->sendmail = new Sendmail($this->log);
+
+            // Setter function to emulate global variables.
+            $this->db->set_fr_error($this->fr_error);
+            $this->log->set_fr_error($this->fr_error);
+
+            // Iteration over Bodytext
+            $ttLineArray = explode("\n", $bodytext);
+
+            foreach ($ttLineArray as $index => $line) {
+                // Fill $frArray, $indexArray, $resultArray
+                $this->parseFRLine($line);
+            }
+            // Sort array
+            $this->sortIndexArray($this->indexArray, $this->generateSortArg());
+
+            // Report
+            $content = $this->triggerReport();
+
+// 		} catch ( codeException $e ) {
+// 			$content = $e->errorMessage();
+// 		} catch ( syntaxException $e ) {
+// 			$content = $e->errorMessage();
+// 		} catch ( sqlException $e ) {
+// 			$content = $e->errorMessage();
+        } catch (\Exception $e) {
+            $content = $e->getMessage();
+        }
+
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+//            debug($this->frArray);
+//            debug($_SESSION);
+        }
+
+        return $content;
+    } // main()
+
+    /**
+     * Split line in level, command, content and fill 'frArray', 'levelCount', 'indexArray'
+     * Example: 10.50.5.sql = select * from person
+     *
+     * @param    string $ttLine : line to split in level, command, content
+     * @return    void
+     */
+    private function parseFRLine($ttLine) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'parseFRLine'));
+        }
+
+
+        // 10.50.5.sql = select ...
+        $arr = explode("=", trim($ttLine), 2);
+
+        // 10.50.5.sql
+        $key = strtolower(trim($arr[0]));
+
+        // comment ?
+        if (substr($key, 0, 1) == "#") return;
+
+        // select ...
+        $value = trim($arr[1]);
+
+        // 10.50.5.sql
+        $arrKey = explode('.', $key);
+
+        // frCmd = "sql"
+        $frCmd = $arrKey[count($arrKey) - 1];
+
+        // remove last item (cmd)
+        unset($arrKey[count($arrKey) - 1]);
+
+        // save elements only if there is a level specified
+        if (count($arrKey)) {
+            // level = "10.50.5"
+            $level = implode(".", $arrKey);
+
+            // fill Array
+            $this->setLine($level, $frCmd, $value);
+        }
+    } // form2hash()
+
+
+    private function setLine($level, $frCmd, $value) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'setLine'));
+        }
+
+        // store complete line reformatted in frArray
+        $this->frArray[$level . "." . $frCmd] = $value;
+
+        // per sql command
+        //pro sql cmd wir der Indexarray abgefüllt. Dieser wird später verwendet um auf den $frArray zuzugreifen
+        //if(preg_match("/^sql/i", $frCmd) == 1){
+        if ($frCmd == "sql" || $frCmd == "form") {
+            // Remember max level
+            $this->levelCount = max(substr_count($level, '.') + 1, $this->levelCount);
+            // $indexArray[10][50][5]
+            $this->indexArray[] = explode(".", $level);
+        }
+    }    // triggerReport()
+
+    /**
+     * Sorts the associative array.
+     *
+     * @param    Array $ary : The unsorted Level Array
+     * @param    String $clause : the sort argument 0 ASC, 1 ASC... according to the number of columns
+     * @return    The content that is displayed on the website
+     */
+    private function sortIndexArray(&$ary, $clause, $ascending = true) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'sortIndexArray'));
+        }
+
+        $clause = str_ireplace('order by', '', $clause);
+        $clause = preg_replace('/\s+/', ' ', $clause);
+        $keys = explode(',', $clause);
+        $dirMap = array('desc' => 1, 'asc' => -1);
+        $def = $ascending ? -1 : 1;
+
+        $keyAry = array();
+        $dirAry = array();
+        foreach ($keys as $key) {
+            $key = explode(' ', trim($key));
+            $keyAry[] = trim($key[0]);
+            if (isset($key[1])) {
+                $dir = strtolower(trim($key[1]));
+                $dirAry[] = $dirMap[$dir] ? $dirMap[$dir] : $def;
+            } else {
+                $dirAry[] = $def;
+            }
+        }
+        $fnBody = '';
+        for ($i = count($keyAry) - 1; $i >= 0; $i--) {
+            $k = $keyAry[$i];
+            $t = $dirAry[$i];
+            $f = -1 * $t;
+            $aStr = '$a[\'' . $k . '\']';
+            $bStr = '$b[\'' . $k . '\']';
+
+            if (strpos($k, '(') !== false) {
+                $aStr = '$a->' . $k;
+                $bStr = '$b->' . $k;
+            }
+
+            if ($fnBody == '') {
+                $fnBody .= "if({$aStr} == {$bStr}) { return 0; }\n";
+                $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n";
+            } else {
+                $fnBody = "if({$aStr} == {$bStr}) {\n" . $fnBody;
+                $fnBody .= "}\n";
+                $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n";
+            }
+        }
+
+        if ($fnBody) {
+            $sortFn = create_function('$a,$b', $fnBody);
+            usort($ary, $sortFn);
+        }
+    } // collectRow()
+
+
+    private function generateSortArg() {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'generateSortArg'));
+        }
+
+        $sortArg = "";
+
+        for ($i = 0; $i < $this->levelCount; $i++) {
+            $sortArg = $sortArg . $i . " ASC, ";
+        }
+        $sortArg = substr($sortArg, 0, strlen($sortArg) - 2);
+        return $sortArg;
+    } // composeLinkPart()
+
+    /**
+     * 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
+     * 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
+     *
+     * @param int $cur_level Which level it will call [10] = level 1, [10.10] = level 2 ...
+     * @param string $super_level_array The Value-Array of the indexarray [0=>10, 1=>50]* @param int $counter The outer numeric Arraykey of indexarray
+     * @return string                       The content that is displayed on the website
+     * @throws codeException
+     * @throws SqlReportException
+     * @throws SyntaxReportException
+     * @throws UserReportException
+     */
+    private function triggerReport($cur_level = 1, $super_level_array = "", $counter = 0) {
+
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'triggerReport'));
+        }
+
+        $lineDebug = 0;
+        $content = "";
+        $rowTotal = 0;
+
+        // CurrentLevel "10.10.50"
+        $full_level = implode(".", $this->indexArray[$counter]);
+        // Superlevel "10.10"
+        $full_super_level = implode(".", $super_level_array);
+
+        //condition1: indexArray
+        //condition2: full_level == Superlevel (but at the length of the Superlevel)
+        while ($counter < count($this->indexArray) && $full_super_level == substr($full_level, 0, strlen($full_super_level))) {
+
+            //True: The cur_level is a subquery -> continue
+            if ($cur_level != count($this->indexArray[$counter])) {
+                $full_level = implode(".", $this->indexArray[++$counter]);
+                continue;
+            }
+
+            // Set dbAlias if one is specified. Else keep the parent one.
+            $this->dbAlias = $this->getValueParentDefault("db", $full_super_level, $full_level, $cur_level, DB);
+
+            // Set debug, if one is specified else keep the parent one.
+            $lineDebug = $this->getValueParentDefault("debug", $full_super_level, $full_level, $cur_level, 0);
+            $this->fr_error["debug_level"] = max($lineDebug, $this->fr_error["debug_level"]); // collect the biggest debuglevel >> debug $_SESSION at the end.
+
+            // Prepare Error reporting
+            $this->fr_error["row"] = $full_level . ".sql = " . $this->frArray[$full_level . ".sql"];
+            $this->fr_error["full_level"] = $full_level . ".sql";
+
+            // Setter function to emulate global variables
+            $this->db->set_fr_error($this->fr_error);
+            $this->log->set_fr_error($this->fr_error);
+
+            // Debug
+            if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+                // T3 function: debug()
+//                debug(array('full_level' => $full_level));
+            }
+
+            // Do form ?
+            if (isset($this->frArray[$full_level . "." . "form"])) {
+                // Disable Typo3 Cache for this page to avoid problems with the session array / ajax
+                //TODO: Is this still necessary?
+//                $GLOBALS["TSFE"]->set_no_cache();
+
+                // Trigger 10.form, 20.form...
+                // Use the form passed by URL via hash
+                if ($this->frArray[$full_level . "." . "form"] == URL_FORM) {
+                    $hash = $this->variables->resultArray["url."][URL_HASH];
+                    $formName = $_SESSION[FORMREPORT][$hash]['formName'][0];
+                } else {
+                    // Use the form configured in tt_content
+                    // Replace possible variables for formRecordId
+                    $formRecordId = isset($this->frArray[$full_level . ".formrecordid"]) ? $this->variables->doVariables($this->frArray[$full_level . ".formrecordid"]) : 0;
+                    $formName = $this->frArray[$full_level . ".form"];
+
+                    // Fill $_SESSION
+                    // If a hash has already been set by url, use that one, otherwise generate a new hash array
+                    if (isset($this->variables->resultArray["url."][URL_HASH])) {
+                        $hash = $this->variables->resultArray["url."][URL_HASH];
+
+                        // Store all allowed forms in an array
+                        $allowedForms = array();
+
+                        // If there are already forms in the hash array, keep them
+                        if (is_array($_SESSION[FORMREPORT][$hash]['formName'])) {
+                            $allowedForms = $_SESSION[FORMREPORT][$hash]['formName'];
+                        }
+                        array_push($allowedForms, $formName);
+
+                        $_SESSION[FORMREPORT][$hash]['formName'] = $allowedForms;
+
+                    } else {
+                        // Otherwise configure new session array
+                        $hash = $this->form2hash($formName, $formRecordId);
+                    }
+                }
+
+                $full_level = implode(".", $this->indexArray[++$counter]);
+                continue;
+            } // Do form
+
+            // Prepare SQL: replace variables.
+            $sql = $this->variables->doVariables($this->frArray[$full_level . ".sql"]);
+
+            // DEBUG
+            if ($this->fr_error["debug_level"] >= DEBUG_SQL) {
+                // T3 function: debug()
+//                debug(array("native" => $full_level . ".sql = " . $this->frArray[$full_level . ".sql"], "replaced" => $full_level . ".sql = " . $sql));
+            }
+            if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+                // T3 function: debug()
+//                debug($this->variables->resultArray);
+            }
+
+            //Execute SQL. All errors have been already catched.
+            unset($result);
+            $this->db->doQueryKeys($this->dbAlias, $sql, $result, $keys, EXPECT_GE_0, MERGE_NONE, MYSQL_NUM);
+
+            // If an array is returned, $sql was a query, otherwise an 'insert', 'update', 'delete', ...
+            // Query: total nummber of rows
+            // insert: last_insert_id
+            // delete, update: number of affected rows
+            $rowTotal = is_array($result) ? count($result) : $result;
+
+            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
+
+            // HEAD: If there is at least one record, do 'head'.
+            if ($rowTotal > 0)
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "head"]);
+
+            // Prepare row alteration
+            $arrRbgd = explode("|", $this->frArray[$full_level . "." . "rbgd"]);
+
+            //---------------------------------
+            // Process each row of resultset
+            $columnValueSeperator = "";
+            $rowIndex = 0;
+            foreach ($result as $row) {
+                // record number counter
+                $this->variables->resultArray[$full_level . ".line."]["count"] = ++$rowIndex;
+
+                // replace ~count and ~total in result, if the variables specify their own full_level. This can't be replaced before firing the query.
+                for ($ii = 0; $ii < count($row); $ii++) {
+                    $row[$ii] = str_replace("~" . $full_level . ".line.count", $rowIndex, $row[$ii]);
+                    $row[$ii] = str_replace("~" . $full_level . ".line.total", $rowTotal, $row[$ii]);
+                }
+
+                // Create Assoc Array of result
+                unset($rowAssoc);
+                for ($i = 0; $i < count($row); $i++) {
+                    $rowAssoc[$keys[$i]] = $row[$i];
+                }
+
+                //Fills the Results in the resultArray to substitute variables
+// array wird neu einzeln gefuellt				$this->tx_fr_variables_pi1->resultArray[$full_level . "."] = $rowAssoc;
+
+                // SEP set seperator (empty on first run)
+                $content .= $columnValueSeperator;
+                $columnValueSeperator = $this->variables->doVariables($this->frArray[$full_level . "." . "sep"]);
+
+                // RBGD: gerade- ungerade- Zeilen/Rows
+                $content .= str_replace("rbgd", $arrRbgd[$rowIndex % 2], $this->frArray[$full_level . "." . "rbeg"]);
+
+                //-----------------------------
+                // COLUMNS: Collect all columns
+                $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);
+
+                // REND
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "rend"]);
+
+                // Trigger subqueries of this level
+                $content .= $this->triggerReport($cur_level + 1, $this->indexArray[$counter], $counter + 1);
+
+                // RENR
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "renr"]);
+
+                // this is necessary: queries 2,3,.. should not be replaced with content of resultArray but with the most recent values. So delete the entry in resultArray.
+                unset($this->variables->resultArray[$full_level . ".line."]["total"]);
+                unset($this->variables->resultArray[$full_level . ".line."]["count"]);
+            }
+
+            // set last valid counts again: should be available for later queries
+            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
+            $this->variables->resultArray[$full_level . ".line."]["count"] = $rowIndex;
+
+            //Print althead or tail
+            if ($rowTotal > 0) {
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "tail"]);
+            } else {
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "althead"]);
+            }
+
+            $full_level = implode(".", $this->indexArray[++$counter]);
+        }
+
+        return $content;
+    } // doPage()
+
+    /**
+     * Determine value:
+     * 1) if one specified in line: take it
+     * 2) if one specified in upper level: take it
+     * 3) if none above take default
+     * Set value on $full_level
+     *
+     * @param    string $level_key - 'db' or 'debug'
+     * @param    string $full_super_level - f.e.: 10.10.
+     * @param    string $full_level - f.e.: 10.10.10.
+     * @param    string $cur_level - f.e.: 2
+     * @param    string $default - f.e.: 0
+     * @return   string  The calculated value.
+     */
+    private function getValueParentDefault($level_key, $full_super_level, $full_level, $cur_level, $default) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'getValueParentDefault'));
+        }
+
+        if ($this->frArray[$full_level . "." . $level_key]) {
+            $value = $this->frArray[$full_level . "." . $level_key];
+        } else {
+            if ($cur_level == 1) {
+                $value = $default;
+            } else {
+                $value = $this->variables->resultArray[$full_super_level . ".line."][$level_key];
+            }
+        }
+        $this->variables->resultArray[$full_level . ".line."][$level_key] = $value;
+
+        return ($value);
+    } // doFixColPosPage()
+
+    /**
+     * Prepare Session Array with Hash Entry: Only for form
+     *
+     * @param $formName
+     * @param $formRecordId
+     * @return string
+     */
+    private function form2hash($formName, $formRecordId) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'form2hash'));
+        }
+        $hash = $this->utils->randomAlphaNumUnique();
+
+        $_SESSION[FORMREPORT][$hash]['formName'][0] = $formName;
+        $_SESSION[FORMREPORT][$hash]['referrer'] = $_SERVER['REQUEST_URI'];
+
+        if (is_numeric($formRecordId) && $formRecordId >= 0) {
+            $_SESSION[FORMREPORT][$hash]['idMap'][1]['recordId'] = $formRecordId;
+            $_SESSION[FORMREPORT][$hash]['idMap'][1]['param'] = "";
+        }
+
+        return ($hash);
+    } // renderColumn()
+
+    /**
+     * Steps through 'row' and collects all columns
+     *
+     * @param array $row Recent row fetch from sql resultset.
+     * @param array $keys List of all columnnames
+     * @param string $full_level Recent position to work on.
+     * @param string $rowIndex Index of recent row in resultset.
+     * @return string               Collected content of all printable columns
+     * @throws SyntaxReportException
+     */
+    private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
+        $content = "";
+
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'collectRow'));
+        }
+
+        for ($ii = 0; $ii < count($keys); $ii++) {
+
+            $this->fr_error["columnIndex"] = $ii;  // Debugging Information
+            // Setter Function to emulate global variables.
+            $this->db->set_fr_error($this->fr_error);
+            $this->log->set_fr_error($this->fr_error);
+
+            $tmp = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $show);
+            if ($show) {
+                //prints
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "fbeg"]);
+                $content .= $tmp;
+                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . "fend"]);
+            }
+        }
+        return ($content);
+    } // getValueParentDefault()
+
+
+    /**
+     * The main method of the PlugIn
+     *
+     * @param    string $content : The PlugIn content
+     * @param    array $conf : The PlugIn configuration
+     * @return   string  The content that is displayed on the website
+     */
+    //Liefert für ~1.1.name  -> [1.1][name] zurück
+    /**
+     * Renders column depending of column name (if name is a reserved column name)
+     *
+     * @param    string $columnIndex :    column index of recent row
+     * @param    string $columnName :    columnname
+     * @param    string $columnValue :    content of column
+     * @param    string $full_level :    position to be processed
+     * @param    string $rowIndex :        index of row of resultset to be processed
+     * @param    bool $show :            RC: 'TRUE' content will be printed on screen
+     *
+     * @return    string        rendered column
+     *
+     */
+    private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$show) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'renderColumn'));
+        }
+#debug(array('columnIndex'=>$columnIndex, 'columnName'=>$columnName, 'columnValue'=>$columnValue,'full_level'=>$full_level,"rowIndex"=>$rowIndex));
+        $show = TRUE;
+        if (substr($columnName, 0, 1) == "_") {
+            $show = FALSE;
+            $columnName = substr($columnName, 1);
+        }
+
+        $content = "";
+        switch ($columnName) {
+            case "link":
+                $link = new Link($full_level, $this->frArray[$full_level . ".sql"], $columnValue, $rowIndex, $columnIndex, $this->fr_error);
+                $content .= $link->renderLink($columnValue, $rowIndex, $this->frArray[$full_level . "." . "hash." . "$columnIndex"]);
+#				unset $link;
+                break;
+            case "exec":
+                $content .= $this->myExec($columnValue);
+                break;
+
+            case "Page":
+            case "Pagec":
+            case "Paged":
+            case "Pagee":
+            case "Pageh":
+            case "Pagei":
+            case "Pagen":
+            case "Pages":
+                $linkValue = $this->doFixColPosPage($columnName, $columnValue);
+
+                $link = new Link($full_level, $this->frArray[$full_level . ".sql"], $linkValue, $rowIndex, $columnIndex, $this->fr_error, $this->dbAlias);
+                $content .= $link->renderLink($linkValue, $rowIndex, $this->frArray[$full_level . "." . "hash." . "$columnIndex"]);
+                break;
+
+            case "page":
+            case "pagec":
+            case "paged":
+            case "pagee":
+            case "pageh":
+            case "pagei":
+            case "pagen":
+            case "pages":
+#debug($columnValue);
+                $linkValue = $this->doPage($columnName, $columnValue);
+// debug($linkValue);
+
+                $link = new Link($full_level, $this->frArray[$full_level . ".sql"], $linkValue, $rowIndex, $columnIndex, $this->fr_error, $this->dbAlias);
+                $content .= $link->renderLink($linkValue, $rowIndex, $this->frArray[$full_level . "." . "hash." . "$columnIndex"]);
+                break;
+
+            case "img":
+                // "<path to image>|[alttext]|[text behind]" renders to: <img src="<path to image>" alt="[alttext]">[text behind]
+                if (empty($columnValue)) break;
+                $tmp = explode("|", $columnValue, 3);
+                if ($tmp[0] == "") break;
+                $content .= '<img src="' . $tmp[0] . '" alt="' . $tmp[1] . '">' . $tmp[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>
+                $tmp = explode("|", $columnValue, 2);
+                if ($tmp[0] == "") break;
+
+                $t1 = explode("@", $tmp[0], 2);
+                $content .= "<script language=javascript><!--" . chr(10);
+                if (empty($tmp[1])) $tmp[1] = $tmp[0];
+
+                $content .= 'var contact = "' . substr($tmp[1], 0, 2) . '"' . chr(10);
+                $content .= 'var contact1 = "' . substr($tmp[1], 2) . '"' . chr(10);
+                $content .= 'var email = "' . $t1[0] . '"' . chr(10);
+                $content .= 'var emailHost = "' . $t1[1] . '"' . chr(10);
+
+                $content .= 'document.write("<a href=" + "mail" + "to:" + email + "@" + emailHost+ ">" + contact + "</a>")' . chr(10);
+                $content .= 'document.write("<a href=" + "mail" + "to:" + email + "@" + emailHost+ ">" + contact1 + "</a>")' . chr(10);
+                $content .= '//--></script>';
+                break;
+
+            case "sendmail":
+                // 'Absender|Empfaenger, mehrere mit Komma getrennt|Betreff|Mailinhalt'
+                $tmp = explode("|", $columnValue, 4);
+
+                $mail['receiver'] = $tmp[0];
+                $mail['sender'] = $tmp[1];
+                $mail['subject'] = $tmp[2];
+                $mail['body'] = $tmp[3];
+
+                $content = $this->sendmail->sendmail($mail);
+                break;
+
+            case "vertical":
+                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and heigth needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
+                $arr = explode("|", $columnValue, 5);
+
+                # angle
+                $angle = $arr[1] ? $arr[1] : 270;
+
+                # width
+                $width = $arr[2] ? $arr[2] : "1em";
+                $tmp = "width:$width; ";
+
+                # height
+                if ($arr[3])
+                    $tmp .= "height:" . $arr[3] . "; ";
+
+                # tag
+                if ($arr[4]) {
+                    $tag = explode(" ", trim($arr[4]), 2);
+                    $tag_open = "<" . $arr[4] . " ";
+                    $tag_close = "</" . $tag[0] . ">";
+                } else {
+                    $tag_open = "<div ";
+                    $tag_close = "</div>";
+                }
+
+                # http://scottgale.com/blog/css-vertical-text/2010/03/01/
+                #$style = "writing-mode:tb-rl; -webkit-transform:rotate(270deg); -moz-transform:rotate(270deg); -o-transform: rotate(270deg); white-space:nowrap; width:1em;  display: block;";
+                $style = "width:1em;  filter: flipv fliph; transform: rotate(" . $angle . "deg) translate(-10em,0); transform-origin: 0 0; writing-mode:tb-rl; -webkit-transform:rotate(" . $angle . "deg); -moz-transform:rotate(" . $angle . "deg); -o-transform: rotate(" . $angle . "deg); white-space:nowrap; display: block;";
+
+                #$style = "line-height: 1.5em; background:#eee; display: block; white-space: nowrap; padding-left: 3px; writing-mode: tb-rl; filter: flipv fliph; transform: rotate(270deg) translate(-10em,0); transform-origin: 0 0; -moz-transform: rotate(270deg) translate(-10em,0); -moz-transform-origin: 0 0; -webkit-transform: rotate(270deg) translate(-10em,0); -webkit-transform-origin: 0 0;";
+                $content = $tag_open . 'style="' . $style . '">' . $arr[0] . $tag_close;
+
+
+                break;
+
+            case "F":
+                $newColumnName = "";
+                $newFinalColumnName = "";
+                $remain = array();
+                $striptags = FALSE;
+                $tag = "";
+
+                # 'Q:mailto|Z|V:mail_first|support@example.com'
+                $arr = explode("|", $columnValue);
+                foreach ($arr as $value) {
+                    $kv = explode(":", $value);
+                    switch ($kv[0]) {
+                        case "Q":
+                            $newColumnName = $kv[1];
+                            #if(!$newColumnName) throw new syntaxException ( "Missing a 'reserved column name' for parameter 'Q' in column 'F': $columnValue","",__FILE__,__LINE__,$this->fr_error);
+                            if ($newColumnName == 'F') throw new SyntaxReportException ("Not allowed: 'F' as 'reserved column name' for parameter 'Q': $columnValue", "", __FILE__, __LINE__, $this->fr_error);
+                            break;
+                        case "Z":
+                            $show = FALSE;
+                            break;
+                        case "T":
+                            $striptags = TRUE;
+                            break;
+                        case "X":
+                            $tag = $kv[1];
+                            if (!$tag) throw new SyntaxReportException ("Missing the 'tag' parameter for 'X'):  $columnValue", "", __FILE__, __LINE__, $this->fr_error);
+                            break;
+                        case "V":
+                            $newFinalColumnName = $kv[1];
+                            if (!$newFinalColumnName) throw new SyntaxReportException ("Missing the 'name' parameter for 'V':  $columnValue", "", __FILE__, __LINE__, $this->fr_error);
+                            break;
+                        case 'F':
+                            throw new SyntaxReportException ("Qualifier 'F' is not allowed inside of a column with column name 'F':  $columnValue", "", __FILE__, __LINE__, $this->fr_error);
+                            break;
+                        # Save every non 'F' qualifier for later usage
+                        default:
+                            $remain[] = $value;
+                            break;
+                    }
+                }
+                # Check for needed action
+                #			if(!$newColumnName) throw new syntaxException ( "Missing parameter 'Q' in column 'F': $columnValue","",__FILE__,__LINE__,$this->fr_error);
+                if ($newColumnName)
+                    $columnName = $newColumnName;
+                else
+                    $columnName = "no column name defined";
+
+                # reconstruct remaining parameters
+                $arr = implode("|", $remain);
+                # render
+                $content = $this->renderColumn($columnIndex, $columnName, $arr, $full_level, $rowIndex, $dummy);
+
+                if ($newFinalColumnName)
+                    $columnName = $newFinalColumnName;
+
+                # striptags
+                if ($striptags) $content = strip_tags($content);
+                # tag
+                if ($tag) {
+                    $arr = explode(" ", $tag);
+                    $content = "<" . $tag . ">" . $content . "</" . $arr[0] . ">";
+                }
+                break;
+
+            default :
+                $content .= $columnValue;
+                break;
+        }
+        $this->variables->resultArray[$full_level . "."][$columnName] = $content;
+
+        return $content;
+    } // getResultArrayIndex()
+
+    /**
+     * The main method of the PlugIn
+     *
+     * @param    string $content : The PlugIn content
+     * @param    array $conf : The PlugIn configuration
+     * @return    string The content that is displayed on the website
+     */
+    //Checkt ob der Beginn von Array2 gleich ist wie Array1
+    // gibt true/false zurück
+    /**
+     * Executes the Command in $cmd
+     * RC: if RC==0 Returns Output, else 'RC - Output'
+     *
+     * @param    string $cmd : contains the Comand
+     * @return   string  The content that is displayed on the website
+     */
+    private function myExec($cmd) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'myExec'));
+        }
+
+        exec($cmd, $arr, $rc);
+
+        $output = implode('<BR>', $arr);
+        if ($rc != 0)
+            $output = $rc . " - " . $output;
+
+        return ($output);
+    } // compareArraystart()
+
+    /**
+     * The main method of the PlugIn
+     *
+     * @param    string $content : The PlugIn content
+     * @param    array $conf : The PlugIn configuration
+     * @return   string The content that is displayed on the website
+     */
+    //Check ob arr1 nur 1 Feld mehr hat als arr2
+    /**
+     * Renders PageX: convert position content to token content. Respect default values depending on PageX
+     *
+     * @param    string $columnName
+     * @param    string $columnValue
+     * @return string rendered link
+     *
+     * $columnValue:
+     * -------------
+     * [<page id|alias>[&param=value&...]] | [record id] | [text] | [tooltip] | [msgbox] | [class] | [target] | [render mode] | [create hash]
+     *
+     * param[0]: <page id|alias>[&param=value&..
+     * param[1]: record id
+     * param[2]: text
+     * param[3]: tooltip
+     * param[4]: msgbox
+     * param[5]: class
+     * param[6]: target
+     * param[7]: render mode
+     * param[8]: create hash
+     * @throws SyntaxReportException
+     */
+    private function doFixColPosPage($columnName, $columnValue) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'doFixColPosPage'));
+        }
+
+        $link = "";
+
+        // Split definition
+        $param = explode('|', $columnValue);
+        if (count($param) > 9) throw new SyntaxReportException ("Too many parameter (max=9): $columnValue", "", __FILE__, __LINE__, $this->fr_error);
+
+        // make first 'P' lowercase
+        $columnName = 'p' . substr($columnName, 1);
+
+        // -- Page --
+        // Split PageId|PageAlias and  URL Params
+        $tmparr = explode('&', $param[0], 2);
+
+        $link .= $this->composeLinkPart('p', $tmparr[0]);            // -- PageID --
+        $link .= $this->composeLinkPart('U', $tmparr[1]);            // -- URL Params --
+        $link .= $this->composeLinkPart('i', $param[1]);                // -- record id --
+        $link .= $this->composeLinkPart('t', $param[2]);                // -- Text --
+        $link .= $this->composeLinkPart('o', $param[3]);                // -- tooltip --
+        $link .= $this->composeLinkPart('q', $param[4], $this->page_control["msgbox"][$columnName]);                // -- msgbox
+        $link .= $this->composeLinkPart('c', $param[5]);                // -- class --
+        $link .= $this->composeLinkPart('g', $param[6]);                // -- target --
+        $link .= $this->composeLinkPart('r', $param[7]);                // -- render mode --
+
+        if (!$param[8]) $param[8] = $this->page_control["hash"][$columnName]; // if no hash behaviour defined, use default
+        if ($param[8] == "h") $link .= "h|";
+
+        if ($this->page_control["icon"][$columnName]) $link .= $this->page_control["icon"][$columnName] . "|";
+
+        return ($link);
+    } // compareArraylength()
+
+    /**
+     * If there is a value (or a defaultValue): compose it together with qualifier and delimiter.
+     *
+     * @param    string $qualifier
+     * @param    string $value
+     * @param    string $defaultValue
+     *
+     * @return    string        rendered link
+     */
+    private function composeLinkPart($qualifier, $value, $defaultValue = "") {
+
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'composeLinkPart'));
+        }
+
+        if (!$value) $value = $defaultValue;
+
+        if ($value)
+            return ($qualifier . ":" . $value . "|");
+
+        return '';
+    } // parseFRLine()
+
+    /**
+     * Store level, frCmd, value
+     *
+     * @param    string $level : 10.50.5
+     * @param    string $frCmd : sql
+     * @param    string $value : select ...
+     * @return    void
+     */
+    //Füllt frArray und indexArray
+    /**
+     * Renders pageX: extract token and determine if any default value has be applied
+     *
+     * @param    string $columnName
+     * @param    string $columnValue
+     *
+     * @return    string        rendered link
+     */
+    private function doPage($columnName, $columnValue) {
+        $defaultQuestion = '';
+
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//        debug(array('function' => 'doPage'));
+
+        }
+        $param = explode('|', $columnValue);
+
+        # get all defaultvalues, depending on the columnname
+        $defaultImage = $this->page_control["icon"][$columnName];
+        $defaultHash = $this->page_control["hash"][$columnName];
+        # define defaultquestion only, if pagetype needs a question
+        if ($this->page_control["msgbox"][$columnName]) $defaultQuestion = 'q:' . $this->page_control["msgbox"][$columnName];
+
+        foreach ($param as $key) {
+            switch (substr($key, 0, 1)) {
+                case 'P':
+                case 'E':
+                case 'N':
+                case 'D':
+                case 'H':
+                case 'I':
+                case 'S':
+                case 'B':
+                case 'C':
+                    $defaultImage = '';    // if any of the img token is given: no default
+                    break;
+                case 'h':
+                    $defaultHash = '';    // if a hash definition is given: no default
+                    break;
+                case 'q':
+                    $defaultQuestion = '';    // if a question is given: no default
+                    break;
+            }
+        }
+
+        $columnValue .= "|";
+
+        // append defaulst
+        if ($defaultImage) $columnValue .= $defaultImage . "|";
+        if ($defaultHash) $columnValue .= $defaultHash . "|";
+        if ($defaultQuestion) $columnValue .= $defaultQuestion . "|";
+
+#debug($columnValue);
+
+        return ($columnValue);
+    } // setLine()
+
+    /**
+     * Generate SortArgument
+     *
+     * @param    void
+     * @return    string: sortArg
+     */
+    //Hier wird das Sortierargument generiert
+
+    private function getResultArrayIndex($variable) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'getResultArrayIndex'));
+        }
+
+        $variable = substr($variable, 1, strlen($variable));
+        return "[" . preg_replace_callback("/[a-z]/", "replaceToIndex", $variable) . "][" . preg_replace_callback("/[^a-z]/", "replaceToIndex", $variable) . "]";
+
+    } // generateSortArg()
+
+
+    private function compareArraystart($arr1, $arr2) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'compareArraystart'));
+        }
+
+        for ($i = 0; $i < count($arr1); $i++) {
+            if ($arr1[$i] != $arr2[$i]) {
+                return false;
+            }
+        }
+        return true;
+    } // sortIndexArray()
+
+
+    private function compareArraylength($arr1, $arr2) {
+        if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
+            // T3 function: debug()
+//            debug(array('function' => 'compareArraylength'));
+        }
+
+        if (count($arr1) + 1 == count($arr2)) {
+            return true;
+        }
+        return false;
+    } // myExec()
+
+} // tx_formreport_pi1
diff --git a/qfq/report/Sendmail.php b/qfq/report/Sendmail.php
new file mode 100644
index 000000000..1bc9d23e4
--- /dev/null
+++ b/qfq/report/Sendmail.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace qfq;
+
+//use qfq;
+
+require_once(__DIR__ . '/Define.php');
+require_once(__DIR__ . '/Error.php');
+
+
+class Sendmail {
+
+    /**
+     * @var Log
+     */
+    private $Log;
+
+    /**
+     * Constructor:
+     *
+     * @param Log $fr_log
+     * @internal param Log $class :   fully created for logging.
+     *
+     */
+
+    public function __construct(Log $fr_log) {
+
+        $this->Log = $fr_log;
+    }
+
+    /**
+     * Send an email. Mail delivery should work - else the mails might disappear
+     * Seperate lines in the body with '\r\n'
+     * RC: if RC==0 Returns Output, else 'RC - Output'
+     *
+     * @param    array $mailarr : $mailarr['receiver'] (multiple with comma), $mailarr['sender'], $mailarr['subject'], $mailarr['body']
+     * @return string
+     */
+
+    public function sendmail($mailarr) {
+        $status = 'E';
+
+        // sending only if there is a receiver !
+        if ($mailarr['receiver']) {
+            // if(mail($receiver,$subject,$message, "From: ".$sender."\nX-Mailer: PHP/ . $phpversion()", "-f ".$sender))
+            if (mail($mailarr['receiver'], $mailarr['subject'], $mailarr['body'], "X-Mailer: PHP/" . phpversion() . "\r\nFrom: " . $mailarr['sender'] . ".\r\n", "-f " . $mailarr['sender'])) {
+                $msg = "Mail has been sent";
+                $status = 'I';
+            } else {
+                $msg = "Sending Mail not accepted";
+            }
+        } else {
+            $msg = "Mail not sent: missing receiver";
+        }
+
+        // Log every mail
+        $this->Log->log_mail("form", $status, $msg, $mailarr);
+
+        return ($msg);
+    } // sendmail()
+
+}
diff --git a/qfq/report/Utils.php b/qfq/report/Utils.php
new file mode 100644
index 000000000..accf74cde
--- /dev/null
+++ b/qfq/report/Utils.php
@@ -0,0 +1,324 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+require_once(__DIR__ . '/Define.php');
+require_once(__DIR__ . '/Db.php');
+
+
+class Utils {
+    /**
+     * @var Db
+     */
+    private $db = null;
+
+    /**
+     * @param $db
+     */
+    public function __construct($db = null) {
+        //TODO: Im Original nachschauen woher die globale Variable $Db kommt??? Ubergangsweise hier im Konstruktor plaziert. Wird aber aktuell nicht initialisiert!!!
+        $this->db = null;
+    }
+
+    /**
+     * Sanitize GET and POST Parameters. Categorize Parameter: name begins with:
+     * 'S_' String:                   sanitize via  t3lib_db::quoteStr, htmltospecialchars
+     * 'H_' HTML:                     no sanitize at all
+     * [Typo3, xdebug, zend debugger] special - no change
+     * [rest]                         has to be numeric - if not, will not be copied.
+     *
+     * @return    Array of sanitized GET and POST Variables. Only correctly filled variables will be returned.
+     */
+    public function sanitize() {
+        $arr = array();
+        // Merge GET and POST arrays. GET will be preferred.
+        $params = array_merge($_POST, $_GET);
+
+        foreach ($params as $key => $value) {
+
+            // if $value is an array (e.g. when using checkboxes on a 'set' field), convert it to a csv list.
+            if (is_array($value)) {
+                $value = implode(",", $value);
+            }
+
+            switch (substr($key, 0, 2)) {
+                //alphanum
+                // String values. HTML entitites will be converted, strings escaped.
+                case "S_":
+//					$arr[$key] = mysql_real_escape_string(htmlspecialchars($value));
+//					TA: htmlspecialchars darf hier nicht angewendet werden - Daten sollen so in DB geschrieben werden wie sie angegeben werden		
+                    $arr[$key] = mysql_real_escape_string($value);
+                    break;
+                //default (F_ kommt vom ursprünglichen namen form)
+                //Tags werden bis auf die Whitelist LIST_MARKUP_TAGS (siehe define) ersetzt
+                case "F_":
+                    $arr[$key] = addslashes(strip_tags($value, LIST_MARKUP_TAGS));
+                    break;
+                //email
+                //ExtJs definition (unterstützt nicht alle erlaubten Emailadressen)
+                case "E_":
+                    $pattern = "/^((\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6})?$/";
+                    if (preg_match($pattern, $value) > 0) {
+                        $arr[$key] = $value;
+                    }
+                    break;
+                //url
+                //Protokoll http / https muss angegeben werden. Definition ExtJs
+                case "U_":
+                    $pattern = "/((((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?))?$/i";
+                    if (preg_match($pattern, $value) > 0) {
+                        $arr[$key] = $value;
+                    }
+                    break;
+                //html
+                // HTML allowed: no sanitize at all.
+                case "H_":
+                    $arr[$key] = addslashes($value);
+                    break;
+                //date
+                //DD.MM.YYYY or DD/MM/YYYY
+                case "D_":
+// 					$pattern = "/^\d\d[(.|\/)]\d\d[(.|\/)]\d\d\d\d$/";
+// 					if(preg_match($pattern, $value)>0){
+// 						$arr[$key] = $value;
+// 					} 
+
+                    // use strtotime instead of a complex regex:
+                    // - the function accepts different formats of times / dates (e.g. speaking names) - this is extremely powerful
+                    // - the date is automatically converted to a defined format, which makes it easier to process in later steps
+                    if (strtotime($value)) {
+                        $arr[$key] = date("Y-m-d", strtotime($value));
+                    }
+                    break;
+                //time
+                //hh:mm:ss
+                case "T_":
+                    //$pattern = "/^(((0|1)\d|2[0-4])[:]([0-6]\d)[:]([0-6]\d))?$/";  // CR: restriction to 00-23 is to strong
+// 					$pattern = "/^\d?\d[:]\d?\d[:]?\d?\d?$/";
+// 					if(preg_match($pattern, $value)>0){
+// 						$arr[$key] = $value;
+// 					} 
+
+                    // use strtotime instead of a complex regex:
+                    // - the function accepts different formats of times / dates (e.g. speaking names) - this is extremely powerful
+                    // - the date is automatically converted to a defined format, which makes it easier to process in later steps
+                    if (strtotime($value)) {
+                        $arr[$key] = date("h:i:s", strtotime($value));
+                    }
+                    break;
+                //date_time
+                //DD.MM.YYYY hh:mm:ss
+                case "Z_":
+                    //$pattern = "/^((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d[ ]((0|1)\d|2[0-4])[:]([0-6]\d)[:]([0-6]\d))?$/";
+// 					$pattern = "/^(\d*\d[.]\d*\d[.]\d*\d*\d*\d[ ]\d?\d[:]\d?\d[:]?\d?\d?$/";
+// 					if(preg_match($pattern, $value)>0){
+// 						$arr[$key] = $value;
+// 					}
+
+                    // use strtotime instead of a complex regex:
+                    // - the function accepts different formats of times / dates (e.g. speaking names) - this is extremely powerful
+                    // - the date is automatically converted to a defined format, which makes it easier to process in later steps
+                    if (strtotime($value)) {
+                        $arr[$key] = date("Y-m-d h:i:s", strtotime($value));
+                    }
+
+                    break;
+                case "N_":
+                    //num
+                    //Only numeric-value allowed
+                    $pattern = "/^-?[0-9]*$/";
+                    if (preg_match($pattern, $value) > 0) {
+                        $arr[$key] = $value;
+                    }
+                    break;
+                default:
+                    /* 'id' (T3 page identifier) and ZEND 'debugger' GET_VARS won't be sanitized*/
+                    ;
+                    switch ($key) {
+                        case 'id':
+                        case 'start_debug':
+                        case 'debug_host':
+                        case 'send_sess_end':
+                        case 'debug_session_id':
+                        case 'original_url':
+                        case 'debug_start_session':
+                        case 'debug_no_cache':
+                        case 'debug_port':
+                            $arr[$key] = $value;
+                            continue 2;
+                    }
+                    break;
+            }
+        }
+        return ($arr);
+    }    // sanitize()
+
+    /**
+     * Create a new _unique_ (max 20 tries, else breaks) hash string and saves it in $_SESSION[FORMREPORT][$hash]
+     *
+     * @return    string        A random alphanumeric hash, or
+     *                      FALSE if it was not possible to create a unique hash.
+     */
+    public function randomAlphaNumUnique() {
+
+        for ($i = 0; $i < 20; $i++) {
+
+            $hash = $this->randomAlphaNum(LENGTH_HASH);
+
+            if (!isset($_SESSION[FORMREPORT][$hash])) {
+                $_SESSION[FORMREPORT][$hash] = array();
+                return ($hash);
+            }
+        }
+        // Too much tries without success
+        return (FALSE);
+    } // randomAlphaNum ()
+
+    /**
+     * @param int $length Length of the required hash string
+     * @return string       A random alphanumeric hash
+     */
+    private function randomAlphaNum($length) {
+        $possible_characters = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        $string = "";
+        while (strlen($string) < $length) {
+            $string .= substr($possible_characters, rand() % (strlen($possible_characters)), 1);
+        }
+
+        return ($string);
+    } // randomAlphaNumUnique()
+
+    /**
+     * If record locking has been enabled in ext_localconf.php, create a record in the lock table
+     *
+     * @param string $form
+     * @param int $record_id
+     * @param string $tablename
+     * @param string $dbalias
+     * @param $tx_db_pi1
+     */
+    function setLockRecord($form, $record_id, $tablename, $dbalias, &$tx_db_pi1) {
+        $result = '';
+        $mode = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT]['lock_records']['mode'];
+        if ($mode == "warn" || $mode == "lock") {
+            $query = "INSERT INTO `" . FR_LOCK . "` (`phpsession_id`, `fe_user_uid`, `form`, `record_id`, `tablename`, `dbalias`) VALUES ('" . session_id() . "', '" . $GLOBALS["TSFE"]->fe_user->user["uid"] . "', '" . $form . "', '" . $record_id . "', '" . $tablename . "', '" . $dbalias . "')";
+            $this->db->doQuery(DB, $query, $result, EXPECT_0);
+        } // if
+    } // eo setLockRecord
+
+
+    /**
+     * If record locking has been enabled in ext_localconf.php,
+     *    delete all expired locking records
+     *    check if a record exists in the lock table for the currently edited record
+     *
+     * @param int $form form_id
+     * @param int $record_id record_id
+     * @param string $tablename tablename
+     * @param Db $dbalias Db class object
+     * @param $tx_db_pi1
+     * @return array|bool           information on locking mode, locking user and timestamp. false if not locked
+     */
+    function checkLockRecord($form, $record_id, $tablename, $dbalias, &$tx_db_pi1) {
+        // Get config values from localconf or use default from define.php
+        $mode = $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT]['lock_records']['mode'];
+        $interval = ($GLOBALS['TYPO3_CONF_VARS'][FORMREPORT]['lock_records']['interval']) ?: LOCK_RECORDS_INTERVAL;
+
+        if ($mode == "warn" || $mode == "lock") {
+            // Delete all expired locking records
+            $query = "DELETE FROM `" . FR_LOCK . "` WHERE timestamp + INTERVAL " . $GLOBALS['TYPO3_CONF_VARS'][FORMREPORT]['lock_records']['interval'] . " SECOND < NOW()";
+            $this->db->doQuery(DB, $query, $result, EXPECT_0);
+
+            // Check if locking records exist
+            $query = "SELECT fe_user_uid, phpsession_id, date_format(timestamp + INTERVAL " . $interval . " SECOND, \"%H:%i %d.%m.%Y\") as lock_endtime FROM `" . FR_LOCK . "` WHERE `record_id`='" . $record_id . "' and `tablename`='" . $tablename . "' and `dbalias`='" . $dbalias . "' LIMIT 1";
+            $this->db->doQuery(DB, $query, $result, EXPECT_GE_0);
+
+            // If result is empty, return false
+            if (empty($result))
+                return false;
+
+            // If user is the same as the current one, return false
+            // Compare fe_user_uid and session-id
+            if ($result[0]['phpsession_id'] == session_id())
+                return false;
+
+            // Build array with locking information - will be used to create a warning/error message etc.
+            $arr = array();
+            $arr['mode'] = $mode;
+            $arr['fe_user_uid'] = $result[0]['fe_user_uid'];
+            $arr['lock_endtime'] = $result[0]['lock_endtime'];
+            return $arr;
+
+        } // if
+        // no locking configured
+        return false;
+    } // eo setLockRecord
+
+    /**
+     * Returns username for a fe_user_uid
+     *
+     * @param int $uid fe_user_uid
+     * @param $tx_db_pi1
+     * @return string       username
+     */
+    function getFEUserName($uid, &$tx_db_pi1) {
+        $query = "SELECT username FROM `fe_users` WHERE `uid`='" . $uid . "'";
+        $this->db->doQuery(T3, $query, $result, EXPECT_1);
+        $username = ($result['username']) ?: "anonymous";
+        return $username;
+    }
+
+    /**
+     * Create a unique directory in $path
+     *
+     * @param string $path path
+     * @return string           path/uniqedir
+     * @throws codeException
+     */
+    function createUniqueDir($path) {
+        // Try max. 20 times
+        for ($i = 0; $i < 20; $i++) {
+            $dirname = $this->randomAlphaNum(5);
+            $dirpath = $path . "/" . $dirname;
+
+            if (!file_exists($dirpath)) {
+                mkdir($dirpath, 0700, true);
+                return $dirpath;
+            }
+        }
+        // Too many tries without success
+        throw new CodeReportException ("Could not create unique directory.", __FILE__, __LINE__);
+    } // eo createUniqueDir
+
+    /**
+     * Create a ToolTip: $toolTip[0] and $toolTip[1] have to inserted in HTML code accordingly.
+     *
+     * @param    string $note Text to be shown in the tooltip
+     * @return    array        $toolTip        $toolTip[0]: JS to show '$toolTip[1]'.
+     *                                        $toolTip[1]: '<span>...</span>' with the tooltip text.
+     */
+    public function createToolTip($note) {
+        static $count = 0;
+
+        $toolTipIndex = 'tooltip.' . $GLOBALS["TSFE"]->currentRecord . '.' . ++$count;
+        $toolTip = array();
+
+        // Expample: <img src="fileadmin/icons/bullet-gray.gif" onmouseover="document.getElementById('gm167979').style.display='block';" onmouseout="document.getElementById('gm167979').style.display='none';" />
+        $toolTip[0] = " onmouseover=\"document.getElementById('" . $toolTipIndex . "').style.display='block';\" onmouseout=\"document.getElementById('" . $toolTipIndex . "').style.display='none';\"";
+
+        // Example: <span id="gm167979" style="display:none; position:absolute; border:solid 1px black; background-color:#F9F3D0; padding:3px;">My pesonal tooltip</span>
+        $toolTip[1] = '<span id="' . $toolTipIndex . '" style="display:none; position:absolute; border:solid 1px black; background-color:#F9F3D0; padding:3px;">' . $note . '</span>';
+
+        return ($toolTip);
+    } // createToolTip()
+
+}
diff --git a/qfq/report/Variables.php b/qfq/report/Variables.php
new file mode 100644
index 000000000..ab8f4970d
--- /dev/null
+++ b/qfq/report/Variables.php
@@ -0,0 +1,159 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Glowbase GmbH <support@glowbase.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+namespace qfq;
+
+//use qfq;
+
+class Variables {
+
+    public $resultArray = array();
+
+    // TODO to explain
+    private $tt_content_uid = 0;
+
+    /**
+     * @param int $tmp_ttcontent_uid
+     */
+    public function __construct($tmp_ttcontent_uid = 0) {
+
+        // specified once during initialisation.
+        $this->tt_content_uid = $tmp_ttcontent_uid;
+
+    }
+
+    /**
+     * Matches on the variables ~1.2name and substitutes them with the values
+     *
+     * @param string $text
+     * @return mixed
+     */
+    public function doVariables($text) {
+        $str = preg_replace_callback("/(~([a-zA-Z0-9._])*)/", array($this, 'replaceVariables'), $text);
+        return $str;
+    }
+
+    /**
+     * Callbackfunction called by variableSQL()
+     * Replaces the variablenames whith the value from the resultArray
+     *
+     * @param $matches
+     * @return string The content that is displayed on the website
+     * @internal param string $content : The PlugIn content
+     * @internal param array $conf : The PlugIn configuration
+     */
+    public function replaceVariables($matches) {
+
+        // variablename: ~10.20.column
+        $sqlVariable = $matches[1];
+
+        // index of last '.'
+        $pos = strrpos($sqlVariable, ".");
+
+        $replace = $this->resultArray[substr($sqlVariable, 1, $pos)][substr($sqlVariable, $pos + 1, strlen($sqlVariable))];
+
+        return (isset($this->resultArray[substr($sqlVariable, 1, $pos)][substr($sqlVariable, $pos + 1, strlen($sqlVariable))]) ? $replace : $sqlVariable);
+    }
+
+    /**
+     * Collect Global Variables
+     *
+     * @param    void
+     * @return    array with global variables which might be replaced
+     */
+    public function collectGlobalVariables() {
+        $arr = array();
+
+        $arr["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"];
+        $arr["HTTP_HOST"] = $_SERVER["HTTP_HOST"];
+        $arr["REQUEST_URI"] = $_SERVER["REQUEST_URI"];
+//        $arr["fe_user_uid"]			= $GLOBALS["TSFE"]->fe_user->user["uid"]?:'-';
+//        $arr["fe_user"]				= $GLOBALS["TSFE"]->fe_user->user["username"]?:'-';
+//        $arr["be_user_uid"]       	= $GLOBALS['BE_USER']->user["uid"]?:'-';
+//        $arr["page_id"]           	= $GLOBALS["TSFE"]->id;
+//        $arr["page_type"]			= $GLOBALS["TSFE"]->type;
+//        $arr["page_language_uid"]	= $GLOBALS["TSFE"]->sys_language_uid;
+//        $arr["ttcontent_uid"]		= $this->tt_content_uid;
+//        $protocol					= (!$_SERVER["HTTPS"] || $_SERVER["HTTPS"]=="off")?"http":"https";
+//        $arr["url"]					= $protocol . "://" . $arr["HTTP_HOST"];
+//        if($_SERVER["SERVER_PORT"] != 80)
+//            $arr["url"]				.= ":" . $_SERVER["SERVER_PORT"];
+//        $arr["url"]					.= $arr["REQUEST_URI"];
+
+        // Add all variables from ext_localconf
+        //		database aliases can be used in form sql queries (e.g. select * from "~global.t3_name".fe_users...)
+        //		localconf can be used to configure an application
+//        $tmp=array();
+//        $this->linearizeArray($GLOBALS['TYPO3_CONF_VARS'][FORMREPORT], $tmp);
+
+        // remove anything that contains "_password" "_username" in the key to prevent the webmaster from doing something stupid
+//        foreach($tmp as $key => $value) {
+//            if(!strstr($key, "_password") && !strstr($key, "_username"))
+//                $arr[$key]=$value;
+//        }
+
+        // Add t3 db name too
+//        require(PATH_typo3conf.'localconf.php');
+        //TODO: $typo_db kommt von T3: PHPSTorm weiss das nicht und meckert hier. Es ist auch nicht gut das die globale Variable genommen wird. Umbauen auf etwas sinnvollerers!
+        $typo_db = '';
+        $arr["t3_name"] = $typo_db;
+
+        return ($arr);
+    } // eo collectGlobalVariables
+
+    public function linearizeArray($arr, &$return, $keypath = "") {
+        if (is_array($arr)) {
+            foreach ($arr as $key => $value) {
+                $this->linearizeArray($value, $return, $keypath . "_" . $key);
+            }
+        } else {
+            $return[ltrim($keypath, "_")] = $arr;
+        }
+    } // linearizeArray()
+
+    /**
+     * Method to circumvate 'Undefined index'
+     *
+     * @param $varName
+     * @param $firstLevel
+     * @return string
+     */
+    public function get($varName, $firstLevel, $secondLevel = false) {
+        if (!isset($this->$varName))
+            $this->$varName = array();
+
+        if (!isset($this->$varName[$firstLevel]))
+            $this->$varName[$firstLevel] = array();
+
+        if ($secondLevel !== false && !isset($this->$varName[$firstLevel][$secondLevel]))
+            $this->$varName[$firstLevel][$secondLevel] = array();
+
+        if ($secondLevel === false) {
+            return $this->$varName[$firstLevel];
+        } else {
+            return $this->$varName[$firstLevel][$secondLevel];
+        }
+        return '';
+    }
+}
-- 
GitLab