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

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

parents f9d0fab4 8bf606a0
......@@ -50,6 +50,7 @@ const ROW_EXPECT_0 = "expect_0";
const ROW_EXPECT_1 = "expect_1";
const ROW_EXPECT_0_1 = "expect_0_1";
const ROW_EXPECT_GE_1 = "expect_ge_1";
const ROW_KEYS = "keys";
// KeyValueParser
const IF_VALUE_EMPTY_COPY_KEY = 'if_value_empty_copy_key';
......@@ -97,7 +98,7 @@ const ERROR_UNKNOWN_MODE = 1032;
const ERROR_NOT_IMPLEMENTED = 1033;
const ERROR_RESERVED_KEY_NAME = 1034;
const ERROR_VALUE_HAS_NO_KEY = 1035;
const ERROR_DB_EXECUTE = 1036;
const ERROR_COLUMN_NOT_FOUND_IN_TABLE = 1037;
const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1038;
const ERROR_KEY_EXIST_IN_STORE = 1039;
......@@ -112,6 +113,13 @@ const ERROR_GET_STORE_ZERO = 1047;
const ERROR_SET_STORE_ZERO = 1048;
const ERROR_MISSING_FORMELEMENT = 1049;
const ERROR_DB_EXECUTE = 1050;
const ERROR_DB_PREPARE = 1051;
const ERROR_DB_BIND = 1052;
const ERROR_DB_QUERY = 1053;
const ERROR_CLOSE_MYSQLI_RESULT = 1054;
const ERROR_CLOSE_MYSQLI_STMT = 1055;
// DB Errors
//const ERROR_DB_QUERY_SIMPLE = 2000;
......@@ -182,7 +190,6 @@ const SYSTEM_DBPW = 'DBPW';
const SYSTEM_DB = 'DB';
const SYSTEM_TESTDB = 'TESTDB';
const SYSTEM_SESSIONNAME = 'SESSIONNAME';
const SYSTEM_DBH = 'dataBaseHandle';
// Information for: Log / Debug / Exception
const SYSTEM_SQL_RAW = 'sqlRaw'; // Type: SANATIZE_ALL / String. SQL Query (before substitute). Useful for error reporting.
const SYSTEM_SQL_FINAL = 'sqlFinal'; // Type: SANATIZE_ALL / String. SQL Query (after substitute). Useful for error reporting.
......
......@@ -9,19 +9,20 @@
namespace qfq;
use qfq;
use qfq\DbException;
use qfq\Store;
use qfq\CodeException;
use qfq\UserException;
use qfq\DbException;
use qfq\Support;
require_once(__DIR__ . '/../qfq/exceptions/UserException.php');
require_once(__DIR__ . '/../qfq/exceptions/CodeException.php');
require_once(__DIR__ . '/../qfq/exceptions/DbException.php');
require_once(__DIR__ . '/exceptions/UserException.php');
require_once(__DIR__ . '/exceptions/CodeException.php');
require_once(__DIR__ . '/exceptions/DbException.php');
require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/helper/Support.php');
require_once(__DIR__ . '/store/Store.php');
require_once(__DIR__ . '/helper/Support.php');
require_once(__DIR__ . '/helper/BindParam.php');
/**
* Class Database
......@@ -29,15 +30,26 @@ require_once(__DIR__ . '/../qfq/helper/Support.php');
*/
class Database {
/**
* @var \PDO
*/
public $pdo = null;
/**
* @var Store
*/
private $store = null;
private $stmt = null;
/**
* @var \mysqli
*/
private $mysqli = null;
/**
* @var \mysqli_stmt
*/
private $mysqli_stmt = null;
/**
* @var \mysqli_result
*/
private $mysqli_result = null;
/**
* Returns current data base handle from Store[System][SYSTEM_DBH].
......@@ -49,32 +61,33 @@ class Database {
public function __construct() {
$this->store = Store::getInstance();
$this->pdo = $this->store->getVar(SYSTEM_DBH, STORE_SYSTEM);
if ($this->pdo === false) {
$this->pdo = $this->dbConnect();
$this->store->setVar(SYSTEM_DBH, $this->pdo, STORE_SYSTEM);
if ($this->mysqli === null) {
$this->mysqli = $this->dbConnect();
}
}
/**
* If not
* @return \PDO
* Open mysqli database connection if not already done.
*
* @return \mysqli
* @throws UserException
*/
private function dbConnect() {
$mysqli = null;
$dbuser = $this->store->getVar(SYSTEM_DBUSER, STORE_SYSTEM);
$dbserver = $this->store->getVar(SYSTEM_DBSERVER, STORE_SYSTEM);
$dbpw = $this->store->getVar(SYSTEM_DBPW, STORE_SYSTEM);
$db = $this->store->getVar(SYSTEM_DB, STORE_SYSTEM);
try {
$pdo = new \PDO("mysql:host=" . $dbserver . ";dbname=" . $db, $dbuser, $dbpw, array(\PDO::ATTR_PERSISTENT => true));
} catch (\Exception $e) {
throw new UserException ("Error open Database 'mysql:host=" . $dbserver . ";dbname=" . $db . ";dbuser=" . $dbuser . "'': " . $e->getMessage(), ERROR_OPEN_DATABASE);
$mysqli = new \mysqli($dbserver, $dbuser, $dbpw, $db);
if ($mysqli->connect_error) {
throw new UserException ("Error open Database 'mysql:host=" . $dbserver . ";dbname=" . $db . ";dbuser=" . $dbuser . "'': " . $mysqli->connect_errno . PHP_EOL . $mysqli->connect_error, ERROR_OPEN_DATABASE);
}
return $pdo;
return $mysqli;
}
/**
......@@ -86,11 +99,11 @@ class Database {
* has never been called prior a call to this method, false is returned.
*/
public function getRowCount() {
if ($this->stmt == null) {
if ($this->mysqli_result == null) {
return false;
}
return $this->stmt->rowCount();
return $this->mysqli_result->num_rows;
}
/**
......@@ -134,11 +147,9 @@ class Database {
* @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 UserException
*
* @throws \qfq\DbException
*/
private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getTableDefinition($table);
......@@ -185,7 +196,9 @@ class Database {
* @throws \qfq\CodeException
* @throws \qfq\DbException
*/
public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '') {
public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '', array &$keys = array()) {
$result = array();
$this->closeMysqliStmt();
// CR often forgets to specify the $mode and use prepared statement with parameter instead.
if (is_array($mode))
......@@ -200,52 +213,68 @@ class Database {
throw new DbException($specificMessage . "No idea why this error happens - please take some time and check this: $sql", ERROR_DB_GENERIC_CHECK);
}
if ($mode === ROW_IMPLODE_ALL) {
return $this->fetchAll(ROW_IMPLODE_ALL);
}
if ($mode === ROW_REGULAR) {
return $this->fetchAll();
}
switch ($mode) {
case ROW_IMPLODE_ALL:
return $this->fetchAll(ROW_IMPLODE_ALL);
$result = $this->fetchAll($mode);
break;
case ROW_KEYS:
case ROW_REGULAR:
return $this->fetchAll();
$result = $this->fetchAll($mode, $keys);
break;
case ROW_EXPECT_0:
if ($count === 0)
return array();
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
$result = array();
else
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
break;
case ROW_EXPECT_1:
if ($count === 1)
return $this->fetchOne();
throw new DbException($specificMessage . "Expected one row, got 0 or more than 1: $sql", ERROR_DB_COUNT_DO_NOT_MATCH);
$result = $this->fetchAll($mode)[0];
else
throw new DbException($specificMessage . "Expected one row, got 0 or more than 1: $sql", ERROR_DB_COUNT_DO_NOT_MATCH);
break;
case ROW_EXPECT_0_1:
if ($count === 1)
return $this->fetchOne();
if ($count === 0)
return array();
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
$result = $this->fetchAll($mode)[0];
elseif ($count === 0)
$result = array();
else
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
break;
// case ROW_EXPECT_GE_0:
// if ($count === 0)
// return array();
// return $this->fetchAll();
// break;
case ROW_EXPECT_GE_1:
if ($count > 0)
return $this->fetchAll();
throw new DbException($specificMessage . "Expected at least one row, got nothing: $sql", ERROR_DB_TOO_FEW_ROWS);
$result = $this->fetchAll($mode);
else
throw new DbException($specificMessage . "Expected at least one row, got nothing: $sql", ERROR_DB_TOO_FEW_ROWS);
break;
default:
throw new DbException($specificMessage . "Unknown mode: $mode", ERROR_UNKNOWN_MODE);
}
$this->closeMysqliStmt();
return $result;
}
/**
* Close an optional open MySQLi Statement.
*
* @throws \qfq\DbException
*/
private function closeMysqliStmt() {
if ($this->mysqli_result !== null && $this->mysqli_result !== false) {
$this->mysqli_result->free_result();
}
if ($this->mysqli_stmt !== null && $this->mysqli_stmt !== false) {
$this->mysqli_stmt->free_result();
if (!$this->mysqli_stmt->close())
throw new DbException('Error closing mysqli_stmt' . ERROR_CLOSE_MYSQLI_STMT);
}
$this->mysqli_stmt = null;
$this->mysqli_result = null;
}
/**
......@@ -259,24 +288,69 @@ class Database {
* @throws \qfq\DbException
* @throws \qfq\UserException
*/
public function prepareExecute($sql, array $parameterArray = array()) {
private function prepareExecute($sql, array $parameterArray = array()) {
$result = null;
$this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
$this->store->setVar(SYSTEM_SQL_PARAM_ARRAY, $parameterArray, STORE_SYSTEM);
// Logfile
$this->dbLog($sql, $parameterArray);
$this->stmt = $this->pdo->prepare($sql);
if (false === $this->stmt->execute($parameterArray)) {
throw new DbException($this->stmt->errorInfo()[2], ERROR_DB_EXECUTE);
// Prepared Statement?
// if (count($parameterArray) === 0) {
// if (false === ($result = $this->mysqli->query($sql))) {
// throw new DbException('[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error, ERROR_DB_QUERY);
// }
//
// } else {
if (false === ($this->mysqli_stmt = $this->mysqli->prepare($sql))) {
throw new DbException('[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error, ERROR_DB_PREPARE);
}
if (count($parameterArray) > 0) {
if (false === $this->fakeCallUserFunc($parameterArray)) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_BIND);
}
}
if (false === $this->mysqli_stmt->execute()) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_EXECUTE);
}
// }
$msg = '';
$count = 0;
$command = strtoupper(explode(' ', $sql, 2)[0]);
switch ($command) {
case 'SELECT':
case 'SHOW':
case 'DESCRIBE':
case 'EXPLAIN':
if (false === ($result = $this->mysqli_stmt->get_result())) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_EXECUTE);
}
$this->mysqli_result = $result;
$count = $this->mysqli_result->num_rows;
$msg = 'Get rows: ' . $count;
break;
case 'INSERT':
$count = $this->mysqli->insert_id;
$msg = 'ID: ' . $count;
break;
case 'UPDATE':
case 'REPLACE':
case 'DELETE':
$count = $this->mysqli->affected_rows;
$msg = 'Affected rows: ' . $count;
break;
default:
break;
}
$count = $this->stmt->rowCount();
$this->store->setVar(SYSTEM_SQL_COUNT, $count, STORE_SYSTEM);
// Logfile
$msg = (substr($sql, 0, 7) === 'INSERT ') ? 'ID: ' . $this->getLastInsertId() : 'Affected rows: ' . $count;
$this->dbLog($msg);
return $count;
......@@ -313,12 +387,48 @@ class Database {
}
/**
* Returns lastInsertId
*
* @return string
* @param $arr
*/
public function getLastInsertId() {
return $this->pdo->lastInsertId();
private function fakeCallUserFunc($arr) {
$type = '';
foreach ($arr as $value) {
if (is_int($value)) {
$type .= 'i';
} elseif (is_double($value)) {
$type .= 'd';
} else {
$type .= 's';
}
}
switch (count($arr)) {
case 0:
break;
case 1:
$this->mysqli_stmt->bind_param($type, $arr[0]);
break;
case 2:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1]);
break;
case 3:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2]);
break;
case 4:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3]);
break;
case 5:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4]);
break;
case 6:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4], $arr[5]);
break;
case 7:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4], $arr[5], $arr[6]);
break;
default:
throw new DbException('Oops, too many parameter in prepared statement.', 0);
}
}
/**
......@@ -330,43 +440,56 @@ class Database {
* All rows and all columns imploded to one string if $mode=IMPLODE_ALL
*
*/
public function fetchAll($mode = '') {
if ($this->stmt == null) {
private function fetchAll($mode = '', &$keys = array()) {
if ($this->mysqli_result == null || $this->mysqli_result == false) {
return false;
}
if ($this->stmt->rowCount() === 0) {
if ($this->mysqli_result->num_rows === 0) {
return ($mode === ROW_IMPLODE_ALL) ? "" : array();
}
$str = "";
if ($mode === ROW_IMPLODE_ALL) {
foreach ($this->stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$str .= implode($row);
}
return $str;
} else {
return $this->stmt->fetchAll(\PDO::FETCH_ASSOC);
switch ($mode) {
case ROW_IMPLODE_ALL:
$str = "";
foreach ($this->mysqli_result->fetch_all(MYSQLI_ASSOC) as $row) {
$str .= implode($row);
}
return $str;
break;
case ROW_KEYS:
$keys = array();
for ($ii = 0; $ii < $this->mysqli_result->field_count; $ii++) {
$keys[$ii] = $this->mysqli_result->fetch_field_direct($ii)->name;
}
return $this->mysqli_result->fetch_all(MYSQLI_NUM);
break;
default:
return $this->mysqli_result->fetch_all(MYSQLI_ASSOC);
}
}
/**
* Fetch one row of the result as associative array.
*
* @return null|mixed the first row of the result of Database::execute() as associative array.
* If Database::execute() has never been called prior a call to this method, false is returned.
* Empty string is returned if the query didn't yield any rows.
* @param $sql
* @param array $keys
* @return array|bool
* @throws \qfq\DbException
*/
public function fetchOne() {
if ($this->stmt == null) {
return false;
}
public function sqlKeys($sql, array &$keys) {
if ($this->stmt->rowCount() === 0) {
return array();
}
return $this->sql($sql, ROW_KEYS, array(), '', $keys);
}
return $this->stmt->fetch(\PDO::FETCH_ASSOC);
/**
* Returns lastInsertId
*
* @return string
*/
public function getLastInsertId() {
return $this->mysqli->insert_id;
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: nick9v at hotmail dot com ¶
* Date: 2/10/16
* Time: 2:05 PM
*/
namespace qfq;
class BindParam {
private $values = array(), $types = '';
public function add(&$value) {
$this->values[] = $value;
$this->types .= $this->getParameterBindType($value);
}
/**
* Depending of $value, returns i (integer), d (double) or s (string). This is needed for mysqli_bind().
*
* @param $value
* @return string
*/
private function getParameterBindType($value) {
if (is_int($value)) {
$type = 'i';
} elseif (is_double($value)) {
$type = 'd';
} else {
$type = 's';
}
return $type;
}
public function get() {
return array_merge(array($this->types), $this->values);
}
}
\ No newline at end of file
......@@ -139,13 +139,15 @@ class Report {
// $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);
//TODO implement logging
// $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);
//TODO implement logging
// $this->db->set_fr_error($this->fr_error);
$this->log->set_fr_error($this->fr_error);
// Iteration over Bodytext
......@@ -325,15 +327,17 @@ class Report {
* 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
* @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) {
private function triggerReport($cur_level = 1, array $super_level_array = array(), $counter = 0) {
$keys = array();
if ($this->fr_error["debug_level"] >= DEBUG_EXTREME) {
// T3 function: debug()
// debug(array('function' => 'triggerReport'));
......@@ -344,9 +348,18 @@ class Report {
$rowTotal = 0;
// CurrentLevel "10.10.50"
$full_level = implode(".", $this->indexArray[$counter]);
if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
$full_level = implode(".", $this->indexArray[$counter]);
} else {
$full_level = '';
}
// Superlevel "10.10"
$full_super_level = implode(".", $super_level_array);
if (isset($super_level_array) && is_array($super_level_array)) {
$full_super_level = implode(".", $super_level_array);
} else {
$full_super_level = '';
}
//condition1: indexArray
//condition2: full_level == Superlevel (but at the length of the Superlevel)
......@@ -354,12 +367,18 @@ class Report {
//True: The cur_level is a subquery -> continue
if ($cur_level != count($this->indexArray[$counter])) {
$full_level = implode(".", $this->indexArray[++$counter]);
++$counter;
if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
$full_level = implode(".", $this->indexArray[$counter]);
} else {
$full_level = '';
}
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);
//TODO dbAlias
// $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);
......@@ -370,7 +389,8 @@ class Report {
$this->fr_error["full_level"] = $full_level . ".sql";
// Setter function to emulate global variables
$this->db->set_fr_error