Database abstraction

This commit is contained in:
Roman Hergenreder 2020-04-02 00:02:51 +02:00
parent 26d28377be
commit 81995b06b8
51 changed files with 1660 additions and 641 deletions

@ -17,13 +17,20 @@ class CreateApiKey extends Request {
} }
$apiKey = generateRandomString(64); $apiKey = generateRandomString(64);
$query = "INSERT INTO ApiKey (user_id, api_key, valid_until) VALUES (?,?,(SELECT DATE_ADD(now(), INTERVAL 30 DAY)))"; $sql = $this->user->getSQL();
$request = new ExecuteStatement($this->user); $validUntil = (new \DateTime())->modify("+30 DAY");
$this->success = $request->execute(array("query" => $query, $this->user->getId(), $apiKey));
$this->lastError = $request->getLastError(); $this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until"))
->addRow($this->user->getId(), $apiKey, $validUntil)
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["api_key"] = $apiKey; $this->result["api_key"] = $apiKey;
$this->result["valid_until"] = "TODO"; $this->result["valid_until"] = $validUntil->getTimestamp();
$this->result["uid"] = $this->user->getSQL()->getLastInsertId(); $this->result["uid"] = $sql->getLastInsertId();
}
return $this->success; return $this->success;
} }
}; };

@ -1,109 +0,0 @@
<?php
namespace Api;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
class ExecuteSelect extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'query' => new StringType('query')
));
$this->isPublic = false;
$this->variableParamCount = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$this->success = false;
$this->result['rows'] = array();
if(count($this->params) === 1) {
$res = $sql->query($this->getParam('query'));
if(!$res) {
$this->lastError = 'Database Error: query() failed with ' . $sql->getLastError();
return false;
}
while($row = $res->fetch_assoc()) {
array_push($this->result['rows'], $row);
}
$this->success = true;
$res->close();
} else {
$aSqlParams = array('');
foreach($this->params as $param) {
if($param->name === 'query') continue;
$value = $param->value;
switch($param->type) {
case Parameter::TYPE_BOOLEAN:
$value = $param->value ? 1 : 0;
case Parameter::TYPE_INT:
$aSqlParams[0] .= 'i';
break;
case Parameter::TYPE_FLOAT:
$aSqlParams[0] .= 'd';
break;
case Parameter::TYPE_DATE:
$value = $value->format('Y-m-d');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_TIME:
$value = $value->format('H:i:s');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_DATE_TIME:
$value = $value->format('Y-m-d H:i:s');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_EMAIL:
default:
$aSqlParams[0] .= 's';
}
$aSqlParams[] = $value;
}
$tmp = array();
foreach($aSqlParams as $key => $value) $tmp[$key] = &$aSqlParams[$key];
if($stmt = $sql->connection->prepare($this->getParam('query'))) {
if(call_user_func_array(array($stmt, "bind_param"), $tmp))
{
if($stmt->execute()) {
$res = $stmt->get_result();
if($res) {
while($row = $res->fetch_assoc()) {
array_push($this->result['rows'], $row);
}
$res->close();
$this->success = true;
} else {
$this->lastError = 'Database Error: execute() failed with ' . $sql->getLastError();
}
} else {
$this->lastError = 'Database Error: get_result() failed with ' . $sql->getLastError();
}
} else {
$this->lastError = 'Database Error: bind_param() failed with ' . $sql->getLastError();
}
$stmt->close();
} else {
$this->lastError = 'Database Error: prepare failed with() ' . $sql->getLastError();
}
}
return $this->success;
}
};
?>

@ -1,97 +0,0 @@
<?php
namespace Api;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
class ExecuteStatement extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'query' => new StringType('query')
));
$this->isPublic = false;
$this->variableParamCount = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->success = false;
$this->result['rows'] = array();
if(count($this->params) == 1) {
$this->success = $this->user->getSQL()->execute($this->getParam('query'));
if(!$this->success) {
$this->lastError = $this->user->getSQL()->getLastError();
}
} else {
$aSqlParams = array('');
foreach($this->params as $param) {
if($param->name === 'query') continue;
$value = $param->value;
if(is_null($value)) {
$aSqlParams[0] .= 's';
} else {
switch($param->type) {
case Parameter::TYPE_BOOLEAN:
$value = $param->value ? 1 : 0;
$aSqlParams[0] .= 'i';
break;
case Parameter::TYPE_INT:
$aSqlParams[0] .= 'i';
break;
case Parameter::TYPE_FLOAT:
$aSqlParams[0] .= 'd';
break;
case Parameter::TYPE_DATE:
$value = $value->format('Y-m-d');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_TIME:
$value = $value->format('H:i:s');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_DATE_TIME:
$value = $value->format('Y-m-d H:i:s');
$aSqlParams[0] .= 's';
break;
case Parameter::TYPE_EMAIL:
default:
$aSqlParams[0] .= 's';
}
}
$aSqlParams[] = $value;
}
$tmp = array();
foreach($aSqlParams as $key => $value) $tmp[$key] = &$aSqlParams[$key];
if($stmt = $this->user->getSQL()->connection->prepare($this->getParam('query'))) {
if(call_user_func_array(array($stmt, "bind_param"), $tmp)) {
if($stmt->execute()) {
$this->result['rows'] = $stmt->affected_rows;
$this->success = true;
} else {
$this->lastError = 'Database Error: execute() failed with ' . $this->user->getSQL()->getLastError();
}
} else {
$this->lastError = 'Database Error: bind_param() failed with ' . $this->user->getSQL()->getLastError();
}
$stmt->close();
} else {
$this->lastError = 'Database Error: prepare() failed with ' . $this->user->getSQL()->getLastError();
}
}
return $this->success;
}
};
?>

@ -1,82 +0,0 @@
<?php
namespace Api\External;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
class RequestData extends \Api\Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
"url" => new StringType("url", 256)
));
$this->isPublic = false;
}
private function requestURL() {
$url = $this->getParam("url");
$ckfile = tempnam("/tmp", 'cookiename');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $ckfile);
curl_setopt($ch, CURLOPT_COOKIEFILE, $ckfile);
$data = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$success = false;
if(curl_errno($ch)) {
$this->lastError = curl_error($ch);
} else if($statusCode != 200) {
$this->lastError = "External Site returned status code: " . $statusCode;
} else {
$this->result["data"] = $data;
$this->result["cached"] = false;
$success = true;
}
unlink($ckfile);
curl_close ($ch);
return $success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$url = $this->getParam("url");
$expires = $this->getParam("expires");
$query = "SELECT data, expires FROM ExternalSiteCache WHERE url=?";
$req = new \Api\ExecuteSelect($this->user);
$this->success = $req->execute(array("query" => $query, $url));
$this->lastError = $req->getLastError();
if($this->success) {
$mustRevalidate = true;
if(!empty($req->getResult()['rows'])) {
$row = $req->getResult()['rows'][0];
if($row["expires"] == null || !isinPast($row["expires"])) {
$mustRevalidate = false;
$this->result["data"] = $row["data"];
$this->result["expires"] = $row["expires"];
$this->result["cached"] = true;
}
}
if($mustRevalidate) {
$this->success = $this->requestURL();
}
}
return $this->success;
}
};
?>

@ -1,44 +0,0 @@
<?php
namespace Api\External;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
class WriteData extends \Api\Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
"url" => new StringType("url", 256),
"data" => new StringType("data", -1),
"expires" => new Parameter("expires", Parameter::TYPE_INT, false, 0),
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$url = $this->getParam("url");
$data = $this->getParam("data");
$expires = $this->getParam("expires");
if($expires > 0) {
$expires = getDateTime(new \DateTime("+${expires} seconds"));
} else {
$expires = null;
}
$query = "INSERT INTO ExternalSiteCache (url, data, expires) VALUES(?,?,?)
ON DUPLICATE KEY UPDATE data=?, expires=?";
$request = new \Api\ExecuteStatement($this->user);
$this->success = $request->execute(array("query" => $query, $url, $data, $expires, $data, $expires));
$this->lastError = $request->getLastError();
return $this->lastError;
}
}
?>

@ -2,6 +2,9 @@
namespace Api; namespace Api;
use \Driver\SQL\Keyword;
use \Driver\SQL\Condition\Compare;
class GetApiKeys extends Request { class GetApiKeys extends Request {
public function __construct($user, $externCall = false) { public function __construct($user, $externCall = false) {
@ -14,16 +17,19 @@ class GetApiKeys extends Request {
return false; return false;
} }
$query = "SELECT ApiKey.uid, ApiKey.api_key, ApiKey.valid_until $sql = $this->user->getSQL();
FROM ApiKey $res = $sql->select("uid", "api_key", "valid_until")
WHERE ApiKey.user_id = ? ->from("ApiKey")
AND ApiKey.valid_until > now()"; ->where(new Compare("user_id", $this->user->getId()))
$request = new ExecuteSelect($this->user); ->where(new Compare("valid_until", new Keyword($sql->currentTimestamp()), ">"))
$this->success = $request->execute(array("query" => $query, $this->user->getId())); ->where(new Compare("active", true))
$this->lastError = $request->getLastError(); ->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) { if($this->success) {
$this->result["api_keys"] = $request->getResult()['rows']; $this->result["api_keys"] = $res;
} }
return $this->success; return $this->success;

@ -13,17 +13,20 @@ class GetLanguages extends Request {
return false; return false;
} }
$query = 'SELECT uid, code, name FROM Language'; $sql = $this->user->getSQL();
$request = new ExecuteSelect($this->user); $res = $sql->select("uid", "code", "name")
$this->success = $request->execute(array('query' => $query)); ->from("Language")
$this->lastError = $request->getLastError(); ->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) { if($this->success) {
$this->result['languages'] = array(); $this->result['languages'] = array();
if(count($request->getResult()['rows']) === 0) { if(empty($res) === 0) {
$this->lastError = L("No languages found"); $this->lastError = L("No languages found");
} else { } else {
foreach($request->getResult()['rows'] as $row) { foreach($res as $row) {
$this->result['languages'][$row['uid']] = $row; $this->result['languages'][$row['uid']] = $row;
} }
} }

@ -4,6 +4,7 @@ namespace Api;
use Api\Parameter\Parameter; use Api\Parameter\Parameter;
use Api\Parameter\StringType; use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
class Login extends Request { class Login extends Request {
@ -42,26 +43,29 @@ class Login extends Request {
$password = $this->getParam('password'); $password = $this->getParam('password');
$stayLoggedIn = $this->getParam('stayLoggedIn'); $stayLoggedIn = $this->getParam('stayLoggedIn');
$query = 'SELECT User.uid, User.password, User.salt FROM User WHERE User.name=?'; $sql = $this->user->getSQL();
$request = new ExecuteSelect($this->user); $res = $sql->select("User.uid", "User.password", "User.salt")
$this->success = $request->execute(array('query' => $query, $username)); ->from("User")
$this->lastError = $request->getLastError(); ->where(new Compare("User.name", $username))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) { if($this->success) {
$this->success = false; if(count($res) === 0) {
if(count($request->getResult()['rows']) === 0) {
return $this->wrongCredentials(); return $this->wrongCredentials();
$this->lastError = L('Wrong username or password');
} else { } else {
$row = $request->getResult()['rows'][0]; $row = $res[0];
$salt = $row['salt']; $salt = $row['salt'];
$uid = $row['uid']; $uid = $row['uid'];
$hash = hash('sha256', $password . $salt); $hash = hash('sha256', $password . $salt);
if($hash === $row['password']) { if($hash === $row['password']) {
if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) { if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) {
return $this->createError("Error creating Session"); return $this->createError("Error creating Session: " . $sql->getLastError());
} else { } else {
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
$this->success = true;
} }
} }
else { else {

@ -15,9 +15,9 @@ class Logout extends Request {
return false; return false;
} }
$this->success = true; $this->success = $this->user->logout();
$this->user->logout(); $this->lastError = $this->user->getSQL()->getLastError();
return true; return $this->success;
} }
}; };

@ -1,7 +1,10 @@
<?php <?php
namespace Api; namespace Api;
use \Api\Parameter\Parameter; use \Api\Parameter\Parameter;
use \Driver\SQL\Keyword;
use \Driver\SQL\Condition\Compare;
class RefreshApiKey extends Request { class RefreshApiKey extends Request {
@ -14,12 +17,20 @@ class RefreshApiKey extends Request {
private function apiKeyExists() { private function apiKeyExists() {
$id = $this->getParam("id"); $id = $this->getParam("id");
$query = "SELECT * FROM ApiKey WHERE uid = ? AND user_id = ? AND valid_until > now()";
$request = new ExecuteSelect($this->user);
$this->success = $request->execute(array("query" => $query, $id, $this->user->getId()));
$this->lastError = $request->getLastError();
if($this->success && count($request->getResult()['rows']) == 0) { $sql = $this->user->getSQL();
$res = $sql->select("COUNT(*)")
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", new Keyword($sql->currentTimestamp()), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["COUNT(*)"] === 0) {
$this->success = false; $this->success = false;
$this->lastError = "This API-Key does not exist."; $this->lastError = "This API-Key does not exist.";
} }
@ -36,10 +47,18 @@ class RefreshApiKey extends Request {
if(!$this->apiKeyExists()) if(!$this->apiKeyExists())
return false; return false;
$query = "UPDATE ApiKey SET valid_until = (SELECT DATE_ADD(now(), INTERVAL 30 DAY)) WHERE uid = ? AND user_id = ? AND valid_until > now()"; $validUntil = (new \DateTime)->modify("+30 DAY");
$request = new ExecuteStatement($this->user); $sql = $this->user->getSQL();
$this->success = $request->execute(array("query" => $query, $id, $this->user->getId())); $this->success = $sql->update("ApiKey")
$this->lastError = $request->getLastError(); ->set("valid_until", $validUntil)
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["valid_until"] = $validUntil->getTimestamp();
}
return $this->success; return $this->success;
} }

@ -1,7 +1,10 @@
<?php <?php
namespace Api; namespace Api;
use \Api\Parameter\Parameter; use \Api\Parameter\Parameter;
use \Driver\SQL\Keyword;
use \Driver\SQL\Condition\Compare;
class RevokeApiKey extends Request { class RevokeApiKey extends Request {
@ -14,12 +17,20 @@ class RevokeApiKey extends Request {
private function apiKeyExists() { private function apiKeyExists() {
$id = $this->getParam("id"); $id = $this->getParam("id");
$query = "SELECT * FROM ApiKey WHERE uid = ? AND user_id = ? AND valid_until > now()";
$request = new ExecuteSelect($this->user);
$this->success = $request->execute(array("query" => $query, $id, $this->user->getId()));
$this->lastError = $request->getLastError();
if($this->success && count($request->getResult()['rows']) == 0) { $sql = $this->user->getSQL();
$res = $sql->select("COUNT(*)")
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", new Keyword($sql->currentTimestamp()), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["COUNT(*)"] === 0) {
$this->success = false; $this->success = false;
$this->lastError = "This API-Key does not exist."; $this->lastError = "This API-Key does not exist.";
} }
@ -36,10 +47,13 @@ class RevokeApiKey extends Request {
if(!$this->apiKeyExists()) if(!$this->apiKeyExists())
return false; return false;
$query = "DELETE FROM ApiKey WHERE valid_until < now() OR (uid = ? AND user_id = ?)"; $sql = $this->user->getSQL();
$request = new ExecuteStatement($this->user); $this->success = $sql->update("ApiKey")
$this->success = $request->execute(array("query" => $query, $id, $this->user->getId())); ->set("active", false)
$this->lastError = $request->getLastError(); ->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
return $this->success; return $this->success;
} }

@ -4,6 +4,8 @@ namespace Api;
use Api\Parameter\Parameter; use Api\Parameter\Parameter;
use Api\Parameter\StringType; use Api\Parameter\StringType;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Condition\Compare;
class SetLanguage extends Request { class SetLanguage extends Request {
@ -24,16 +26,20 @@ class SetLanguage extends Request {
return $this->createError(L("Either langId or langCode must be given")); return $this->createError(L("Either langId or langCode must be given"));
} }
$query = "SELECT uid, code, name FROM Language WHERE uid=? OR code=?"; $res = $this->user->getSQL()
$request = new ExecuteSelect($this->user); ->select("uid", "code", "name")
$this->success = $request->execute(array("query" => $query, $langId, $langCode)); ->from("Language")
$this->lastError = $request->getLastError(); ->where(new CondOr(new Compare("uid", $langId), new Compare("code", $langCode)))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $this->user->getSQL()->getLastError();
if ($this->success) { if ($this->success) {
if(count($request->getResult()['rows']) == 0) { if(count($res) == 0) {
return $this->createError(L("This Language does not exist")); return $this->createError(L("This Language does not exist"));
} else { } else {
$row = $request->getResult()['rows'][0]; $row = $res[0];
$this->language = \Objects\Language::newInstance($row['uid'], $row['code'], $row['name']); $this->language = \Objects\Language::newInstance($row['uid'], $row['code'], $row['name']);
if(!$this->language) { if(!$this->language) {
return $this->createError(L("Error while loading language")); return $this->createError(L("Error while loading language"));
@ -47,11 +53,13 @@ class SetLanguage extends Request {
private function updateLanguage() { private function updateLanguage() {
$languageId = $this->language->getId(); $languageId = $this->language->getId();
$userId = $this->user->getId(); $userId = $this->user->getId();
$sql = $this->user->getSQL();
$query = "UPDATE User SET language_id = ? WHERE uid = ?"; $this->success = $sql->update("User")
$request = new ExecuteStatement($this->user); ->set("language_id", $languageId)
$this->success = $request->execute(array("query" => $query, $languageId, $userId)); ->where(new Compare("uid", $userId))
$this->lastError = $request->getLastError(); ->execute();
$this->lastError = $sql->getLastError();
return $this->success; return $this->success;
} }

@ -0,0 +1,94 @@
<?php
namespace Configuration;
use \Driver\SQL\Query\CreateTable;
use \Driver\SQL\Query\Insert;
use \Driver\SQL\Column\Column;
use \Driver\SQL\Strategy\UpdateStrategy;
use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\CascadeStrategy;
class CreateDatabase {
public static function createQueries($sql) {
$queries = array();
// Language
$queries[] = $sql->createTable("Language")
->addSerial("uid")
->addString("code", 5)
->addString("name", 32)
->primaryKey("uid")
->unique("code")
->unique("name");
$queries[] = $sql->insert("Language", array("uid", "code", "name"))
->addRow(1, "en_US", 'American English')
->addRow(2, "de_DE", 'Deutsch Standard')
->onDuplicateKeyStrategy(new UpdateStrategy(array("name" => new Column("name"))));
$queries[] = $sql->createTable("User")
->addSerial("uid")
->addString("email", 64, true)
->addString("name", 32)
->addString("salt", 16)
->addString("password", 64)
->addInt("language_id", true, 1)
->primaryKey("uid")
->unique("email")
->unique("name")
->foreignKey("language_id", "Language", "uid", new SetNullStrategy());
$queries[] = $sql->createTable("Session")
->addSerial("uid")
->addBool("active", true)
->addDateTime("expires")
->addInt("user_id")
->addString("ipAddress", 45)
->addString("os", 64)
->addString("browser", 64)
->addJson("data", false, '{}')
->addBool("stay_logged_in", true)
->primaryKey("uid", "user_id")
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("UserToken")
->addInt("user_id")
->addString("token", 36)
->addEnum("type", array("password_reset", "confirmation"))
->addDateTime("valid_until")
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("Group")
->addSerial("uid")
->addString("name", 32)
->primaryKey("uid")
->unique("name");
$queries[] = $sql->insert("Group", array("uid", "name"))
->addRow(1, "Default")
->addRow(2, "Administrator")
->onDuplicateKeyStrategy(new UpdateStrategy(array("name" => new Column("name"))));
$queries[] = $sql->createTable("UserGroup")
->addInt("user_id")
->addInt("group_id")
->unique("user_id", "group_id")
->foreignKey("user_id", "User", "uid")
->foreignKey("group_id", "Group", "uid");
$queries[] = $sql->createTable("ApiKey")
->addSerial("uid")
->addInt("user_id")
->addBool("active", true)
->addString("api_key", 64)
->addDateTime("valid_until")
->primaryKey("uid")
->foreignKey("user_id", "User", "uid");
return $queries;
}
}
?>

@ -20,11 +20,11 @@ CREATE TABLE IF NOT EXISTS User (
`password` varchar(64) NOT NULL, `password` varchar(64) NOT NULL,
`language_id` int(11) DEFAULT 1, `language_id` int(11) DEFAULT 1,
PRIMARY KEY (`uid`), PRIMARY KEY (`uid`),
FOREIGN KEY (`language_id`) REFERENCES `Language` (`uid`) ON DELETE SET DEFAULT FOREIGN KEY (`language_id`) REFERENCES `Language` (`uid`) ON DELETE SET NULL
); );
CREATE TABLE IF NOT EXISTS UserInvitation ( CREATE TABLE IF NOT EXISTS UserInvitation (
`email` VARCHAR(256) NOT NULL, `email` VARCHAR(64) NOT NULL,
`token` VARCHAR(36) UNIQUE NOT NULL, `token` VARCHAR(36) UNIQUE NOT NULL,
`valid_until` DATETIME NOT NULL `valid_until` DATETIME NOT NULL
); );

@ -97,18 +97,15 @@ namespace Documents\Install {
return self::DATABASE_CONFIGURATION; return self::DATABASE_CONFIGURATION;
} }
$request = new \Api\ExecuteSelect($user); $res = $user->getSQL()->select("COUNT(*) as count")->from("User")->execute();
$success = $request->execute(array("query" => "SELECT COUNT(*) AS count FROM User")); if ($res === FALSE) {
$this->errorString = $request->getLastError(); return self::DATABASE_CONFIGURATION;
} else {
if($success) { if ($res[0]["count"] > 0) {
if($request->getResult()['rows'][0]["count"] > 0) {
$step = self::ADD_MAIL_SERVICE; $step = self::ADD_MAIL_SERVICE;
} else { } else {
return self::CREATE_USER; return self::CREATE_USER;
} }
} else {
return self::DATABASE_CONFIGURATION;
} }
if($step === self::ADD_MAIL_SERVICE && $config->isFilePresent("Mail")) { if($step === self::ADD_MAIL_SERVICE && $config->isFilePresent("Mail")) {
@ -163,6 +160,9 @@ namespace Documents\Install {
$username = $this->getParameter("username"); $username = $this->getParameter("username");
$password = $this->getParameter("password"); $password = $this->getParameter("password");
$database = $this->getParameter("database"); $database = $this->getParameter("database");
$type = $this->getParameter("type");
$encoding = $this->getParameter("encoding");
$encoding = ($encoding ? $encoding : "UTF-8");
$success = true; $success = true;
$missingInputs = array(); $missingInputs = array();
@ -191,44 +191,55 @@ namespace Documents\Install {
$missingInputs[] = "Database"; $missingInputs[] = "Database";
} }
if(is_null($type) || empty($type)) {
$success = false;
$missingInputs[] = "Type";
}
$supportedTypes = array("mysql"); # , "oracle", "postgres");
if(!$success) { if(!$success) {
$msg = "Please fill out the following inputs:<br>" . $msg = "Please fill out the following inputs:<br>" .
$this->createUnorderedList($missingInputs); $this->createUnorderedList($missingInputs);
} else if(!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) { } else if(!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) {
$msg = "Port must be in range of 1-65535."; $msg = "Port must be in range of 1-65535.";
$success = false; $success = false;
} else if(!in_array($type, $supportedTypes)) {
$msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes);
$success = false;
} else { } else {
$connectionData = new \Objects\ConnectionData($host, $port, $username, $password); $connectionData = new \Objects\ConnectionData($host, $port, $username, $password);
$connectionData->setProperty('database', $database); $connectionData->setProperty('database', $database);
$connectionData->setProperty('encoding', 'utf8'); $connectionData->setProperty('encoding', $encoding);
$sql = new \Driver\SQL($connectionData); $connectionData->setProperty('type', $type);
$success = $sql->connect(); $sql = \Driver\SQL\SQL::createConnection($connectionData);
$success = false;
if(!$success) { if(!($sql instanceof \Driver\SQL\SQL)) {
$msg = "Error connecting to database: " . str($sql);
} else if(!$sql->isConnected()) {
$msg = "Error connecting to database:<br>" . $sql->getLastError(); $msg = "Error connecting to database:<br>" . $sql->getLastError();
} else { } else {
try {
$msg = "Error loading database script $this->databaseScript"; $msg = "";
$commands = file_get_contents($this->databaseScript); $success = true;
$success = $sql->executeMulti($commands); $queries = \Configuration\CreateDatabase::createQueries($sql);
if(!$success) { foreach($queries as $query) {
$msg = $sql->getLastError(); if (!$query->execute()) {
} else if(!$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) { $msg = "Error creating tables: " . $sql->getLastError();
$success = false;
break;
}
}
if($success && !$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) {
$success = false; $success = false;
$msg = "Unable to write file"; $msg = "Unable to write file";
} else {
$msg = "";
} }
} catch(Exception $e) {
$success = false;
$msg .= ": " . $e->getMessage();
} }
if($sql) { if($sql) {
$sql->close(); $sql->close();
} }
} }
}
return array("success" => $success, "msg" => $msg); return array("success" => $success, "msg" => $msg);
} }
@ -280,10 +291,13 @@ namespace Documents\Install {
} else { } else {
$salt = generateRandomString(16); $salt = generateRandomString(16);
$hash = hash('sha256', $password . $salt); $hash = hash('sha256', $password . $salt);
$query = "INSERT INTO User (name, salt, password) VALUES (?,?,?)"; $sql = $user->getSQL();
$req = new \Api\ExecuteStatement($user);
$success = $req->execute(array("query" => $query, $username, $salt, $hash)); $success = $sql->insert("User", array("name", "salt", "password"))
$nsg = $req->getLastError(); ->addRow($username, $salt, $hash)
->execute();
$msg = $sql->getLastError();
} }
return array("msg" => $msg, "success" => $success); return array("msg" => $msg, "success" => $success);
@ -293,9 +307,9 @@ namespace Documents\Install {
$user = $this->getDocument()->getUser(); $user = $this->getDocument()->getUser();
if($this->getParameter("prev") === "true") { if($this->getParameter("prev") === "true") {
$req = new \Api\ExecuteStatement($user); $sql = $user->getSQL();
$success = $req->execute(array("query" => "TRUNCATE User")); $success = $sql->delete("User")->execute();
$msg = $req->getLastError(); $msg = $sql->getLastError();
return array("success" => $success, "msg" => $msg); return array("success" => $success, "msg" => $msg);
} }
@ -462,14 +476,16 @@ namespace Documents\Install {
$attributes = array( $attributes = array(
"name" => $name, "name" => $name,
"id" => $name, "id" => $name,
"class" => "form-control", "class" => "form-control"
"type" => $type,
); );
if(isset($formItem["required"]) && $formItem["required"]) { if(isset($formItem["required"]) && $formItem["required"]) {
$attributes["required"] = ""; $attributes["required"] = "";
} }
if ($type !== "select") {
$attributes["type"] = $type;
if(isset($formItem["value"]) && $formItem["value"]) { if(isset($formItem["value"]) && $formItem["value"]) {
$attributes["value"] = $formItem["value"]; $attributes["value"] = $formItem["value"];
} }
@ -482,20 +498,36 @@ namespace Documents\Install {
if(isset($formItem["step"]) && is_numeric($formItem["step"])) if(isset($formItem["step"]) && is_numeric($formItem["step"]))
$attributes["step"] = $formItem["step"]; $attributes["step"] = $formItem["step"];
} }
}
$attributes = str_replace("+", " ", str_replace("&", "\" ", str_replace("=", "=\"", http_build_query($attributes)))) . "\""; $replacements = array("+" => " ", "&" => "\" ", "=" => "=\"");
$attributes = http_build_query($attributes) . "\"";
foreach($replacements as $key => $val) {
$attributes = str_replace($key, $val, $attributes);
}
if ($type === "select") {
$items = $formItem["items"] ?? array();
$element = "<select $attributes>";
foreach($items as $key => $val) {
$element .= "<option value=\"$key\">$val</option>";
}
$element .= "</select>";
} else {
$element = "<input $attributes>";
}
if(!$inline) { if(!$inline) {
return return
"<div class=\"d-block my-3\"> "<div class=\"d-block my-3\">
<label for=\"$name\">$title</label> <label for=\"$name\">$title</label>
<input $attributes> $element
</div>"; </div>";
} else { } else {
return return
"<div class=\"col-md-6 mb-3\"> "<div class=\"col-md-6 mb-3\">
<label for=\"$name\">$title</label> <label for=\"$name\">$title</label>
<input $attributes> $element
</div>"; </div>";
} }
} }
@ -510,6 +542,9 @@ namespace Documents\Install {
self::DATABASE_CONFIGURATION => array( self::DATABASE_CONFIGURATION => array(
"title" => "Database configuration", "title" => "Database configuration",
"form" => array( "form" => array(
array("title" => "Database Type", "name" => "type", "type" => "select", "required" => true, "items" => array(
"mysql" => "MySQL", "oracle" => "Oracle", "postgres" => "PostgreSQL"
)),
array("title" => "Username", "name" => "username", "type" => "text", "required" => true), array("title" => "Username", "name" => "username", "type" => "text", "required" => true),
array("title" => "Password", "name" => "password", "type" => "password"), array("title" => "Password", "name" => "password", "type" => "password"),
array("title" => "Database", "name" => "database", "type" => "text", "required" => true), array("title" => "Database", "name" => "database", "type" => "text", "required" => true),
@ -523,6 +558,10 @@ namespace Documents\Install {
"value" => "3306", "min" => "1", "max" => "65535", "row" => true "value" => "3306", "min" => "1", "max" => "65535", "row" => true
) )
)), )),
array(
"title" => "Encoding", "name" => "encoding", "type" => "text", "required" => false,
"value" => "UTF-8"
),
) )
), ),
self::CREATE_USER => array( self::CREATE_USER => array(

@ -1,133 +0,0 @@
<?php
namespace Driver;
class SQL {
public $connection;
public $lastError;
private $connectionData;
public function __construct($connectionData) {
$this->connection = NULL;
$this->lastError = 'Not connected';
$this->connectionData = $connectionData;
}
public function connect() {
if(!is_null($this->connection))
return true;
@$this->connection = mysqli_connect(
$this->connectionData->getHost(),
$this->connectionData->getLogin(),
$this->connectionData->getPassword(),
$this->connectionData->getProperty('database'),
$this->connectionData->getPort()
);
if (mysqli_connect_errno($this->connection)) {
$this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error();
$this->connection = NULL;
return false;
}
mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding'));
return true;
}
public function disconnect() {
if(is_null($this->connection))
return;
mysqli_close($this->connection);
$this->connection = NULL;
}
public function isConnected() {
return !is_null($this->connection);
}
public function getLastError() {
return empty(trim($this->lastError)) ? mysqli_error($this->connection) . " " . $this->getLastErrorNumber() : trim($this->lastError);
}
public function setLastError($str) {
$this->lastError = $str;
}
public function getLastErrorNumber() {
return mysqli_errno($this->connection);
}
public function getLastInsertId() {
return $this->connection->insert_id;
}
public function close() {
if(!is_null($this->connection)) {
$this->connection->close();
}
}
public function getAffectedRows() {
return $this->connection->affected_rows;
}
public function execute($query) {
if(!$this->isConnected()) {
return false;
}
if(!mysqli_query($this->connection, $query)) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return true;
}
public function executeMulti($queries) {
if(!$this->isConnected()) {
return false;
}
if(!$this->connection->multi_query($queries)) {
$this->lastError = mysqli_error($this->connection);
return false;
}
while (($success = $this->connection->next_result())) {
if (!$this->connection->more_results()) break;
}
if(!$success) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return true;
}
public function query($query) {
if(!$this->isConnected()) {
return false;
}
$res = mysqli_query($this->connection, $query);
if(!$res) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return $res;
}
public static function createConnection($connectionData) {
$sql = new SQL($connectionData);
$sql->connect();
return $sql;
}
}
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Column;
class BoolColumn extends Column {
public function __construct($name, $defaultValue=false) {
parent::__construct($name, false, $defaultValue);
}
}
?>

@ -0,0 +1,23 @@
<?php
namespace Driver\SQL\Column;
class Column {
private $name;
private $nullable;
private $defaultValue;
public function __construct($name, $nullable = false, $defaultValue = NULL) {
$this->name = $name;
$this->nullable = $nullable;
$this->defaultValue = $defaultValue;
}
public function getName() { return $this->name; }
public function notNull() { return !$this->nullable; }
public function getDefaultValue() { return $this->defaultValue; }
}
?>

@ -0,0 +1,12 @@
<?php
namespace Driver\SQL\Column;
class DateTimeColumn extends Column {
public function __construct($name, $nullable=false, $defaultValue=NULL) {
parent::__construct($name, $nullable, $defaultValue);
}
}
?>

@ -0,0 +1,17 @@
<?php
namespace Driver\SQL\Column;
class EnumColumn extends Column {
private $values;
public function __construct($name, $values, $nullable=false, $defaultValue=NULL) {
parent::__construct($name, $nullable, $defaultValue);
$this->values = $values;
}
public function getValues() { return $this->values; }
}
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Column;
class IntColumn extends Column {
public function __construct($name, $nullable=false, $defaultValue=NULL) {
parent::__construct($name, $nullable, $defaultValue);
}
}
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Column;
class JsonColumn extends Column {
public function __construct($name, $nullable=false, $defaultValue=null) {
parent::__construct($name, $nullable, $defaultValue);
}
}
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Column;
class SerialColumn extends Column {
public function __construct($name, $defaultValue=NULL) {
parent::__construct($name, false, $defaultValue); # not nullable
}
}
?>

@ -0,0 +1,17 @@
<?php
namespace Driver\SQL\Column;
class StringColumn extends Column {
private $maxSize;
public function __construct($name, $maxSize=null, $nullable=false, $defaultValue=null) {
parent::__construct($name, $nullable, $defaultValue);
$this->maxSize = $maxSize;
}
public function getMaxSize() { return $this->maxSize; }
}
?>

@ -0,0 +1,19 @@
<?php
namespace Driver\SQL\Condition;
class Compare extends Condition {
public function __construct($col, $val, $operator='=') {
$this->operator = $operator;
$this->column = $col;
$this->value = $val;
}
public function getColumn() { return $this->column; }
public function getValue() { return $this->value; }
public function getOperator() { return $this->operator; }
}
?>

@ -0,0 +1,16 @@
<?php
namespace Driver\SQL\Condition;
class CondAnd extends Condition {
private $conditions;
public function __construct(...$conditions) {
$this->conditions = $conditions;
}
public function getConditions() { return $this->conditions; }
}
?>

@ -0,0 +1,15 @@
<?php
namespace Driver\SQL\Condition;
class CondBool extends Condition {
public function __construct($val) {
$this->value = $val;
}
public function getValue() { return $this->value; }
}
?>

@ -0,0 +1,16 @@
<?php
namespace Driver\SQL\Condition;
class CondOr extends Condition {
private $conditions;
public function __construct(...$conditions) {
$this->conditions = $conditions;
}
public function getConditions() { return $this->conditions; }
}
?>

@ -0,0 +1,9 @@
<?php
namespace Driver\SQL\Condition;
abstract class Condition {
}
?>

@ -0,0 +1,16 @@
<?php
namespace Driver\SQL\Constraint;
abstract class Constraint {
private $columnName;
public function __construct($columnName) {
$this->columnName = $columnName;
}
public function getColumnName() { return $this->columnName; }
};
?>

@ -0,0 +1,23 @@
<?php
namespace Driver\SQL\Constraint;
class ForeignKey extends Constraint {
private $referencedTable;
private $referencedColumn;
private $strategy;
public function __construct($name, $refTable, $refColumn, $strategy = NULL) {
parent::__construct($name);
$this->referencedTable = $refTable;
$this->referencedColumn = $refColumn;
$this->strategy = $strategy;
}
public function getReferencedTable() { return $this->referencedTable; }
public function getReferencedColumn() { return $this->referencedColumn; }
public function onDelete() { return $this->strategy; }
};
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Constraint;
class PrimaryKey extends Constraint {
public function __construct(...$names) {
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
}
};
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Constraint;
class Unique extends Constraint {
public function __construct(...$names) {
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
}
};
?>

@ -0,0 +1,26 @@
<?php
namespace Driver\SQL;
class Join {
private $type;
private $table;
private $columnA;
private $columnB;
public function __construct($type, $table, $columnA, $columnB) {
$this->tpye = $type;
$this->table = $table;
$this->columnA = $columnA;
$this->columnB = $columnB;
}
public function getType() { return $this->type; }
public function getTable() { return $this->table; }
public function getColumnA() { return $this->columnA; }
public function getColumnB() { return $this->columnB; }
}
?>

@ -0,0 +1,17 @@
<?php
namespace Driver\SQL;
class Keyword {
private $value;
public function __construct($value) {
$this->value = $value;
}
public function getValue() { return $this->value; }
}
?>

@ -0,0 +1,430 @@
<?php
namespace Driver\SQL;
use \Api\Parameter\Parameter;
use \Driver\SQL\Column\Column;
use \Driver\SQL\Column\IntColumn;
use \Driver\SQL\Column\SerialColumn;
use \Driver\SQL\Column\StringColumn;
use \Driver\SQL\Column\EnumColumn;
use \Driver\SQL\Column\DateTimeColumn;
use Driver\SQL\Column\BoolColumn;
use Driver\SQL\Column\JsonColumn;
use \Driver\SQL\Strategy\CascadeStrategy;
use \Driver\SQL\Strategy\SetDefaultStrategy;
use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\UpdateStrategy;
use \Driver\SQL\Constraint\Unique;
use \Driver\SQL\Constraint\PrimaryKey;
use \Driver\SQL\Constraint\ForeignKey;
class MySQL extends SQL {
public function __construct($connectionData) {
parent::__construct("mysql", $connectionData);
}
public function connect() {
if(!is_null($this->connection)) {
return true;
}
@$this->connection = mysqli_connect(
$this->connectionData->getHost(),
$this->connectionData->getLogin(),
$this->connectionData->getPassword(),
$this->connectionData->getProperty('database'),
$this->connectionData->getPort()
);
if (mysqli_connect_errno($this->connection)) {
$this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error();
$this->connection = NULL;
return false;
}
mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding'));
return true;
}
public function disconnect() {
if(is_null($this->connection)) {
return true;
}
mysqli_close($this->connection);
$this->connection = NULL;
}
public function getLastError() {
$lastError = parent::getLastError();
if (empty($lastError)) {
$lastError = mysqli_error($this->connection);
}
return $lastError;
}
private function getPreparedParams($values) {
$sqlParams = array('');
foreach($values as $value) {
$paramType = Parameter::parseType($value);
switch($paramType) {
case Parameter::TYPE_BOOLEAN:
$value = $value ? 1 : 0;
case Parameter::TYPE_INT:
$sqlParams[0] .= 'i';
break;
case Parameter::TYPE_FLOAT:
$sqlParams[0] .= 'd';
break;
case Parameter::TYPE_DATE:
$value = $value->format('Y-m-d');
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_TIME:
$value = $value->format('H:i:s');
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_DATE_TIME:
$value = $value->format('Y-m-d H:i:s');
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_EMAIL:
default:
$sqlParams[0] .= 's';
}
$sqlParams[] = $value;
}
return $sqlParams;
}
protected function execute($query, $values = NULL, $returnValues = false) {
$resultRows = array();
$this->lastError = "";
if (is_null($values) || empty($values)) {
$res = mysqli_query($this->connection, $query);
$success = $res !== FALSE;
if ($success && $returnValues) {
while($row = $res->fetch_assoc()) {
$resultRows[] = $row;
}
$res->close();
}
} else if($stmt = $this->connection->prepare($query)) {
$success = false;
$sqlParams = $this->getPreparedParams($values);
$tmp = array();
foreach($sqlParams as $key => $value) $tmp[$key] = &$sqlParams[$key];
if(call_user_func_array(array($stmt, "bind_param"), $tmp)) {
if($stmt->execute()) {
if ($returnValues) {
$res = $stmt->get_result();
if($res) {
while($row = $res->fetch_assoc()) {
$resultRows[] = $row;
}
$res->close();
$success = true;
} else {
$this->lastError = "PreparedStatement::get_result failed: $stmt->error ($stmt->errno)";
}
} else {
$success = true;
}
} else {
$this->lastError = "PreparedStatement::execute failed: $stmt->error ($stmt->errno)";
}
} else {
$this->lastError = "PreparedStatement::prepare failed: $stmt->error ($stmt->errno)";
}
$stmt->close();
} else {
$success = false;
}
return ($success && $returnValues) ? $resultRows : $success;
}
public function executeCreateTable($createTable) {
$tableName = $createTable->getTableName();
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
$entries = array();
foreach($createTable->getColumns() as $column) {
$entries[] = ($tmp = $this->getColumnDefinition($column));
if (is_null($tmp)) {
return false;
}
}
foreach($createTable->getConstraints() as $constraint) {
$entries[] = ($tmp = $this->getConstraintDefinition($constraint));
if (is_null($tmp)) {
return false;
}
}
$entries = implode(",", $entries);
$query = "CREATE TABLE$ifNotExists `$tableName` ($entries)";
return $this->execute($query);
}
public function executeInsert($insert) {
$tableName = $insert->getTableName();
$columns = $insert->getColumns();
$rows = $insert->getRows();
$onDuplicateKey = $insert->onDuplicateKey() ?? "";
if (empty($rows)) {
$this->lastError = "No rows to insert given.";
return false;
}
if (is_null($columns) || empty($columns)) {
$columns = "";
$numColumns = count($rows[0]);
} else {
$numColumns = count($columns);
$columns = " (`" . implode("`, `", $columns) . "`)";
}
$numRows = count($rows);
$parameters = array();
$values = implode(",", array_fill(0, $numRows, "(" . implode(",", array_fill(0, $numColumns, "?")) . ")"));
foreach($rows as $row) {
$parameters = array_merge($parameters, $row);
}
if ($onDuplicateKey) {
if ($onDuplicateKey instanceof UpdateStrategy) {
$updateValues = array();
foreach($onDuplicateKey->getValues() as $key => $value) {
if ($value instanceof Column) {
$columnName = $value->getName();
$updateValues[] = "`$key`=`$columnName`";
} else {
$updateValues[] = "`$key`=?";
$parameters[] = $value;
}
}
$onDuplicateKey = " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues);
} else {
$strategy = get_class($onDuplicateKey);
$this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet.";
return false;
}
}
$query = "INSERT INTO `$tableName`$columns VALUES$values$onDuplicateKey";
$success = $this->execute($query, $parameters);
if($success) {
$this->lastInsertId = mysqli_insert_id($this->connection);
}
return $success;
}
public function executeSelect($select) {
$columns = implode(",", $select->getColumns());
$tables = $select->getTables();
$params = array();
if (is_null($tables) || empty($tables)) {
return "SELECT $columns";
} else {
$tables = implode(",", $tables);
}
$conditions = $select->getConditions();
if (!empty($conditions)) {
$condition = " WHERE " . $this->buildCondition($conditions, $params);
} else {
$condition = "";
}
$joinStr = "";
$joins = $select->getJoins();
if (!empty($joins)) {
$joinStr = "";
foreach($joins as $join) {
$type = $join->getType();
$joinTable = $join->getTable();
$columnA = $join->getColumnA();
$columnB = $join->getColumnB();
$joinStr .= " $type JOIN $joinTable ON $columnA=$columnB";
}
}
$orderBy = "";
$limit = "";
$offset = "";
$query = "SELECT $columns FROM $tables$joinStr$condition$orderBy$limit$offset";
return $this->execute($query, $params, true);
}
public function executeDelete($delete) {
$table = $delete->getTable();
$conditions = $delete->getConditions();
if (!empty($conditions)) {
$condition = " WHERE " . $this->buildCondition($conditions, $params);
} else {
$condition = "";
}
$query = "DELETE FROM $table$condition";
return $this->execute($query);
}
public function executeTruncate($truncate) {
return $this->execute("TRUNCATE " . $truncate->getTable());
}
public function executeUpdate($update) {
$params = array();
$table = $update->getTable();
$valueStr = array();
foreach($update->getValues() as $key => $val) {
$valueStr[] = "$key=" . $this->addValue($val, $params);
}
$valueStr = implode(",", $valueStr);
$conditions = $update->getConditions();
if (!empty($conditions)) {
$condition = " WHERE " . $this->buildCondition($conditions, $params);
} else {
$condition = "";
}
$query = "UPDATE $table SET $valueStr$condition";
return $this->execute($query, $params);
}
protected function buildCondition($condition, &$params) {
if ($condition instanceof \Driver\SQL\Condition\CondOr) {
$conditions = array();
foreach($condition->getConditions() as $cond) {
$conditions[] = $this->buildCondition($cond, $params);
}
return "(" . implode(" OR ", $conditions) . ")";
} else if ($condition instanceof \Driver\SQL\Condition\Compare) {
$column = $condition->getColumn();
$value = $condition->getValue();
$operator = $condition->getOperator();
return $column . $operator . $this->addValue($value, $params);
} else if ($condition instanceof \Driver\SQL\Condition\CondBool) {
return $condition->getValue();
} else if (is_array($condition)) {
if (count($condition) == 1) {
return $this->buildCondition($condition[0], $params);
} else {
$conditions = array();
foreach($condition as $cond) {
$conditions[] = $this->buildCondition($cond, $params);
}
return implode(" AND ", $conditions);
}
}
}
public function getColumnDefinition($column) {
$columnName = $column->getName();
if ($column instanceof StringColumn) {
$maxSize = $column->getMaxSize();
if ($maxSize) {
$type = "VARCHAR($maxSize)";
} else {
$type = "TEXT";
}
} else if($column instanceof SerialColumn) {
$type = "INTEGER AUTO_INCREMENT";
} else if($column instanceof IntColumn) {
$type = "INTEGER";
} else if($column instanceof DateTimeColumn) {
$type = "DATETIME";
} else if($column instanceof EnumColumn) {
$values = array();
foreach($column->getValues() as $value) {
$values[] = $this->getValueDefinition($value);
}
$values = implode(",", $values);
$type = "ENUM($values)";
} else if($column instanceof BoolColumn) {
$type = "BOOLEAN";
} else if($column instanceof JsonColumn) {
$type = "JSON";
} else {
$this->lastError = "Unsupported Column Type: " . get_class($column);
return NULL;
}
$notNull = $column->notNull() ? " NOT NULL" : "";
$defaultValue = (!is_null($column->getDefaultValue()) || !$column->notNull()) ? " DEFAULT " . $this->getValueDefinition($column->getDefaultValue()) : "";
return "`$columnName` $type$notNull$defaultValue";
}
public function getConstraintDefinition($constraint) {
$columnName = $constraint->getColumnName();
if ($constraint instanceof PrimaryKey) {
if (is_array($columnName)) $columnName = implode('`,`', $columnName);
return "PRIMARY KEY (`$columnName`)";
} else if ($constraint instanceof Unique) {
if (is_array($columnName)) $columnName = implode('`,`', $columnName);
return "UNIQUE (`$columnName`)";
} else if ($constraint instanceof ForeignKey) {
$refTable = $constraint->getReferencedTable();
$refColumn = $constraint->getReferencedColumn();
$strategy = $constraint->onDelete();
$code = "FOREIGN KEY (`$columnName`) REFERENCES `$refTable` (`$refColumn`)";
if ($strategy instanceof SetDefaultStrategy) {
$code .= " ON DELETE SET DEFAULT";
} else if($strategy instanceof SetNullStrategy) {
$code .= " ON DELETE SET NULL";
} else if($strategy instanceof CascadeStrategy) {
$code .= " ON DELETE CASCADE";
}
return $code;
}
}
// TODO: check this please..
// TODO: Constants??
public function getValueDefinition($value) {
if (is_numeric($value) || is_bool($value)) {
return $value;
} else if(is_null($value)) {
return "NULL";
} else if($value instanceof Keyword) {
return $value->getValue();
} else {
$str = addslashes($value);
return "'$str'";
}
}
public function currentTimestamp() {
return "NOW()";
}
};

@ -0,0 +1,97 @@
<?php
namespace Driver\SQL\Query;
use Driver\SQL\Column\SerialColumn;
use Driver\SQL\Column\StringColumn;
use Driver\SQL\Column\IntColumn;
use Driver\SQL\Column\DateTimeColumn;
use Driver\SQL\Column\EnumColumn;
use Driver\SQL\Column\BoolColumn;
use Driver\SQL\Column\JsonColumn;
use Driver\SQL\Constraint\PrimaryKey;
use Driver\SQL\Constraint\Unique;
use Driver\SQL\Constraint\ForeignKey;
class CreateTable extends Query {
private $tableName;
private $columns;
private $constraints;
private $ifNotExists;
public function __construct($sql, $name) {
parent::__construct($sql);
$this->tableName = $name;
$this->columns = array();
$this->constraints = array();
$this->ifNotExists = false;
}
public function addSerial($name) {
$this->columns[$name] = new SerialColumn($name);
return $this;
}
public function addString($name, $maxSize=NULL, $nullable=false, $defaultValue=NULL) {
$this->columns[$name] = new StringColumn($name, $maxSize, $nullable, $defaultValue);
return $this;
}
public function addDateTime($name, $nullable=false, $defaultNow=false) {
$this->columns[$name] = new DateTimeColumn($name, $nullable, $defaultNow);
return $this;
}
public function addInt($name, $nullable=false, $defaultValue=NULL) {
$this->columns[$name] = new IntColumn($name, $nullable, $defaultValue);
return $this;
}
public function addBool($name, $defaultValue=false) {
$this->columns[$name] = new BoolColumn($name, $defaultValue);
return $this;
}
public function addJson($name, $nullable=false, $defaultValue=NULL) {
$this->columns[$name] = new JsonColumn($name, $nullable, $defaultValue);
return $this;
}
public function addEnum($name, $values, $nullable=false, $defaultValue=NULL) {
$this->columns[$name] = new EnumColumn($name, $values, $nullable, $defaultValue);
return $this;
}
public function primaryKey(...$names) {
$this->constraints[] = new PrimaryKey($names);
return $this;
}
public function unique(...$names) {
$this->constraints[] = new Unique($names);
return $this;
}
public function foreignKey($name, $refTable, $refColumn, $strategy = NULL) {
$this->constraints[] = new ForeignKey($name, $refTable, $refColumn, $strategy);
return $this;
}
public function onlyIfNotExists() {
$this->ifNotExists = true;
return $this;
}
public function execute() {
return $this->sql->executeCreateTable($this);
}
public function ifNotExists() { return $this->ifNotExists; }
public function getTableName() { return $this->tableName; }
public function getColumns() { return $this->columns; }
public function getConstraints() { return $this->constraints; }
};
?>

@ -0,0 +1,29 @@
<?php
namespace Driver\SQL\Query;
class Delete extends Query {
private $table;
private $conditions;
public function __construct($sql, $table) {
parent::__construct($sql);
$this->table = $table;
$this->conditions = array();
}
public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
public function execute() {
return $this->sql->executeDelete($this);
}
public function getTable() { return $this->table; }
public function getConditions() { return $this->conditions; }
};
?>

@ -0,0 +1,40 @@
<?php
namespace Driver\SQL\Query;
class Insert extends Query {
private $tableName;
private $columns;
private $rows;
private $onDuplicateKey;
public function __construct($sql, $name, $columns=array()) {
parent::__construct($sql);
$this->tableName = $name;
$this->columns = $columns;
$this->rows = array();
$this->onDuplicateKey = NULL;
}
public function addRow(...$values) {
$this->rows[] = $values;
return $this;
}
public function onDuplicateKeyStrategy($strategy) {
$this->onDuplicateKey = $strategy;
return $this;
}
public function execute() {
return $this->sql->executeInsert($this);
}
public function getTableName() { return $this->tableName; }
public function getColumns() { return $this->columns; }
public function getRows() { return $this->rows; }
public function onDuplicateKey() { return $this->onDuplicateKey; }
};
?>

@ -0,0 +1,17 @@
<?php
namespace Driver\SQL\Query;
abstract class Query {
protected $sql;
public function __construct($sql) {
$this->sql = $sql;
}
public abstract function execute();
};
?>

@ -0,0 +1,50 @@
<?php
namespace Driver\SQL\Query;
class Select extends Query {
private $columns;
private $tables;
private $conditions;
private $joins;
public function __construct($sql, ...$columns) {
parent::__construct($sql);
$this->columns = (!empty($columns) && is_array($columns[0])) ? $columns[0] : $columns;
$this->tables = array();
$this->conditions = array();
$this->joins = array();
}
public function from(...$tables) {
$this->tables = array_merge($this->tables, $tables);
return $this;
}
public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
public function innerJoin($table, $columnA, $columnB) {
$this->joins[] = new \Driver\SQL\Join("INNER", $table, $columnA, $columnB);
return $this;
}
public function leftJoin($table, $columnA, $columnB) {
$this->joins[] = new \Driver\SQL\Join("LEFT", $table, $columnA, $columnB);
return $this;
}
public function execute() {
return $this->sql->executeSelect($this);
}
public function getColumns() { return $this->columns; }
public function getTables() { return $this->tables; }
public function getConditions() { return $this->conditions; }
public function getJoins() { return $this->joins; }
};
?>

@ -0,0 +1,21 @@
<?php
namespace Driver\SQL\Query;
class Truncate extends Query {
private $tableName;
public function __construct($sql, $name) {
parent::__construct($sql);
$this->tableName = $name;
}
public function execute() {
return $this->sql->executeTruncate($this);
}
public function getTableName() { return $this->tableName; }
};
?>

@ -0,0 +1,37 @@
<?php
namespace Driver\SQL\Query;
class Update extends Query {
private $values;
private $table;
private $conditions;
public function __construct($sql, $table) {
parent::__construct($sql);
$this->values = array();
$this->table = $table;
$this->conditions = array();
}
public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
public function set($key, $val) {
$this->values[$key] = $val;
return $this;
}
public function execute() {
return $this->sql->executeUpdate($this);
}
public function getTable() { return $this->table; }
public function getConditions() { return $this->conditions; }
public function getValues() { return $this->values; }
};
?>

@ -0,0 +1,186 @@
<?php
namespace Driver\SQL;
abstract class SQL {
protected $lastError;
protected $connection;
protected $connectionData;
protected $lastInsertId;
private $type;
public function __construct($type, $connectionData) {
$this->type = $type;
$this->connection = NULL;
$this->lastError = 'Not connected';
$this->connectionData = $connectionData;
$this->lastInsertId = 0;
}
public abstract function connect();
public abstract function disconnect();
public function isConnected() {
return !is_null($this->connection);
}
public function getLastError() {
return trim($this->lastError);
}
// public function executeQuery($query) {
// if(!$this->isConnected()) {
// $this->lastError = "Database is not connected yet.";
// return false;
// }
//
// return $query->execute($this);
// // var_dump($generatedQuery);
// // return $this->execute($generatedQuery);
// }
public function createTable($tableName) {
return new Query\CreateTable($this, $tableName);
}
public function insert($tableName, $columns=array()) {
return new Query\Insert($this, $tableName, $columns);
}
public function select(...$columNames) {
return new Query\Select($this, $columNames);
}
public function truncate($table) {
return new Query\Truncate($this, $table);
}
public function delete($table) {
return new Query\Delete($this, $table);
}
public function update($table) {
return new Query\Update($this, $table);
}
// Querybuilder
public abstract function executeCreateTable($query);
public abstract function executeInsert($query);
public abstract function executeSelect($query);
public abstract function executeDelete($query);
public abstract function executeTruncate($query);
public abstract function executeUpdate($query);
//
public abstract function currentTimestamp();
protected abstract function getColumnDefinition($column);
protected abstract function getConstraintDefinition($constraint);
protected abstract function getValueDefinition($val);
protected abstract function buildCondition($conditions, &$params);
// Execute
protected abstract function execute($query, $values=NULL, $returnValues=false);
public function setLastError($str) {
$this->lastError = $str;
}
protected function addValue($val, &$params) {
if ($val instanceof Keyword) {
return $val->getValue();
} else {
$params[] = $val;
return "?";
}
}
/*public function getLastErrorNumber() {
return mysqli_errno($this->connection);
}*/
public function getLastInsertId() {
return $this->lastInsertId;
}
public function close() {
if(!is_null($this->connection)) {
$this->connection->close();
}
}
/*public function getAffectedRows() {
return $this->connection->affected_rows;
}*/
/*
public function execute($query) {
if(!$this->isConnected()) {
return false;
}
if(!mysqli_query($this->connection, $query)) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return true;
}
public function executeMulti($queries) {
if(!$this->isConnected()) {
return false;
}
if(!$this->connection->multi_query($queries)) {
$this->lastError = mysqli_error($this->connection);
return false;
}
while (($success = $this->connection->next_result())) {
if (!$this->connection->more_results()) break;
}
if(!$success) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return true;
}
public function query($query) {
if(!$this->isConnected()) {
return false;
}
$res = mysqli_query($this->connection, $query);
if(!$res) {
$this->lastError = mysqli_error($this->connection);
return false;
}
return $res;
}
*/
public static function createConnection($connectionData) {
$type = $connectionData->getProperty("type");
if ($type === "mysql") {
$sql = new MySQL($connectionData);
/*} else if ($type === "postgres") {
// $sql = new PostgreSQL($connectionData);
} else if ($type === "oracle") {
// $sql = new OracleSQL($connectionData);
*/
} else {
return "Unknown database type";
}
$sql->connect();
return $sql;
}
}
?>

@ -0,0 +1,12 @@
<?php
namespace Driver\SQL\Strategy;
class CascadeStrategy extends Strategy {
public function __construct() {
}
};
?>

@ -0,0 +1,12 @@
<?php
namespace Driver\SQL\Strategy;
class SetDefaultStrategy extends Strategy {
public function __construct() {
}
};
?>

@ -0,0 +1,12 @@
<?php
namespace Driver\SQL\Strategy;
class SetNullStrategy extends Strategy {
public function __construct() {
}
};
?>

@ -0,0 +1,9 @@
<?php
namespace Driver\SQL\Strategy;
abstract class Strategy {
};
?>

@ -0,0 +1,16 @@
<?php
namespace Driver\SQL\Strategy;
class UpdateStrategy extends Strategy {
private $values;
public function __construct($values) {
$this->values = $values;
}
public function getValues() { return $this->values; }
};
?>

@ -2,6 +2,8 @@
namespace Objects; namespace Objects;
use \Driver\SQL\Condition\Compare;
class Session extends ApiObject { class Session extends ApiObject {
const DURATION = 120; const DURATION = 120;
@ -84,20 +86,22 @@ class Session extends ApiObject {
public function insert($stayLoggedIn) { public function insert($stayLoggedIn) {
$this->updateMetaData(); $this->updateMetaData();
$query = "INSERT INTO Session (expires, user_id, ipAddress, os, browser, data, stay_logged_in) $sql = $this->user->getSQL();
VALUES (DATE_ADD(NOW(), INTERVAL ? MINUTE),?,?,?,?,?,?)";
$request = new \Api\ExecuteStatement($this->user);
$success = $request->execute(array( $hours = Session::DURATION;
'query' => $query, $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in");
Session::DURATION,
$success = $sql
->insert("Session", $columns)
->addRow(
(new \DateTime)->modify("+$hours hour"),
$this->user->getId(), $this->user->getId(),
$this->ipAddress, $this->ipAddress,
$this->os, $this->os,
$this->browser, $this->browser,
json_encode($_SESSION), json_encode($_SESSION),
$stayLoggedIn $stayLoggedIn)
)); ->execute();
if($success) { if($success) {
$this->sessionId = $this->user->getSQL()->getLastInsertId(); $this->sessionId = $this->user->getSQL()->getLastInsertId();
@ -108,30 +112,30 @@ class Session extends ApiObject {
} }
public function destroy() { public function destroy() {
$query = 'DELETE FROM Session WHERE Session.uid=? OR (Session.stay_logged_in = 0 AND Session.expires<=NOW())'; $success = $this->user->getSQL()->update("Session")
$request = new \Api\ExecuteStatement($this->user); ->set("active", false)
$success = $request->execute(array('query' => $query, $this->sessionId)); ->where(new Compare("Session.uid", $this->sessionId))
->where(new Compare("Session.user_id", $this->user->getId()))
->execute();
return $success; return $success;
} }
public function update() { public function update() {
$this->updateMetaData(); $this->updateMetaData();
$hours = Session::DURATION;
$query = 'UPDATE Session $sql = $this->user->getSQL();
SET Session.expires=DATE_ADD(NOW(), INTERVAL ? MINUTE), $success = $sql->update("Session")
Session.ipAddress=?, Session.os=?, Session.browser=?, Session.data=? ->set("Session.expires", (new \DateTime)->modify("+$hours hour"))
WHERE Session.uid=?'; ->set("Session.ipAddress", $this->ipAddress)
->set("Session.os", $this->os)
->set("Session.browser", $this->browser)
->set("Session.data", json_encode($_SESSION))
->where(new Compare("Session.uid", $this->sessionId))
->where(new Compare("Session.user_id", $this->user->getId()))
->execute();
$request = new \Api\ExecuteStatement($this->user);
$success = $request->execute(array(
'query' => $query,
Session::DURATION,
$this->ipAddress,
$this->os,
$this->browser,
json_encode($_SESSION),
$this->sessionId,
));
return $success; return $success;
} }
} }

@ -2,6 +2,11 @@
namespace Objects; namespace Objects;
use Driver\SQL\Keyword;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondBool;
class User extends ApiObject { class User extends ApiObject {
private $sql; private $sql;
@ -30,7 +35,7 @@ class User extends ApiObject {
private function connectDb() { private function connectDb() {
$databaseConf = $this->configuration->getDatabase(); $databaseConf = $this->configuration->getDatabase();
if($databaseConf) { if($databaseConf) {
$this->sql = \Driver\SQL::createConnection($databaseConf); $this->sql = \Driver\SQL\SQL::createConnection($databaseConf);
} }
} }
@ -74,10 +79,13 @@ class User extends ApiObject {
} }
public function logout() { public function logout() {
$success = true;
if($this->loggedIn) { if($this->loggedIn) {
$this->session->destroy(); $success = $this->session->destroy();
$this->reset(); $this->reset();
} }
return $success;
} }
public function updateLanguage($lang) { public function updateLanguage($lang) {
@ -96,30 +104,29 @@ class User extends ApiObject {
} }
public function readData($userId, $sessionId, $sessionUpdate = true) { public function readData($userId, $sessionId, $sessionUpdate = true) {
$query = 'SELECT User.name as userName, Language.uid as langId, Language.code as langCode,
Language.name as langName, Session.data as sessionData, Session.stay_logged_in as stayLoggedIn
FROM User
INNER JOIN Session ON User.uid=Session.user_id
LEFT JOIN Language ON User.language_id=Language.uid
WHERE User.uid=? AND Session.uid=?
AND (Session.stay_logged_in OR Session.expires>now())';
$request = new \Api\ExecuteSelect($this);
$success = $request->execute(array('query' => $query, $userId, $sessionId));
// var_dump($userId); $res = $this->sql->select("User.name", "Language.uid as langId", "Language.code as langCode", "Language.name as langName",
// var_dump($sessionId); "Session.data", "Session.stay_logged_in")
// var_dump($request->getResult()); ->from("User")
->innerJoin("Session", "Session.user_id", "User.uid")
->leftJoin("Language", "User.language_id", "Language.uid")
->where(new Compare("User.uid", $userId))
->where(new Compare("Session.uid", $sessionId))
->where(new Compare("Session.active", true))
->where(new CondBool("Session.stay_logged_in"), new Compare("Session.expires", new Keyword($this->sql->currentTimestamp()), '>'))
->execute();
$success = ($res !== FALSE);
if($success) { if($success) {
if(count($request->getResult()['rows']) === 0) { if(empty($res)) {
$success = false; $success = false;
} else { } else {
$row = $request->getResult()['rows'][0]; $row = $res[0];
$this->username = $row['userName']; $this->username = $row['name'];
$this->uid = $userId; $this->uid = $userId;
$this->session = new Session($this, $sessionId); $this->session = new Session($this, $sessionId);
$this->session->setData(json_decode($row["sessionData"])); $this->session->setData(json_decode($row["data"]));
$this->session->stayLoggedIn($row["stayLoggedIn"]); $this->session->stayLoggedIn($row["stay_logged_in"]);
if($sessionUpdate) $this->session->update(); if($sessionUpdate) $this->session->update();
$this->loggedIn = true; $this->loggedIn = true;
@ -127,6 +134,8 @@ class User extends ApiObject {
$this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
} }
} }
} else {
var_dump($this->sql->getLastError());
} }
return $success; return $success;
@ -171,29 +180,34 @@ class User extends ApiObject {
} }
public function authorize($apiKey) { public function authorize($apiKey) {
if($this->loggedIn) if($this->loggedIn)
return true; return true;
$query = 'SELECT ApiKey.user_id as uid, User.name as username, Language.uid as langId, Language.code as langCode $res = $this->sql->select("ApiKey.user_id as uid", "User.name as username", "Language.uid as langId", "Language.code as langCode", "Language.name as langName")
FROM ApiKey, User ->from("ApiKey")
LEFT JOIN Language ON User.language_id=Language.uid ->innerJoin("User", "ApiKey.user_id", "User.uid")
WHERE api_key=? AND valid_until > now() AND User.uid = ApiKey.user_id'; ->leftJoin("Language", "User.language_id", "Language.uid")
->where(new Compare("ApiKey.api_key", $apiKey))
$request = new \Api\ExecuteSelect($this); ->where(new Compare("valid_until", new Keyword($this->sql->currentTimestamp()), ">"))
$success = $request->execute(array('query' => $query, $apiKey)); ->where(new COmpare("ApiKey.active", 1))
->execute();
$success = ($res !== FALSE);
if($success) { if($success) {
if(count($request->getResult()['rows']) === 0) { if(empty($res)) {
$success = false; $success = false;
} else { } else {
$row = $request->getResult()['rows'][0]; $row = $res[0];
$this->uid = $row['uid']; $this->uid = $row['uid'];
$this->username = $row['username']; $this->username = $row['username'];
if(!is_null($row['langId'])) { if(!is_null($row['langId'])) {
$this->setLangauge(Language::newInstance($row['langId'], $row['langCode'])); $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
} }
} }
} else {
var_dump($this->sql->getLastError());
} }
return $success; return $success;