diff --git a/core/Api/CreateApiKey.class.php b/core/Api/CreateApiKey.class.php index ba6ec01..a0c3c3c 100644 --- a/core/Api/CreateApiKey.class.php +++ b/core/Api/CreateApiKey.class.php @@ -17,13 +17,20 @@ class CreateApiKey extends Request { } $apiKey = generateRandomString(64); - $query = "INSERT INTO ApiKey (user_id, api_key, valid_until) VALUES (?,?,(SELECT DATE_ADD(now(), INTERVAL 30 DAY)))"; - $request = new ExecuteStatement($this->user); - $this->success = $request->execute(array("query" => $query, $this->user->getId(), $apiKey)); - $this->lastError = $request->getLastError(); - $this->result["api_key"] = $apiKey; - $this->result["valid_until"] = "TODO"; - $this->result["uid"] = $this->user->getSQL()->getLastInsertId(); + $sql = $this->user->getSQL(); + $validUntil = (new \DateTime())->modify("+30 DAY"); + + $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["valid_until"] = $validUntil->getTimestamp(); + $this->result["uid"] = $sql->getLastInsertId(); + } return $this->success; } }; diff --git a/core/Api/ExecuteSelect.class.php b/core/Api/ExecuteSelect.class.php deleted file mode 100644 index b54f4fc..0000000 --- a/core/Api/ExecuteSelect.class.php +++ /dev/null @@ -1,109 +0,0 @@ - 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; - } -}; - -?> diff --git a/core/Api/ExecuteStatement.class.php b/core/Api/ExecuteStatement.class.php deleted file mode 100644 index 7162fbc..0000000 --- a/core/Api/ExecuteStatement.class.php +++ /dev/null @@ -1,97 +0,0 @@ - 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; - } -}; - -?> diff --git a/core/Api/External/RequestData.class.php b/core/Api/External/RequestData.class.php deleted file mode 100644 index 468883b..0000000 --- a/core/Api/External/RequestData.class.php +++ /dev/null @@ -1,82 +0,0 @@ - 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; - } -}; - -?> diff --git a/core/Api/External/WriteData.class.php b/core/Api/External/WriteData.class.php deleted file mode 100644 index d982a13..0000000 --- a/core/Api/External/WriteData.class.php +++ /dev/null @@ -1,44 +0,0 @@ - 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; - } -} - -?> diff --git a/core/Api/GetApiKeys.class.php b/core/Api/GetApiKeys.class.php index 79469ad..8d53594 100644 --- a/core/Api/GetApiKeys.class.php +++ b/core/Api/GetApiKeys.class.php @@ -2,6 +2,9 @@ namespace Api; +use \Driver\SQL\Keyword; +use \Driver\SQL\Condition\Compare; + class GetApiKeys extends Request { public function __construct($user, $externCall = false) { @@ -14,16 +17,19 @@ class GetApiKeys extends Request { return false; } - $query = "SELECT ApiKey.uid, ApiKey.api_key, ApiKey.valid_until - FROM ApiKey - WHERE ApiKey.user_id = ? - AND ApiKey.valid_until > now()"; - $request = new ExecuteSelect($this->user); - $this->success = $request->execute(array("query" => $query, $this->user->getId())); - $this->lastError = $request->getLastError(); + $sql = $this->user->getSQL(); + $res = $sql->select("uid", "api_key", "valid_until") + ->from("ApiKey") + ->where(new Compare("user_id", $this->user->getId())) + ->where(new Compare("valid_until", new Keyword($sql->currentTimestamp()), ">")) + ->where(new Compare("active", true)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); if($this->success) { - $this->result["api_keys"] = $request->getResult()['rows']; + $this->result["api_keys"] = $res; } return $this->success; diff --git a/core/Api/GetLanguages.class.php b/core/Api/GetLanguages.class.php index aec1f86..d10be01 100644 --- a/core/Api/GetLanguages.class.php +++ b/core/Api/GetLanguages.class.php @@ -13,17 +13,20 @@ class GetLanguages extends Request { return false; } - $query = 'SELECT uid, code, name FROM Language'; - $request = new ExecuteSelect($this->user); - $this->success = $request->execute(array('query' => $query)); - $this->lastError = $request->getLastError(); + $sql = $this->user->getSQL(); + $res = $sql->select("uid", "code", "name") + ->from("Language") + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); if($this->success) { $this->result['languages'] = array(); - if(count($request->getResult()['rows']) === 0) { + if(empty($res) === 0) { $this->lastError = L("No languages found"); } else { - foreach($request->getResult()['rows'] as $row) { + foreach($res as $row) { $this->result['languages'][$row['uid']] = $row; } } diff --git a/core/Api/Login.class.php b/core/Api/Login.class.php index e6b2cf9..ea4b412 100644 --- a/core/Api/Login.class.php +++ b/core/Api/Login.class.php @@ -4,6 +4,7 @@ namespace Api; use Api\Parameter\Parameter; use Api\Parameter\StringType; +use Driver\SQL\Condition\Compare; class Login extends Request { @@ -42,27 +43,30 @@ class Login extends Request { $password = $this->getParam('password'); $stayLoggedIn = $this->getParam('stayLoggedIn'); - $query = 'SELECT User.uid, User.password, User.salt FROM User WHERE User.name=?'; - $request = new ExecuteSelect($this->user); - $this->success = $request->execute(array('query' => $query, $username)); - $this->lastError = $request->getLastError(); + $sql = $this->user->getSQL(); + $res = $sql->select("User.uid", "User.password", "User.salt") + ->from("User") + ->where(new Compare("User.name", $username)) + ->execute(); + + $this->success = ($res !== FALSE); + $this->lastError = $sql->getLastError(); if($this->success) { - $this->success = false; - if(count($request->getResult()['rows']) === 0) { + if(count($res) === 0) { return $this->wrongCredentials(); - $this->lastError = L('Wrong username or password'); } else { - $row = $request->getResult()['rows'][0]; + $row = $res[0]; $salt = $row['salt']; $uid = $row['uid']; $hash = hash('sha256', $password . $salt); if($hash === $row['password']) { - if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) { - return $this->createError("Error creating Session"); - } else { - $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); - } + if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) { + return $this->createError("Error creating Session: " . $sql->getLastError()); + } else { + $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); + $this->success = true; + } } else { return $this->wrongCredentials(); diff --git a/core/Api/Logout.class.php b/core/Api/Logout.class.php index 4fb6149..c126bc0 100644 --- a/core/Api/Logout.class.php +++ b/core/Api/Logout.class.php @@ -15,9 +15,9 @@ class Logout extends Request { return false; } - $this->success = true; - $this->user->logout(); - return true; + $this->success = $this->user->logout(); + $this->lastError = $this->user->getSQL()->getLastError(); + return $this->success; } }; diff --git a/core/Api/RefreshApiKey.class.php b/core/Api/RefreshApiKey.class.php index 9f83af8..3a24a30 100644 --- a/core/Api/RefreshApiKey.class.php +++ b/core/Api/RefreshApiKey.class.php @@ -1,7 +1,10 @@ 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->lastError = "This API-Key does not exist."; } @@ -36,10 +47,18 @@ class RefreshApiKey extends Request { if(!$this->apiKeyExists()) return false; - $query = "UPDATE ApiKey SET valid_until = (SELECT DATE_ADD(now(), INTERVAL 30 DAY)) WHERE uid = ? AND user_id = ? AND valid_until > now()"; - $request = new ExecuteStatement($this->user); - $this->success = $request->execute(array("query" => $query, $id, $this->user->getId())); - $this->lastError = $request->getLastError(); + $validUntil = (new \DateTime)->modify("+30 DAY"); + $sql = $this->user->getSQL(); + $this->success = $sql->update("ApiKey") + ->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; } diff --git a/core/Api/RevokeApiKey.class.php b/core/Api/RevokeApiKey.class.php index 314ebd9..f2b8448 100644 --- a/core/Api/RevokeApiKey.class.php +++ b/core/Api/RevokeApiKey.class.php @@ -1,7 +1,10 @@ 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->lastError = "This API-Key does not exist."; } @@ -36,10 +47,13 @@ class RevokeApiKey extends Request { if(!$this->apiKeyExists()) return false; - $query = "DELETE FROM ApiKey WHERE valid_until < now() OR (uid = ? AND user_id = ?)"; - $request = new ExecuteStatement($this->user); - $this->success = $request->execute(array("query" => $query, $id, $this->user->getId())); - $this->lastError = $request->getLastError(); + $sql = $this->user->getSQL(); + $this->success = $sql->update("ApiKey") + ->set("active", false) + ->where(new Compare("uid", $id)) + ->where(new Compare("user_id", $this->user->getId())) + ->execute(); + $this->lastError = $sql->getLastError(); return $this->success; } diff --git a/core/Api/SetLanguage.class.php b/core/Api/SetLanguage.class.php index 881cfbf..c12d69a 100644 --- a/core/Api/SetLanguage.class.php +++ b/core/Api/SetLanguage.class.php @@ -4,6 +4,8 @@ namespace Api; use Api\Parameter\Parameter; use Api\Parameter\StringType; +use Driver\SQL\Condition\CondOr; +use Driver\SQL\Condition\Compare; class SetLanguage extends Request { @@ -24,16 +26,20 @@ class SetLanguage extends Request { return $this->createError(L("Either langId or langCode must be given")); } - $query = "SELECT uid, code, name FROM Language WHERE uid=? OR code=?"; - $request = new ExecuteSelect($this->user); - $this->success = $request->execute(array("query" => $query, $langId, $langCode)); - $this->lastError = $request->getLastError(); + $res = $this->user->getSQL() + ->select("uid", "code", "name") + ->from("Language") + ->where(new CondOr(new Compare("uid", $langId), new Compare("code", $langCode))) + ->execute(); - if($this->success) { - if(count($request->getResult()['rows']) == 0) { + $this->success = ($res !== FALSE); + $this->lastError = $this->user->getSQL()->getLastError(); + + if ($this->success) { + if(count($res) == 0) { return $this->createError(L("This Language does not exist")); } else { - $row = $request->getResult()['rows'][0]; + $row = $res[0]; $this->language = \Objects\Language::newInstance($row['uid'], $row['code'], $row['name']); if(!$this->language) { return $this->createError(L("Error while loading language")); @@ -47,11 +53,13 @@ class SetLanguage extends Request { private function updateLanguage() { $languageId = $this->language->getId(); $userId = $this->user->getId(); + $sql = $this->user->getSQL(); - $query = "UPDATE User SET language_id = ? WHERE uid = ?"; - $request = new ExecuteStatement($this->user); - $this->success = $request->execute(array("query" => $query, $languageId, $userId)); - $this->lastError = $request->getLastError(); + $this->success = $sql->update("User") + ->set("language_id", $languageId) + ->where(new Compare("uid", $userId)) + ->execute(); + $this->lastError = $sql->getLastError(); return $this->success; } diff --git a/core/Configuration/CreateDatabase.class.php b/core/Configuration/CreateDatabase.class.php new file mode 100644 index 0000000..8d18a6a --- /dev/null +++ b/core/Configuration/CreateDatabase.class.php @@ -0,0 +1,94 @@ +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; + } +} + +?> diff --git a/core/Configuration/database.sql b/core/Configuration/database.sql index 7fda489..b4ce6d4 100644 --- a/core/Configuration/database.sql +++ b/core/Configuration/database.sql @@ -20,11 +20,11 @@ CREATE TABLE IF NOT EXISTS User ( `password` varchar(64) NOT NULL, `language_id` int(11) DEFAULT 1, 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 ( - `email` VARCHAR(256) NOT NULL, + `email` VARCHAR(64) NOT NULL, `token` VARCHAR(36) UNIQUE NOT NULL, `valid_until` DATETIME NOT NULL ); diff --git a/core/Documents/Install.class.php b/core/Documents/Install.class.php index f917714..c0b429f 100644 --- a/core/Documents/Install.class.php +++ b/core/Documents/Install.class.php @@ -97,18 +97,15 @@ namespace Documents\Install { return self::DATABASE_CONFIGURATION; } - $request = new \Api\ExecuteSelect($user); - $success = $request->execute(array("query" => "SELECT COUNT(*) AS count FROM User")); - $this->errorString = $request->getLastError(); - - if($success) { - if($request->getResult()['rows'][0]["count"] > 0) { + $res = $user->getSQL()->select("COUNT(*) as count")->from("User")->execute(); + if ($res === FALSE) { + return self::DATABASE_CONFIGURATION; + } else { + if ($res[0]["count"] > 0) { $step = self::ADD_MAIL_SERVICE; } else { return self::CREATE_USER; } - } else { - return self::DATABASE_CONFIGURATION; } if($step === self::ADD_MAIL_SERVICE && $config->isFilePresent("Mail")) { @@ -163,6 +160,9 @@ namespace Documents\Install { $username = $this->getParameter("username"); $password = $this->getParameter("password"); $database = $this->getParameter("database"); + $type = $this->getParameter("type"); + $encoding = $this->getParameter("encoding"); + $encoding = ($encoding ? $encoding : "UTF-8"); $success = true; $missingInputs = array(); @@ -191,43 +191,54 @@ namespace Documents\Install { $missingInputs[] = "Database"; } + if(is_null($type) || empty($type)) { + $success = false; + $missingInputs[] = "Type"; + } + + $supportedTypes = array("mysql"); # , "oracle", "postgres"); if(!$success) { $msg = "Please fill out the following inputs:
" . $this->createUnorderedList($missingInputs); } else if(!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) { $msg = "Port must be in range of 1-65535."; $success = false; + } else if(!in_array($type, $supportedTypes)) { + $msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes); + $success = false; } else { $connectionData = new \Objects\ConnectionData($host, $port, $username, $password); $connectionData->setProperty('database', $database); - $connectionData->setProperty('encoding', 'utf8'); - $sql = new \Driver\SQL($connectionData); - $success = $sql->connect(); - - if(!$success) { + $connectionData->setProperty('encoding', $encoding); + $connectionData->setProperty('type', $type); + $sql = \Driver\SQL\SQL::createConnection($connectionData); + $success = false; + if(!($sql instanceof \Driver\SQL\SQL)) { + $msg = "Error connecting to database: " . str($sql); + } else if(!$sql->isConnected()) { $msg = "Error connecting to database:
" . $sql->getLastError(); } else { - try { - $msg = "Error loading database script $this->databaseScript"; - $commands = file_get_contents($this->databaseScript); - $success = $sql->executeMulti($commands); - if(!$success) { - $msg = $sql->getLastError(); - } else if(!$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) { + + $msg = ""; + $success = true; + $queries = \Configuration\CreateDatabase::createQueries($sql); + foreach($queries as $query) { + if (!$query->execute()) { + $msg = "Error creating tables: " . $sql->getLastError(); $success = false; - $msg = "Unable to write file"; - } else { - $msg = ""; + break; } - } catch(Exception $e) { - $success = false; - $msg .= ": " . $e->getMessage(); } - if($sql) { - $sql->close(); + if($success && !$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) { + $success = false; + $msg = "Unable to write file"; } } + + if($sql) { + $sql->close(); + } } return array("success" => $success, "msg" => $msg); @@ -280,10 +291,13 @@ namespace Documents\Install { } else { $salt = generateRandomString(16); $hash = hash('sha256', $password . $salt); - $query = "INSERT INTO User (name, salt, password) VALUES (?,?,?)"; - $req = new \Api\ExecuteStatement($user); - $success = $req->execute(array("query" => $query, $username, $salt, $hash)); - $nsg = $req->getLastError(); + $sql = $user->getSQL(); + + $success = $sql->insert("User", array("name", "salt", "password")) + ->addRow($username, $salt, $hash) + ->execute(); + + $msg = $sql->getLastError(); } return array("msg" => $msg, "success" => $success); @@ -293,9 +307,9 @@ namespace Documents\Install { $user = $this->getDocument()->getUser(); if($this->getParameter("prev") === "true") { - $req = new \Api\ExecuteStatement($user); - $success = $req->execute(array("query" => "TRUNCATE User")); - $msg = $req->getLastError(); + $sql = $user->getSQL(); + $success = $sql->delete("User")->execute(); + $msg = $sql->getLastError(); return array("success" => $success, "msg" => $msg); } @@ -462,40 +476,58 @@ namespace Documents\Install { $attributes = array( "name" => $name, "id" => $name, - "class" => "form-control", - "type" => $type, + "class" => "form-control" ); if(isset($formItem["required"]) && $formItem["required"]) { $attributes["required"] = ""; } - if(isset($formItem["value"]) && $formItem["value"]) { - $attributes["value"] = $formItem["value"]; + if ($type !== "select") { + $attributes["type"] = $type; + + if(isset($formItem["value"]) && $formItem["value"]) { + $attributes["value"] = $formItem["value"]; + } + + if($type === "number") { + if(isset($formItem["min"]) && is_numeric($formItem["min"])) + $attributes["min"] = $formItem["min"]; + if(isset($formItem["max"]) && is_numeric($formItem["max"])) + $attributes["max"] = $formItem["max"]; + if(isset($formItem["step"]) && is_numeric($formItem["step"])) + $attributes["step"] = $formItem["step"]; + } } - if($type === "number") { - if(isset($formItem["min"]) && is_numeric($formItem["min"])) - $attributes["min"] = $formItem["min"]; - if(isset($formItem["max"]) && is_numeric($formItem["max"])) - $attributes["max"] = $formItem["max"]; - if(isset($formItem["step"]) && is_numeric($formItem["step"])) - $attributes["step"] = $formItem["step"]; + $replacements = array("+" => " ", "&" => "\" ", "=" => "=\""); + $attributes = http_build_query($attributes) . "\""; + foreach($replacements as $key => $val) { + $attributes = str_replace($key, $val, $attributes); } - $attributes = str_replace("+", " ", str_replace("&", "\" ", str_replace("=", "=\"", http_build_query($attributes)))) . "\""; + if ($type === "select") { + $items = $formItem["items"] ?? array(); + $element = ""; + } else { + $element = ""; + } if(!$inline) { return "
- + $element
"; } else { return "
- + $element
"; } } @@ -510,6 +542,9 @@ namespace Documents\Install { self::DATABASE_CONFIGURATION => array( "title" => "Database configuration", "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" => "Password", "name" => "password", "type" => "password"), array("title" => "Database", "name" => "database", "type" => "text", "required" => true), @@ -523,6 +558,10 @@ namespace Documents\Install { "value" => "3306", "min" => "1", "max" => "65535", "row" => true ) )), + array( + "title" => "Encoding", "name" => "encoding", "type" => "text", "required" => false, + "value" => "UTF-8" + ), ) ), self::CREATE_USER => array( diff --git a/core/Driver/SQL.class.php b/core/Driver/SQL.class.php deleted file mode 100644 index 80a1bf1..0000000 --- a/core/Driver/SQL.class.php +++ /dev/null @@ -1,133 +0,0 @@ -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; - } -} - -?> diff --git a/core/Driver/SQL/Column/BoolColumn.class.php b/core/Driver/SQL/Column/BoolColumn.class.php new file mode 100644 index 0000000..8c91230 --- /dev/null +++ b/core/Driver/SQL/Column/BoolColumn.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Column/Column.class.php b/core/Driver/SQL/Column/Column.class.php new file mode 100644 index 0000000..658a011 --- /dev/null +++ b/core/Driver/SQL/Column/Column.class.php @@ -0,0 +1,23 @@ +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; } + +} + +?> diff --git a/core/Driver/SQL/Column/DateTimeColumn.class.php b/core/Driver/SQL/Column/DateTimeColumn.class.php new file mode 100644 index 0000000..7c41ab9 --- /dev/null +++ b/core/Driver/SQL/Column/DateTimeColumn.class.php @@ -0,0 +1,12 @@ + diff --git a/core/Driver/SQL/Column/EnumColumn.class.php b/core/Driver/SQL/Column/EnumColumn.class.php new file mode 100644 index 0000000..e5744a4 --- /dev/null +++ b/core/Driver/SQL/Column/EnumColumn.class.php @@ -0,0 +1,17 @@ +values = $values; + } + + public function getValues() { return $this->values; } +} + +?> diff --git a/core/Driver/SQL/Column/IntColumn.class.php b/core/Driver/SQL/Column/IntColumn.class.php new file mode 100644 index 0000000..8c904a2 --- /dev/null +++ b/core/Driver/SQL/Column/IntColumn.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Column/JsonColumn.class.php b/core/Driver/SQL/Column/JsonColumn.class.php new file mode 100644 index 0000000..748237c --- /dev/null +++ b/core/Driver/SQL/Column/JsonColumn.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Column/SerialColumn.class.php b/core/Driver/SQL/Column/SerialColumn.class.php new file mode 100644 index 0000000..2e25825 --- /dev/null +++ b/core/Driver/SQL/Column/SerialColumn.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Column/StringColumn.class.php b/core/Driver/SQL/Column/StringColumn.class.php new file mode 100644 index 0000000..381580f --- /dev/null +++ b/core/Driver/SQL/Column/StringColumn.class.php @@ -0,0 +1,17 @@ +maxSize = $maxSize; + } + + public function getMaxSize() { return $this->maxSize; } +} + +?> diff --git a/core/Driver/SQL/Condition/Compare.class.php b/core/Driver/SQL/Condition/Compare.class.php new file mode 100644 index 0000000..93a32eb --- /dev/null +++ b/core/Driver/SQL/Condition/Compare.class.php @@ -0,0 +1,19 @@ +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; } + +} + +?> diff --git a/core/Driver/SQL/Condition/CondAnd.class.php b/core/Driver/SQL/Condition/CondAnd.class.php new file mode 100644 index 0000000..7616c90 --- /dev/null +++ b/core/Driver/SQL/Condition/CondAnd.class.php @@ -0,0 +1,16 @@ +conditions = $conditions; + } + + public function getConditions() { return $this->conditions; } +} + +?> diff --git a/core/Driver/SQL/Condition/CondBool.class.php b/core/Driver/SQL/Condition/CondBool.class.php new file mode 100644 index 0000000..d374ea8 --- /dev/null +++ b/core/Driver/SQL/Condition/CondBool.class.php @@ -0,0 +1,15 @@ +value = $val; + } + + public function getValue() { return $this->value; } + +} + +?> diff --git a/core/Driver/SQL/Condition/CondOr.class.php b/core/Driver/SQL/Condition/CondOr.class.php new file mode 100644 index 0000000..c145632 --- /dev/null +++ b/core/Driver/SQL/Condition/CondOr.class.php @@ -0,0 +1,16 @@ +conditions = $conditions; + } + + public function getConditions() { return $this->conditions; } +} + +?> diff --git a/core/Driver/SQL/Condition/Condition.class.php b/core/Driver/SQL/Condition/Condition.class.php new file mode 100644 index 0000000..b8e6853 --- /dev/null +++ b/core/Driver/SQL/Condition/Condition.class.php @@ -0,0 +1,9 @@ + diff --git a/core/Driver/SQL/Constraint/Constraint.class.php b/core/Driver/SQL/Constraint/Constraint.class.php new file mode 100644 index 0000000..83afe69 --- /dev/null +++ b/core/Driver/SQL/Constraint/Constraint.class.php @@ -0,0 +1,16 @@ +columnName = $columnName; + } + + public function getColumnName() { return $this->columnName; } +}; + +?> diff --git a/core/Driver/SQL/Constraint/ForeignKey.class.php b/core/Driver/SQL/Constraint/ForeignKey.class.php new file mode 100644 index 0000000..9b88782 --- /dev/null +++ b/core/Driver/SQL/Constraint/ForeignKey.class.php @@ -0,0 +1,23 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/Constraint/PrimaryKey.class.php b/core/Driver/SQL/Constraint/PrimaryKey.class.php new file mode 100644 index 0000000..1bb7683 --- /dev/null +++ b/core/Driver/SQL/Constraint/PrimaryKey.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Constraint/Unique.class.php b/core/Driver/SQL/Constraint/Unique.class.php new file mode 100644 index 0000000..022062d --- /dev/null +++ b/core/Driver/SQL/Constraint/Unique.class.php @@ -0,0 +1,13 @@ + diff --git a/core/Driver/SQL/Join.class.php b/core/Driver/SQL/Join.class.php new file mode 100644 index 0000000..270c289 --- /dev/null +++ b/core/Driver/SQL/Join.class.php @@ -0,0 +1,26 @@ +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; } + +} + +?> diff --git a/core/Driver/SQL/Keyword.class.php b/core/Driver/SQL/Keyword.class.php new file mode 100644 index 0000000..16bb6c1 --- /dev/null +++ b/core/Driver/SQL/Keyword.class.php @@ -0,0 +1,17 @@ +value = $value; + } + + public function getValue() { return $this->value; } + +} + +?> diff --git a/core/Driver/SQL/MySQL.class.php b/core/Driver/SQL/MySQL.class.php new file mode 100644 index 0000000..55f675c --- /dev/null +++ b/core/Driver/SQL/MySQL.class.php @@ -0,0 +1,430 @@ +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()"; + } +}; diff --git a/core/Driver/SQL/Query/CreateTable.class.php b/core/Driver/SQL/Query/CreateTable.class.php new file mode 100644 index 0000000..77116de --- /dev/null +++ b/core/Driver/SQL/Query/CreateTable.class.php @@ -0,0 +1,97 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/Query/Delete.class.php b/core/Driver/SQL/Query/Delete.class.php new file mode 100644 index 0000000..1b4f507 --- /dev/null +++ b/core/Driver/SQL/Query/Delete.class.php @@ -0,0 +1,29 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/Query/Insert.class.php b/core/Driver/SQL/Query/Insert.class.php new file mode 100644 index 0000000..b6145dd --- /dev/null +++ b/core/Driver/SQL/Query/Insert.class.php @@ -0,0 +1,40 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/Query/Query.class.php b/core/Driver/SQL/Query/Query.class.php new file mode 100644 index 0000000..dce4510 --- /dev/null +++ b/core/Driver/SQL/Query/Query.class.php @@ -0,0 +1,17 @@ +sql = $sql; + } + + public abstract function execute(); + +}; + +?> diff --git a/core/Driver/SQL/Query/Select.class.php b/core/Driver/SQL/Query/Select.class.php new file mode 100644 index 0000000..4616393 --- /dev/null +++ b/core/Driver/SQL/Query/Select.class.php @@ -0,0 +1,50 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/Query/Truncate.class.php b/core/Driver/SQL/Query/Truncate.class.php new file mode 100644 index 0000000..d5b39af --- /dev/null +++ b/core/Driver/SQL/Query/Truncate.class.php @@ -0,0 +1,21 @@ +tableName = $name; + } + + public function execute() { + return $this->sql->executeTruncate($this); + } + + public function getTableName() { return $this->tableName; } +}; + +?> diff --git a/core/Driver/SQL/Query/Update.class.php b/core/Driver/SQL/Query/Update.class.php new file mode 100644 index 0000000..9c29edf --- /dev/null +++ b/core/Driver/SQL/Query/Update.class.php @@ -0,0 +1,37 @@ +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; } +}; + +?> diff --git a/core/Driver/SQL/SQL.class.php b/core/Driver/SQL/SQL.class.php new file mode 100644 index 0000000..ff4b47a --- /dev/null +++ b/core/Driver/SQL/SQL.class.php @@ -0,0 +1,186 @@ +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; + } +} + +?> diff --git a/core/Driver/SQL/Strategy/CascadeStrategy.class.php b/core/Driver/SQL/Strategy/CascadeStrategy.class.php new file mode 100644 index 0000000..103f8bd --- /dev/null +++ b/core/Driver/SQL/Strategy/CascadeStrategy.class.php @@ -0,0 +1,12 @@ + diff --git a/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php b/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php new file mode 100644 index 0000000..b896080 --- /dev/null +++ b/core/Driver/SQL/Strategy/SetDefaultStrategy.class.php @@ -0,0 +1,12 @@ + diff --git a/core/Driver/SQL/Strategy/SetNullStrategy.class.php b/core/Driver/SQL/Strategy/SetNullStrategy.class.php new file mode 100644 index 0000000..7b6fd05 --- /dev/null +++ b/core/Driver/SQL/Strategy/SetNullStrategy.class.php @@ -0,0 +1,12 @@ + diff --git a/core/Driver/SQL/Strategy/Strategy.class.php b/core/Driver/SQL/Strategy/Strategy.class.php new file mode 100644 index 0000000..83b662d --- /dev/null +++ b/core/Driver/SQL/Strategy/Strategy.class.php @@ -0,0 +1,9 @@ + diff --git a/core/Driver/SQL/Strategy/UpdateStrategy.class.php b/core/Driver/SQL/Strategy/UpdateStrategy.class.php new file mode 100644 index 0000000..3fce860 --- /dev/null +++ b/core/Driver/SQL/Strategy/UpdateStrategy.class.php @@ -0,0 +1,16 @@ +values = $values; + } + + public function getValues() { return $this->values; } +}; + +?> diff --git a/core/Objects/Session.class.php b/core/Objects/Session.class.php index acc025f..5a54ede 100644 --- a/core/Objects/Session.class.php +++ b/core/Objects/Session.class.php @@ -2,6 +2,8 @@ namespace Objects; +use \Driver\SQL\Condition\Compare; + class Session extends ApiObject { const DURATION = 120; @@ -84,20 +86,22 @@ class Session extends ApiObject { public function insert($stayLoggedIn) { $this->updateMetaData(); - $query = "INSERT INTO Session (expires, user_id, ipAddress, os, browser, data, stay_logged_in) - VALUES (DATE_ADD(NOW(), INTERVAL ? MINUTE),?,?,?,?,?,?)"; - $request = new \Api\ExecuteStatement($this->user); + $sql = $this->user->getSQL(); - $success = $request->execute(array( - 'query' => $query, - Session::DURATION, - $this->user->getId(), - $this->ipAddress, - $this->os, - $this->browser, - json_encode($_SESSION), - $stayLoggedIn - )); + $hours = Session::DURATION; + $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in"); + + $success = $sql + ->insert("Session", $columns) + ->addRow( + (new \DateTime)->modify("+$hours hour"), + $this->user->getId(), + $this->ipAddress, + $this->os, + $this->browser, + json_encode($_SESSION), + $stayLoggedIn) + ->execute(); if($success) { $this->sessionId = $this->user->getSQL()->getLastInsertId(); @@ -108,30 +112,30 @@ class Session extends ApiObject { } public function destroy() { - $query = 'DELETE FROM Session WHERE Session.uid=? OR (Session.stay_logged_in = 0 AND Session.expires<=NOW())'; - $request = new \Api\ExecuteStatement($this->user); - $success = $request->execute(array('query' => $query, $this->sessionId)); + $success = $this->user->getSQL()->update("Session") + ->set("active", false) + ->where(new Compare("Session.uid", $this->sessionId)) + ->where(new Compare("Session.user_id", $this->user->getId())) + ->execute(); + return $success; } public function update() { $this->updateMetaData(); + $hours = Session::DURATION; - $query = 'UPDATE Session - SET Session.expires=DATE_ADD(NOW(), INTERVAL ? MINUTE), - Session.ipAddress=?, Session.os=?, Session.browser=?, Session.data=? - WHERE Session.uid=?'; + $sql = $this->user->getSQL(); + $success = $sql->update("Session") + ->set("Session.expires", (new \DateTime)->modify("+$hours hour")) + ->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; } } diff --git a/core/Objects/User.class.php b/core/Objects/User.class.php index 6d6c701..f1e9d21 100644 --- a/core/Objects/User.class.php +++ b/core/Objects/User.class.php @@ -2,6 +2,11 @@ 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 { private $sql; @@ -30,7 +35,7 @@ class User extends ApiObject { private function connectDb() { $databaseConf = $this->configuration->getDatabase(); 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() { + $success = true; if($this->loggedIn) { - $this->session->destroy(); + $success = $this->session->destroy(); $this->reset(); } + + return $success; } public function updateLanguage($lang) { @@ -96,30 +104,29 @@ class User extends ApiObject { } 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); - // var_dump($sessionId); - // var_dump($request->getResult()); + $res = $this->sql->select("User.name", "Language.uid as langId", "Language.code as langCode", "Language.name as langName", + "Session.data", "Session.stay_logged_in") + ->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(count($request->getResult()['rows']) === 0) { + if(empty($res)) { $success = false; } else { - $row = $request->getResult()['rows'][0]; - $this->username = $row['userName']; + $row = $res[0]; + $this->username = $row['name']; $this->uid = $userId; $this->session = new Session($this, $sessionId); - $this->session->setData(json_decode($row["sessionData"])); - $this->session->stayLoggedIn($row["stayLoggedIn"]); + $this->session->setData(json_decode($row["data"])); + $this->session->stayLoggedIn($row["stay_logged_in"]); if($sessionUpdate) $this->session->update(); $this->loggedIn = true; @@ -127,6 +134,8 @@ class User extends ApiObject { $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); } } + } else { + var_dump($this->sql->getLastError()); } return $success; @@ -171,29 +180,34 @@ class User extends ApiObject { } public function authorize($apiKey) { + if($this->loggedIn) return true; - $query = 'SELECT ApiKey.user_id as uid, User.name as username, Language.uid as langId, Language.code as langCode - FROM ApiKey, User - LEFT JOIN Language ON User.language_id=Language.uid - WHERE api_key=? AND valid_until > now() AND User.uid = ApiKey.user_id'; - - $request = new \Api\ExecuteSelect($this); - $success = $request->execute(array('query' => $query, $apiKey)); + $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") + ->innerJoin("User", "ApiKey.user_id", "User.uid") + ->leftJoin("Language", "User.language_id", "Language.uid") + ->where(new Compare("ApiKey.api_key", $apiKey)) + ->where(new Compare("valid_until", new Keyword($this->sql->currentTimestamp()), ">")) + ->where(new COmpare("ApiKey.active", 1)) + ->execute(); + $success = ($res !== FALSE); if($success) { - if(count($request->getResult()['rows']) === 0) { + if(empty($res)) { $success = false; } else { - $row = $request->getResult()['rows'][0]; + $row = $res[0]; $this->uid = $row['uid']; $this->username = $row['username']; 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;