Commit fbeba721 authored by Rafael Ostertag's avatar Rafael Ostertag
Browse files

Merge remote-tracking branch 'origin/crose_work' into raos_work

parents cc0b12e6 191b063a
......@@ -88,6 +88,8 @@ Setup a *report* to manage all *forms*: Create a Typo3 page and insert a content
+------------------------+----------------------------------+----------------------------------------------------------------------------+
| DB_NAME_TEST | DB_NAME_TEST=qfq_db_test | Used during development of QFQ |
+------------------------+----------------------------------+----------------------------------------------------------------------------+
| DB_INIT | DB_INIT=set names utf8 | Global init for using the database. |
+------------------------+----------------------------------+----------------------------------------------------------------------------+
| SESSION_NAME | SESSION_NAME=qfq | PHP Session name, by default 'qfq' |
+------------------------+----------------------------------+----------------------------------------------------------------------------+
| SQL_LOG | SQL_LOG=sql.log | Filename to log SQL commands: relative to <ext_dir> or absolute. |
......@@ -114,6 +116,7 @@ Example: *<ext_dir>/config.ini*
DB_PASSWORD = 12345678
DB_NAME = qfq_db
DB_NAME_TEST = qfq_db_test
DB_INIT = set names utf8
SESSION_NAME = qfq
SQL_LOG = sql.log
SHOW_DEBUG_INFO = auto
......
......@@ -6,6 +6,8 @@ DB_NAME = <DB>
DB_NAME_TEST = <TESTDB>
DB_INIT = set names utf8
SESSION_NAME = qfq
SQL_LOG = sql.log
......
......@@ -1248,9 +1248,9 @@ abstract class AbstractBuildForm {
}
// '&nbsp;' - This is necessary to correctly align an empty input.
$value = ($itemValue[$ii] === '') ? '&nbsp;' : $itemValue[$ii];
$tmpValue = ($itemValue[$ii] === '') ? '&nbsp;' : $itemValue[$ii];
$htmlElement = '<input ' . $attribute . '>' . $value;
$htmlElement = '<input ' . $attribute . '>' . $tmpValue;
// With ALIGN_HORIZONTAL: the label causes some trouble: skip it
if (($orientation === ALIGN_VERTICAL)) {
......
......@@ -91,8 +91,9 @@ const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
const ERROR_CODE_SHOULD_NOT_HAPPEN = 1003;
const ERROR_SIP_MALFORMED = 1005;
const ERROR_SIP_INVALID = 1006;
//const ERROR_MISSING_FORM_NAME = 1007;
const ERROR_MISSING_RECORD_ID = 1007;
const ERROR_IN_SQL_STATEMENT = 1008;
const ERROR_MISSING_REQUIRED_PARAMETER = 1009;
const ERROR_MISSING_SESSIONNAME = 1010;
const ERROR_BROKEN_PARAMETER = 1011;
const ERROR_FE_USER_UID_CHANGED = 1012;
......@@ -258,6 +259,7 @@ const SYSTEM_DB_SERVER = 'DB_SERVER';
const SYSTEM_DB_PASSWORD = 'DB_PASSWORD';
const SYSTEM_DB_NAME = 'DB_NAME';
const SYSTEM_DB_NAME_TEST = 'DB_NAME_TEST';
const SYSTEM_DB_INIT = 'DB_INIT';
const SYSTEM_SESSION_NAME = 'SESSION_NAME';
const SYSTEM_SQL_LOG = 'SQL_LOG'; // Logging to file
const SYSTEM_SQL_LOG_MODE = 'SQL_LOG_MODE'; // Mode, which statements to log.
......@@ -372,6 +374,9 @@ const GLYPH_ICON_SHOW = 'glyphicon glyphicon-search';
const GLYPH_ICON_TOOL = 'glyphicon-wrench';
const GLYPH_ICON_CHECK = 'glyphicon glyphicon-ok';
// FORM
const F_REQUIRED_PARAMETER = 'requiredParameter';
// FORM_ELEMENT_STATI
const FE_MODE_SHOW = 'show';
const FE_MODE_READONLY = 'readonly';
......@@ -405,6 +410,7 @@ const FLAG_DYNAMIC_UPDATE = 'flagDynamicUpdate';
const QUERY_TYPE_SELECT = 'type: select,show,describe,explain';
const QUERY_TYPE_INSERT = 'type: insert';
const QUERY_TYPE_UPDATE = 'type: update,replace,delete';
const QUERY_TYPE_CONTROL = 'type: set';
//Regexp
//const REGEXP_DATE_INT = '^\d{4}-\d{2}-\d{2}$';
......
......@@ -69,6 +69,12 @@ class Database {
$this->mysqli = $this->dbConnect();
}
$this->sqlLog = $this->store->getVar(SYSTEM_SQL_LOG, STORE_SYSTEM);
// DB Init
$dbInit = $this->store->getVar(SYSTEM_DB_INIT, STORE_SYSTEM);
if ($dbInit !== false && $dbInit != '') {
$this->sql($dbInit);
}
}
......@@ -95,96 +101,6 @@ class Database {
return $mysqli;
}
/**
* Return the number of rows returned by the last call to execute().
*
* If execute() has never been called, returns FALSE.
*
* @return mixed Number of rows returned by last call to execute(). If Database::execute()
* has never been called prior a call to this method, false is returned.
*/
public function getRowCount() {
if ($this->mysqli_result == null) {
return false;
}
return $this->mysqli_result->num_rows;
}
/**
* Get the values for a given ENUM or SET column
*
* @param string $table name of the table
* @param string $columnName name of the column
*
* @throws UserFormException if the table or column does not exist, or is not of type ENUM or SET
* @return array
*/
public function getEnumSetValueList($table, $columnName) {
$columnDefinition = $this->getFieldDefinitionFromTable($table, $columnName);
$setEnumDefinition = $columnDefinition["Type"];
// $setEnumDefinition holds now a string like
// String: enum('','red','blue','green')
$len = mb_strlen($setEnumDefinition);
# "enum('" = 6, "set('" = 5
$tokenLength = strpos($setEnumDefinition, "'") + 1;
// count("enum('") == 6, count("')") == 2
$enumSetString = mb_substr($setEnumDefinition, $tokenLength, $len - (2 + $tokenLength));
// String: ','red','blue','green
if (($setEnumValueList = explode("','", $enumSetString)) === false) {
return array();
}
return $setEnumValueList;
}
/**
* Get database column definition.
*
* If the column is not found in the table, an exception is thrown.
*
* @param string $table name of the table
*
* @param string $columnName name of the column
* @return array the definition of the column as retrieved by Database::getTableDefinition().
*
* @throws \qfq\DbException
*/
private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getTableDefinition($table);
foreach ($tableDefinition AS $row) {
if ($row["Field"] == $columnName) {
return $row;
}
}
throw new DbException("Column name '$columnName' not found in table '$table'.", ERROR_DB_COLUMN_NOT_FOUND_IN_TABLE);
}
/**
* Get all column definitions for a table. Return Assoc Array:
*
* Field Type Null Key Default Extra
* --------------------------------------------------------------------------
* id bigint(20) NO PRI NULL auto_increment
* name varchar(128) YES NULL
* firstname varchar(128) YES NULL
* gender enum('','male','female') NO male
* groups set('','a','b','c') NO a
*
* @param string $table table to retrieve column definition from
*
* @return array column definition of table as returned by SHOW FIELDS FROM as associative array.
*/
public function getTableDefinition($table) {
return $this->sql("SHOW FIELDS FROM `$table`");
}
/**
* Fires query $sql and fetches result als assoc array (all modes but ROW_KEYS) or as num array (mode: ROW_EKYS). Throws exception.
*
......@@ -344,11 +260,11 @@ class Database {
if (false === ($result = $this->mysqli_stmt->get_result())) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_EXECUTE);
}
$queryType = QUERY_TYPE_SELECT;
$queryType = QUERY_TYPE_SELECT;
$this->mysqli_result = $result;
$stat[DB_NUM_ROWS] = $this->mysqli_result->num_rows;
$count = $stat[DB_NUM_ROWS];
$msg = 'Read rows: ' . $stat[DB_NUM_ROWS];
$stat[DB_NUM_ROWS] = $this->mysqli_result->num_rows;
$count = $stat[DB_NUM_ROWS];
$msg = 'Read rows: ' . $stat[DB_NUM_ROWS];
break;
case 'INSERT':
$queryType = QUERY_TYPE_INSERT;
......@@ -360,11 +276,18 @@ class Database {
case 'UPDATE':
case 'REPLACE':
case 'DELETE':
$queryType = QUERY_TYPE_UPDATE;
$stat[DB_AFFECTED_ROWS] = $this->mysqli->affected_rows;
$count = $stat[DB_AFFECTED_ROWS];
$queryType = QUERY_TYPE_UPDATE;
$stat[DB_AFFECTED_ROWS] = $this->mysqli->affected_rows;
$count = $stat[DB_AFFECTED_ROWS];
$msg = 'Affected rows: ' . $count;
break;
case 'SET':
$queryType = QUERY_TYPE_CONTROL;
$stat[DB_AFFECTED_ROWS] = 0;
$count = $stat[DB_AFFECTED_ROWS];
$msg = '';
break;
default:
throw new DbException('Unknown comand: "' . $command . '"', ERROR_DB_UNKNOWN_COMMAND);
break;
......@@ -510,6 +433,96 @@ class Database {
}
}
/**
* Return the number of rows returned by the last call to execute().
*
* If execute() has never been called, returns FALSE.
*
* @return mixed Number of rows returned by last call to execute(). If Database::execute()
* has never been called prior a call to this method, false is returned.
*/
public function getRowCount() {
if ($this->mysqli_result == null) {
return false;
}
return $this->mysqli_result->num_rows;
}
/**
* Get the values for a given ENUM or SET column
*
* @param string $table name of the table
* @param string $columnName name of the column
*
* @throws UserFormException if the table or column does not exist, or is not of type ENUM or SET
* @return array
*/
public function getEnumSetValueList($table, $columnName) {
$columnDefinition = $this->getFieldDefinitionFromTable($table, $columnName);
$setEnumDefinition = $columnDefinition["Type"];
// $setEnumDefinition holds now a string like
// String: enum('','red','blue','green')
$len = mb_strlen($setEnumDefinition);
# "enum('" = 6, "set('" = 5
$tokenLength = strpos($setEnumDefinition, "'") + 1;
// count("enum('") == 6, count("')") == 2
$enumSetString = mb_substr($setEnumDefinition, $tokenLength, $len - (2 + $tokenLength));
// String: ','red','blue','green
if (($setEnumValueList = explode("','", $enumSetString)) === false) {
return array();
}
return $setEnumValueList;
}
/**
* Get database column definition.
*
* If the column is not found in the table, an exception is thrown.
*
* @param string $table name of the table
*
* @param string $columnName name of the column
* @return array the definition of the column as retrieved by Database::getTableDefinition().
*
* @throws \qfq\DbException
*/
private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getTableDefinition($table);
foreach ($tableDefinition AS $row) {
if ($row["Field"] == $columnName) {
return $row;
}
}
throw new DbException("Column name '$columnName' not found in table '$table'.", ERROR_DB_COLUMN_NOT_FOUND_IN_TABLE);
}
/**
* Get all column definitions for a table. Return Assoc Array:
*
* Field Type Null Key Default Extra
* --------------------------------------------------------------------------
* id bigint(20) NO PRI NULL auto_increment
* name varchar(128) YES NULL
* firstname varchar(128) YES NULL
* gender enum('','male','female') NO male
* groups set('','a','b','c') NO a
*
* @param string $table table to retrieve column definition from
*
* @return array column definition of table as returned by SHOW FIELDS FROM as associative array.
*/
public function getTableDefinition($table) {
return $this->sql("SHOW FIELDS FROM `$table`");
}
/**
* Wrapper for sql(), to simplyfy access.
*
......
......@@ -408,6 +408,11 @@ class QuickFormQuery {
// Retrieve record_id either from SIP (prefered) or via URL
$r = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT, '', $recordIdFoundInStore);
// Missing 'r' is in general an error
if ($r === false) {
throw new UserFormException("Missing RECORD_ID (broken SIP?).", ERROR_MISSING_RECORD_ID);
}
// If there is a record_id>0: EDIT else NEW: 'sip','logged_in','logged_out','always','never'
$permitMode = ($r > 0) ? $this->formSpec['permitEdit'] : $this->formSpec['permitNew'];
......@@ -450,6 +455,23 @@ class QuickFormQuery {
throw new UserFormException("MultiMode selected, but MultiSQL missing", ERROR_MULTI_SQL_MISSING);
}
$sipArray = $this->store->getStore(STORE_SIP);
// Check: requiredParameter: '' or 'form' or 'form,grId' or 'form #formname for form,grId'
$param = explode(',', $this->formSpec[F_REQUIRED_PARAMETER]);
foreach ($param AS $name) {
$name = explode('#', $name, 2);
$name = trim($name[0]);
if ($name === '') {
continue;
}
if (!isset($sipArray[$name])) {
throw new UserFormException("Missing required SIP parameter: $name", ERROR_MISSING_REQUIRED_PARAMETER);
}
}
return $sipFound;
}
......@@ -513,7 +535,7 @@ class QuickFormQuery {
private function doReport() {
$report = new Report($this->t3data, $this->store->getVar(SYSTEM_SESSION_NAME, STORE_SYSTEM), $this->eval, $this->phpUnit);
$html = $report->process();
$html = $report->process($this->t3data['bodytext']);
return $html;
......
......@@ -21,8 +21,6 @@ class AbstractException extends \Exception {
protected $file = '';
protected $line = '';
/**
* @return string
*/
......@@ -78,6 +76,8 @@ class AbstractException extends \Exception {
if ($store !== null && $store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes') {
$this->messageArray['current sip'] = $store->getStore(STORE_SIP);
// Layout
$debug = '<tr bgcolor="#dddddd"><td colspan="2">Exception</td></tr>';
foreach ($this->messageArray as $key => $value) {
......
......@@ -106,7 +106,29 @@ class Support {
break;
}
return $type . '="' . trim($value) . '" ';
$value = self::escapeDoubleTick(trim($value));
return $type . '="' . $value . '" ';
}
/**
* Escapes Double Ticks ("), which are not already escaped.
*
* @param $str
* @return string
*/
public static function escapeDoubleTick($str) {
$newStr = '';
for ($ii = 0; $ii < strlen($str); $ii++) {
if ($str[$ii] === '"') {
if ($ii === 0 || $str[$ii - 1] != '\\') {
$newStr .= '\\';
}
}
$newStr .= $str[$ii];
}
return $newStr;
}
/**
......
......@@ -57,7 +57,7 @@ require_once(__DIR__ . '/../exceptions/UserReportExtension.php');
* P:picture [file]
* C:checkbox [name]
* R:right
* h:hash
* s:sip
*
* A: A:[u:p:m]
* G: G:[N|..]
......@@ -82,12 +82,44 @@ const NAME_LINK_CLASS = 'linkClass';
const NAME_LINK_CLASS_DEFAULT = 'linkClassDefault';
const NAME_QUESTION = 'question';
const NAME_ENCRYPTION = 'encryption';
const NAME_HASH = 'hash';
const NAME_SIP = 'sip';
const NAME_URL_PARAM = 'param';
const NAME_RIGHT_PICTURE_POSITION = 'picturePosition';
const TOKEN_URL = 'u';
const TOKEN_MAIL = 'm';
const TOKEN_PAGE = 'p';
const TOKEN_TEXT = 't';
const TOKEN_ALT_TEXT = 'a';
const TOKEN_TOOL_TIP = 'o';
const TOKEN_PICTURE = 'P';
const TOKEN_BULLET = 'B';
const TOKEN_CHECK = 'C';
const TOKEN_DELETE = 'D';
const TOKEN_EDIT = 'E';
const TOKEN_HELP = 'H';
const TOKEN_INFO = 'I';
const TOKEN_NEW = 'N';
const TOKEN_SHOW = 'S';
const TOKEN_RENDER = 'r';
const TOKEN_TARGET = 'g';
const TOKEN_CLASS = 'c';
const TOKEN_QUESTION = 'q';
const TOKEN_ENCRYPTION = 'e';
const TOKEN_SIP = 's';
const TOKEN_URL_PARAM = 'U';
const TOKEN_RIGHT = 'R';
const TOKEN_CLASS_NONE = 'n';
const TOKEN_CLASS_INTERNAL = 'i';
const TOKEN_CLASS_EXTERNAL = 'e';
const NO_CLASS = 'no_class';
const DEFAULT_BULLET_COLOR = 'green';
const DEFAULT_CHECK_COLOR = 'green';
/**
* Class Link
* @package qfq
......@@ -118,50 +150,50 @@ class Link {
* @var array
*/
private $renderControl = array();
private $linkClassSelector = array("i" => "internal", "e" => "external");
private $linkClassSelector = array(TOKEN_CLASS_INTERNAL => "internal", TOKEN_CLASS_EXTERNAL => "external");
private $cssLinkClassInternal = '';
private $cssLinkClassExternal = '';
private $callTable = [
'u' => 'buildUrl',
'm' => 'buildMail',
'p' => 'buildPage',
'o' => 'buildToolTip',
'P' => 'buildPicture',
'B' => 'buildBullet',
'C' => 'buildCheck',
'D' => 'buildDelete',
'E' => 'buildEdit',
'H' => 'buildHelp',
'I' => 'buildInfo',
'N' => 'buildNew',
'S' => 'buildShow',
TOKEN_URL => 'buildUrl',
TOKEN_MAIL => 'buildMail',
TOKEN_PAGE => 'buildPage',
TOKEN_TOOL_TIP => 'buildToolTip',
TOKEN_PICTURE => 'buildPicture',
TOKEN_BULLET => 'buildBullet',
TOKEN_CHECK => 'buildCheck',
TOKEN_DELETE => 'buildDelete',
TOKEN_EDIT => 'buildEdit',
TOKEN_HELP => 'buildHelp',
TOKEN_INFO => 'buildInfo',
TOKEN_NEW => 'buildNew',
TOKEN_SHOW => 'buildShow',
];
private $tableVarName = [
'u' => NAME_URL,
'm' => NAME_MAIL,
'p' => NAME_PAGE,
't' => NAME_TEXT,
'a' => NAME_ALT_TEXT,
'o' => NAME_TOOL_TIP,
'P' => NAME_IMAGE,
'B' => NAME_IMAGE,
'C' => NAME_IMAGE,
'D' => NAME_IMAGE,
'E' => NAME_IMAGE,
'H' => NAME_IMAGE,
'I' => NAME_IMAGE,
'N' => NAME_IMAGE,
'S' => NAME_IMAGE,
'r' => NAME_RENDER,
'g' => NAME_TARGET,
'c' => NAME_LINK_CLASS,
'q' => NAME_QUESTION,
'e' => NAME_ENCRYPTION,
'h' => NAME_HASH,
'U' => NAME_URL_PARAM,
'R' => NAME_RIGHT_PICTURE_POSITION,
TOKEN_URL => NAME_URL,
TOKEN_MAIL => NAME_MAIL,
TOKEN_PAGE => NAME_PAGE,
TOKEN_TEXT => NAME_TEXT,
TOKEN_ALT_TEXT => NAME_ALT_TEXT,
TOKEN_TOOL_TIP => NAME_TOOL_TIP,
TOKEN_PICTURE => NAME_IMAGE,
TOKEN_BULLET => NAME_IMAGE,
TOKEN_CHECK => NAME_IMAGE,
TOKEN_DELETE => NAME_IMAGE,
TOKEN_EDIT => NAME_IMAGE,
TOKEN_HELP => NAME_IMAGE,
TOKEN_INFO => NAME_IMAGE,
TOKEN_NEW => NAME_IMAGE,
TOKEN_SHOW => NAME_IMAGE,
TOKEN_RENDER => NAME_RENDER,
TOKEN_TARGET => NAME_TARGET,
TOKEN_CLASS => NAME_LINK_CLASS,
TOKEN_QUESTION => NAME_QUESTION,
TOKEN_ENCRYPTION => NAME_ENCRYPTION,
TOKEN_SIP => NAME_SIP,
TOKEN_URL_PARAM => NAME_URL_PARAM,
TOKEN_RIGHT => NAME_RIGHT_PICTURE_POSITION,
];
private $varsDefault = array();
......@@ -186,7 +218,7 @@ class Link {
$this->varsDefault[NAME_ENCRYPTION] = '0';
$this->varsDefault[NAME_RIGHT_PICTURE_POSITION] = 'l';
$this->varsDefault[NAME_RENDER] = 0;
$this->varsDefault[NAME_HASH] = 0;
$this->varsDefault[NAME_SIP] = 0;
/*
* mode:
......@@ -259,14 +291,13 @@ class Link {
$this->parseItem($vars, $item);
}
$necessaryDefaults = [NAME_RENDER, NAME_RIGHT_PICTURE_POSITION, NAME_HASH, NAME_ENCRYPTION];
$necessaryDefaults = [NAME_RENDER, NAME_RIGHT_PICTURE_POSITION, NAME_SIP, NAME_ENCRYPTION];
foreach ($necessaryDefaults AS $keyName) {
if ($vars[$keyName] == '') {
$vars[$keyName] = $this->varsDefault[$keyName];
}
}
// if there is no url or mailto definition: {{global.pageId}}
// if ($vars[NAME_URL] == '' && $vars[NAME_MAIL] == '') {
// $vars[NAME_URL] = "?" . $this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3);
......@@ -346,7 +377,8 @@ class Link {
// 3: <a href=url>url</a>
case '3':
$link = $htmlUrl . $vars[NAME_URL] . '</a>' . $vars[NAME_TOOL_TIP_JS][1];
// $link = $htmlUrl . $vars[NAME_URL] . '</a>' . $vars[NAME_TOOL_TIP_JS][1];
$link = $htmlUrl . $vars[NAME_URL] . '</a>';
break;
case '13':
$vars[NAME_TEXT] = $vars[NAME_MAIL];
......@@ -355,7 +387,8 @@ class Link {
// 4: <a href=url>Text</a>
case '4':
$link = $htmlUrl . $vars[NAME_TEXT] . '</a>' . $vars[NAME_TOOL_TIP_JS][1];
// $link = $htmlUrl . $vars[NAME_TEXT] . '</a>' . $vars[NAME_TOOL_TIP_JS][1];
$link = $htmlUrl . $vars[NAME_TEXT] . '</a>';
break;
case '14':
$link = $this->encryptMailtoJS($vars, TRUE);
......@@ -365,7 +398,7 @@ class Link {
case '23':
case '24':
//TODO: Alter Code, umstellen auf JS Client von RO. Vorlage koennte 'Delete' in Subrecord sein.
$link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'',hash:'',forward:'" .
$link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'',sip:'',forward:'" .
$vars[NAME_PAGE] . "'});\" " . $vars[NAME_LINK_CLASS] . ">" . $vars[NAME_TEXT] . "</a>";
}
......@@ -400,7 +433,7 @@ class Link {