Merge branch 'reactjs'

This commit is contained in:
Roman Hergenreder 2020-07-02 01:21:15 +02:00
commit fd738c46e8
170 changed files with 24878 additions and 8936 deletions

@ -1,8 +1,13 @@
php_flag display_errors on php_flag display_errors on
Options -Indexes Options -Indexes
RedirectMatch 404 /\.git DirectorySlash Off
RewriteEngine On RewriteEngine On
RewriteRule ^api/(.*)?$ index.php?api=$1&$2 [L,QSA] RewriteRule ^api(/.*)?$ /index.php?api=$1 [L,QSA]
RewriteRule ^((?!((js|css|img|fonts|api)($|\/)))(.*)?)$ index.php?site=$1&$2 [L,QSA]
RewriteEngine On
RewriteOptions AllowNoSlash
RewriteRule ^((\.idea|\.git|src|test|core)(/.*)?)$ /index.php?site=$1 [L,QSA]
FallbackResource /index.php

2
.idea/.gitignore vendored Normal file

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="webbase">
<words>
<w>adminlte</w>
<w>navbar</w>
</words>
</dictionary>
</component>

6
.idea/misc.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
</project>

8
.idea/modules.xml Normal file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/web-base.iml" filepath="$PROJECT_DIR$/.idea/web-base.iml" />
</modules>
</component>
</project>

4
.idea/php.xml Normal file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
</project>

6
.idea/vcs.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

8
.idea/web-base.iml Normal file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,12 +1,19 @@
# Web-Base # Web-Base
Web-Base is a php framework which provides basic web functionalities. Web-Base is a php framework which provides basic web functionalities and a modern ReactJS Admin Dashboard.
### Requirements
- PHP >= 7.4
- One of these php extensions: mysqli, postgres
### Current Functionalities: ### Current Functionalities:
- Installation Guide with automatic database setup - Installation Guide with automatic database setup
- REST API - REST API
- Account managment - Account management
- New: Now supporting MySQL + PostgreSQL - Supporting MySQL + PostgreSQL
- New: Page Routing
- New: Admin Dashboard
- New: Account & User functions
### Upcoming: ### Upcoming:
I actually don't know what i want to implement here. There are quite to many CMS out there with alot of vulnerabilities. There also exist some frameworks already. This project is meant to provide a stable project base to implement what ever a developer wants to: Dynamic Homepages, Webshops, .. I actually don't know what i want to implement here. There are quite to many CMS out there with alot of vulnerabilities. There also exist some frameworks already. This project is meant to provide a stable project base to implement what ever a developer wants to: Dynamic Homepages, Webshops, ..

@ -1,44 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
class Create extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array());
$this->apiKeyAllowed = false;
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$apiKey = generateRandomString(64);
$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)
->returning("uid")
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["api_key"] = array(
"api_key" => $apiKey,
"valid_until" => $validUntil->getTimestamp(),
"uid" => $sql->getLastInsertId(),
);
} else {
$this->result["api_key"] = null;
}
return $this->success;
}
};
?>

@ -1,46 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use \Driver\SQL\Condition\Compare;
class Fetch extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array());
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$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", $sql->currentTimestamp(), ">"))
->where(new Compare("active", true))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["api_keys"] = array();
foreach($res as $row) {
$this->result["api_keys"][] = array(
"uid" => $row["uid"],
"api_key" => $row["api_key"],
"valid_until" => (new \DateTime($row["valid_until"]))->getTimestamp(),
);
}
}
return $this->success;
}
};
?>

@ -1,67 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare;
class Refresh extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
}
private function apiKeyExists() {
$id = $this->getParam("id");
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $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.";
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists())
return false;
$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;
}
};
?>

@ -1,62 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare;
class Revoke extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
}
private function apiKeyExists() {
$id = $this->getParam("id");
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $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.";
}
return $this->success;
}
public function execute($aValues = array()) {
if(!parent::execute($aValues)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists())
return false;
$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;
}
};
?>

@ -0,0 +1,187 @@
<?php
namespace Api {
use Driver\SQL\Condition\Compare;
abstract class ApiKeyAPI extends Request {
protected function apiKeyExists($id) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["count"] === 0) {
return $this->createError("This API-Key does not exist.");
}
return $this->success;
}
}
}
namespace Api\ApiKey {
use Api\ApiKeyAPI;
use Api\Parameter\Parameter;
use DateTime;
use Driver\SQL\Condition\Compare;
use Exception;
class Create extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->apiKeyAllowed = false;
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$apiKey = generateRandomString(64);
$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)
->returning("uid")
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["api_key"] = array(
"api_key" => $apiKey,
"valid_until" => $validUntil->getTimestamp(),
"uid" => $sql->getLastInsertId(),
);
} else {
$this->result["api_key"] = null;
}
return $this->success;
}
}
class Fetch extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$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", $sql->currentTimestamp(), ">"))
->where(new Compare("active", true))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["api_keys"] = array();
foreach($res as $row) {
try {
$validUntil = (new DateTime($row["valid_until"]))->getTimestamp();
} catch (Exception $e) {
$validUntil = $row["valid_until"];
}
$this->result["api_keys"][] = array(
"uid" => intval($row["uid"]),
"api_key" => $row["api_key"],
"valid_until" => $validUntil,
);
}
}
return $this->success;
}
}
class Refresh extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists($id))
return false;
$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;
}
}
class Revoke extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
}
public function execute($aValues = array()) {
if(!parent::execute($aValues)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists($id))
return false;
$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;
}
}
}

@ -0,0 +1,115 @@
<?php
namespace Api {
abstract class ContactAPI extends Request {
}
}
namespace Api\Contact {
use Api\ContactAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\VerifyCaptcha;
use Objects\User;
class Request extends ContactAPI {
private int $notificationId;
private int $contactRequestId;
public function __construct(User $user, bool $externalCall = false) {
$parameters = array(
'fromName' => new StringType('fromName', 32),
'fromEmail' => new Parameter('fromEmail', Parameter::TYPE_EMAIL),
'message' => new StringType('message', 512),
);
$settings = $user->getConfiguration()->getSettings();
if ($settings->isRecaptchaEnabled()) {
$parameters["captcha"] = new StringType("captcha");
}
parent::__construct($user, $externalCall, $parameters);
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$settings = $this->user->getConfiguration()->getSettings();
if ($settings->isRecaptchaEnabled()) {
$captcha = $this->getParam("captcha");
$req = new VerifyCaptcha($this->user);
if (!$req->execute(array("captcha" => $captcha, "action" => "contact"))) {
return $this->createError($req->getLastError());
}
}
if (!$this->insertContactRequest()) {
return false;
}
$this->createNotification();
if (!$this->success) {
return $this->createError("The contact request was saved, but the server was unable to create a notification.");
}
return $this->success;
}
private function insertContactRequest() {
$sql = $this->user->getSQL();
$name = $this->getParam("fromName");
$email = $this->getParam("fromEmail");
$message = $this->getParam("message");
$res = $sql->insert("ContactRequest", array("from_name", "from_email", "message"))
->addRow($name, $email, $message)
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->contactRequestId = $sql->getLastInsertId();
}
return $this->success;
}
private function createNotification() {
$sql = $this->user->getSQL();
$name = $this->getParam("fromName");
$email = $this->getParam("fromEmail");
$message = $this->getParam("message");
$res = $sql->insert("Notification", array("title", "message", "type"))
->addRow("New Contact Request from: $name", "$name ($email) wrote:\n$message", "message")
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->notificationId = $sql->getLastInsertId();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow(USER_GROUP_ADMIN, $this->notificationId)
->addRow(USER_GROUP_SUPPORT, $this->notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
}
}

@ -1,39 +0,0 @@
<?php
namespace Api;
class GetLanguages extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array());
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$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(empty($res) === 0) {
$this->lastError = L("No languages found");
} else {
foreach($res as $row) {
$this->result['languages'][$row['uid']] = $row;
}
}
}
return $this->success;
}
};
?>

@ -0,0 +1,194 @@
<?php
namespace Api {
use Driver\SQL\Condition\Compare;
abstract class GroupsAPI extends Request {
protected function groupExists($name) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("Group")
->where(new Compare("name", $name))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success && $res[0]["count"] > 0;
}
}
}
namespace Api\Groups {
use Api\GroupsAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
class Fetch extends GroupsAPI {
private int $groupCount;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
'count' => new Parameter('count', Parameter::TYPE_INT, true, 20)
));
$this->groupCount = 0;
}
private function getGroupCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("Group")->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->groupCount = $res[0]["count"];
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$page = $this->getParam("page");
if($page < 1) {
return $this->createError("Invalid page count");
}
$count = $this->getParam("count");
if($count < 1 || $count > 50) {
return $this->createError("Invalid fetch count");
}
if (!$this->getGroupCount()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id"))
->from("Group")
->leftJoin("UserGroup", "UserGroup.group_id", "Group.uid")
->groupBy("Group.uid")
->orderBy("Group.uid")
->ascending()
->limit($count)
->offset(($page - 1) * $count)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["groups"] = array();
foreach($res as $row) {
$groupId = intval($row["groupId"]);
$groupName = $row["groupName"];
$groupColor = $row["groupColor"];
$memberCount = $row["usergroup_user_id_count"];
$this->result["groups"][$groupId] = array(
"name" => $groupName,
"memberCount" => $memberCount,
"color" => $groupColor,
);
}
$this->result["pageCount"] = intval(ceil($this->groupCount / $count));
$this->result["totalCount"] = $this->groupCount;
}
return $this->success;
}
}
class Create extends GroupsAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'name' => new StringType('name', 32),
'color' => new StringType('color', 10),
));
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$name = $this->getParam("name");
if (preg_match("/^[a-zA-Z][a-zA-Z0-9_-]*$/", $name) !== 1) {
return $this->createError("Invalid name");
}
$color = $this->getParam("color");
if (preg_match("/^#[a-fA-F0-9]{3,6}$/", $color) !== 1) {
return $this->createError("Invalid color");
}
$exists = $this->groupExists($name);
if (!$this->success) {
return false;
} else if ($exists) {
return $this->createError("A group with this name already exists");
}
$sql = $this->user->getSQL();
$res = $sql->insert("Group", array("name", "color"))
->addRow($name, $color)
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["uid"] = $sql->getLastInsertId();
}
return $this->success;
}
}
class Delete extends GroupsAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'uid' => new Parameter('uid', Parameter::TYPE_INT)
));
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$id = $this->getParam("uid");
if (in_array($id, DEFAULT_GROUPS)) {
return $this->createError("You cannot delete a default group.");
}
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("Group")
->where(new Compare("uid", $id))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success && $res[0]["count"] === 0) {
return $this->createError("This group does not exist.");
}
$res = $sql->delete("Group")->where(new Compare("uid", $id))->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
}
}

@ -0,0 +1,128 @@
<?php
namespace Api {
abstract class LanguageAPI extends Request {
}
}
namespace Api\Language {
use Api\LanguageAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondOr;
use Objects\Language;
class Get extends LanguageAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$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(empty($res) === 0) {
$this->lastError = L("No languages found");
} else {
foreach($res as $row) {
$this->result['languages'][$row['uid']] = $row;
}
}
}
return $this->success;
}
}
class Set extends LanguageAPI {
private Language $language;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
'langCode' => new StringType('langCode', 5, true, NULL),
));
}
private function checkLanguage() {
$langId = $this->getParam("langId");
$langCode = $this->getParam("langCode");
if(is_null($langId) && is_null($langCode)) {
return $this->createError(L("Either langId or langCode must be given"));
}
$res = $this->user->getSQL()
->select("uid", "code", "name")
->from("Language")
->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(count($res) == 0) {
return $this->createError(L("This Language does not exist"));
} else {
$row = $res[0];
$this->language = Language::newInstance($row['uid'], $row['code'], $row['name']);
if(!$this->language) {
return $this->createError(L("Error while loading language"));
}
}
}
return $this->success;
}
private function updateLanguage() {
$languageId = $this->language->getId();
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$this->success = $sql->update("User")
->set("language_id", $languageId)
->where(new Compare("uid", $userId))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if(!$this->checkLanguage())
return false;
if($this->user->isLoggedIn()) {
$this->updateLanguage();
}
$this->user->setLanguage($this->language);
return $this->success;
}
}
}

120
core/Api/MailAPI.class.php Normal file

@ -0,0 +1,120 @@
<?php
namespace Api {
abstract class MailAPI extends Request {
}
}
namespace Api\Mail {
use Api\MailAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use External\PHPMailer\Exception;
use External\PHPMailer\PHPMailer;
use Objects\ConnectionData;
use Objects\User;
class Test extends MailAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"receiver" => new Parameter("receiver", Parameter::TYPE_EMAIL)
));
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$receiver = $this->getParam("receiver");
$req = new \Api\Mail\Send($this->user);
$this->success = $req->execute(array(
"to" => $receiver,
"subject" => "Test E-Mail",
"body" => "Hey! If you receive this e-mail, your mail configuration seems to be working."
));
$this->lastError = $req->getLastError();
return $this->success;
}
}
class Send extends MailAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
'subject' => new StringType('subject', -1),
'body' => new StringType('body', -1),
));
$this->isPublic = false;
}
private function getMailConfig() : ?ConnectionData {
$req = new \Api\Settings\Get($this->user);
$this->success = $req->execute(array("key" => "^mail_"));
$this->lastError = $req->getLastError();
if ($this->success) {
$settings = $req->getResult()["settings"];
if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") {
$this->createError("Mail is not configured yet.");
return null;
}
$host = $settings["mail_host"] ?? "localhost";
$port = intval($settings["mail_port"] ?? "25");
$login = $settings["mail_username"] ?? "";
$password = $settings["mail_password"] ?? "";
$connectionData = new ConnectionData($host, $port, $login, $password);
$connectionData->setProperty("from", $settings["mail_from"] ?? "");
return $connectionData;
}
return null;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$mailConfig = $this->getMailConfig();
if (!$this->success) {
return false;
}
try {
$mail = new PHPMailer;
$mail->IsSMTP();
$mail->setFrom($mailConfig->getProperty("from"));
$mail->addAddress($this->getParam('to'));
$mail->Subject = $this->getParam('subject');
$mail->SMTPDebug = 0;
$mail->Host = $mailConfig->getHost();
$mail->Port = $mailConfig->getPort();
$mail->SMTPAuth = true;
$mail->Username = $mailConfig->getLogin();
$mail->Password = $mailConfig->getPassword();
$mail->SMTPSecure = 'tls';
$mail->IsHTML(true);
$mail->CharSet = 'UTF-8';
$mail->Body = $this->getParam('body');
$this->success = @$mail->Send();
if (!$this->success) {
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
error_log("sendMail() failed: $mail->ErrorInfo");
}
} catch (Exception $e) {
$this->success = false;
$this->lastError = "Error sending Mail: $e";
}
return $this->success;
}
}
}

@ -1,135 +0,0 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Create extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
'title' => new StringType('title', 32),
'message' => new StringType('message', 256),
));
$this->isPublic = false;
}
private function checkUser($userId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("User")
->where(new Compare("uid", $userId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "User not found";
}
}
return $this->success;
}
private function insertUserNotification($userId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
->addRow($userId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function checkGroup($groupId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("Group")
->where(new Compare("uid", $groupId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "Group not found";
}
}
return $this->success;
}
private function insertGroupNotification($groupId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow($groupId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function createNotification($title, $message) {
$sql = $this->user->getSQL();
$res = $sql->insert("Notification", array("title", "message"))
->addRow($title, $message)
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userId = $this->getParam("userId");
$groupId = $this->getParam("groupId");
$title = $this->getParam("title");
$message = $this->getParam("message");
if (is_null($userId) && is_null($groupId)) {
return $this->createError("Either userId or groupId must be specified.");
} else if(!is_null($userId) && !is_null($groupId)) {
return $this->createError("Only one of userId and groupId must be specified.");
} else if(!is_null($userId)) {
if ($this->checkUser($userId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertUserNotification($userId, $id);
}
}
} else if(!is_null($groupId)) {
if ($this->checkGroup($groupId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertGroupNotification($groupId, $id);
}
}
}
return $this->success;
}
};
?>

@ -1,94 +0,0 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Driver\SQL\Condition\Compare;
class Fetch extends Request {
private $notifications;
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array());
$this->loginRequired = true;
}
private function fetchUserNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
->where(new Compare("UserNotification.user_id", $userId))
->where(new Compare("UserNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
private function fetchGroupNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
->where(new Compare("UserGroup.user_id", $userId))
->where(new Compare("GroupNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->notifications = array();
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
$this->result["notifications"] = $this->notifications;
}
return $this->success;
}
};
?>

@ -0,0 +1,268 @@
<?php
namespace Api {
abstract class NotificationsAPI extends Request {
}
}
namespace Api\Notifications {
use Api\NotificationsAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondIn;
use Driver\SQL\Query\Select;
use Objects\User;
class Create extends NotificationsAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
'title' => new StringType('title', 32),
'message' => new StringType('message', 256),
));
$this->isPublic = false;
}
private function checkUser($userId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("User")
->where(new Compare("uid", $userId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "User not found";
}
}
return $this->success;
}
private function insertUserNotification($userId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
->addRow($userId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function checkGroup($groupId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("Group")
->where(new Compare("uid", $groupId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "Group not found";
}
}
return $this->success;
}
private function insertGroupNotification($groupId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow($groupId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function createNotification($title, $message) {
$sql = $this->user->getSQL();
$res = $sql->insert("Notification", array("title", "message"))
->addRow($title, $message)
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userId = $this->getParam("userId");
$groupId = $this->getParam("groupId");
$title = $this->getParam("title");
$message = $this->getParam("message");
if (is_null($userId) && is_null($groupId)) {
return $this->createError("Either userId or groupId must be specified.");
} else if(!is_null($userId) && !is_null($groupId)) {
return $this->createError("Only one of userId and groupId must be specified.");
} else if(!is_null($userId)) {
if ($this->checkUser($userId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertUserNotification($userId, $id);
}
}
} else if(!is_null($groupId)) {
if ($this->checkGroup($groupId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertGroupNotification($groupId, $id);
}
}
}
return $this->success;
}
}
class Fetch extends NotificationsAPI {
private array $notifications;
private array $notificationids;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'new' => new Parameter('new', Parameter::TYPE_BOOLEAN, true, true)
));
$this->loginRequired = true;
}
private function fetchUserNotifications() {
$onlyNew = $this->getParam('new');
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type")
->from("Notification")
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
->where(new Compare("UserNotification.user_id", $userId))
->orderBy("created_at")->descending();
if ($onlyNew) {
$query->where(new Compare("UserNotification.seen", false));
}
return $this->fetchNotifications($query);
}
private function fetchGroupNotifications() {
$onlyNew = $this->getParam('new');
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type")
->from("Notification")
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
->where(new Compare("UserGroup.user_id", $userId))
->orderBy("created_at")->descending();
if ($onlyNew) {
$query->where(new Compare("GroupNotification.seen", false));
}
return $this->fetchNotifications($query);
}
private function fetchNotifications(Select $query) {
$sql = $this->user->getSQL();
$res = $query->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!in_array($id, $this->notificationids)) {
$this->notificationids[] = $id;
$this->notifications[] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
"type" => $row["type"]
);
}
}
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->notifications = array();
$this->notificationids = array();
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
$this->result["notifications"] = $this->notifications;
}
return $this->success;
}
}
class Seen extends NotificationsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->update("UserNotification")
->set("seen", true)
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$res = $sql->update("GroupNotification")
->set("seen", true)
->where(new CondIn("group_id",
$sql->select("group_id")
->from("UserGroup")
->where(new Compare("user_id", $this->user->getId()))))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
}
}

@ -2,6 +2,8 @@
namespace Api\Parameter; namespace Api\Parameter;
use DateTime;
class Parameter { class Parameter {
const TYPE_INT = 0; const TYPE_INT = 0;
const TYPE_FLOAT = 1; const TYPE_FLOAT = 1;
@ -14,15 +16,17 @@ class Parameter {
// only internal access // only internal access
const TYPE_RAW = 8; const TYPE_RAW = 8;
// only json will work here i guess
const TYPE_ARRAY = 9; const TYPE_ARRAY = 9;
const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array'); const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array');
public $name; public string $name;
public $value; public $value;
public $optional; public $optional;
public $type; public int $type;
public $typeName; public string $typeName;
public function __construct($name, $type, $optional = FALSE, $defaultValue = NULL) { public function __construct($name, $type, $optional = FALSE, $defaultValue = NULL) {
$this->name = $name; $this->name = $name;
@ -59,11 +63,11 @@ class Parameter {
return Parameter::TYPE_BOOLEAN; return Parameter::TYPE_BOOLEAN;
else if(is_a($value, 'DateTime')) else if(is_a($value, 'DateTime'))
return Parameter::TYPE_DATE_TIME; return Parameter::TYPE_DATE_TIME;
else if(($d = \DateTime::createFromFormat('Y-m-d', $value)) && $d->format('Y-m-d') === $value) else if(($d = DateTime::createFromFormat('Y-m-d', $value)) && $d->format('Y-m-d') === $value)
return Parameter::TYPE_DATE; return Parameter::TYPE_DATE;
else if(($d = \DateTime::createFromFormat('H:i:s', $value)) && $d->format('H:i:s') === $value) else if(($d = DateTime::createFromFormat('H:i:s', $value)) && $d->format('H:i:s') === $value)
return Parameter::TYPE_TIME; return Parameter::TYPE_TIME;
else if(($d = \DateTime::createFromFormat('Y-m-d H:i:s', $value)) && $d->format('Y-m-d H:i:s') === $value) else if(($d = DateTime::createFromFormat('Y-m-d H:i:s', $value)) && $d->format('Y-m-d H:i:s') === $value)
return Parameter::TYPE_DATE_TIME; return Parameter::TYPE_DATE_TIME;
else if (filter_var($value, FILTER_VALIDATE_EMAIL)) else if (filter_var($value, FILTER_VALIDATE_EMAIL))
return Parameter::TYPE_EMAIL; return Parameter::TYPE_EMAIL;
@ -157,5 +161,3 @@ class Parameter {
} }
} }
} }
?>

@ -4,10 +4,10 @@ namespace Api\Parameter;
class StringType extends Parameter { class StringType extends Parameter {
public $maxLength; public int $maxLength;
public function __construct($name, $maxLength = -1, $optional = FALSE, $defaultValue = NULL) { public function __construct($name, $maxLength = -1, $optional = FALSE, $defaultValue = NULL) {
parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue);
$this->maxLength = $maxLength; $this->maxLength = $maxLength;
parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue);
} }
public function parseParam($value) { public function parseParam($value) {
@ -39,5 +39,3 @@ class StringType extends Parameter {
return $str; return $str;
} }
} }
?>

@ -0,0 +1,213 @@
<?php
namespace Api {
abstract class PermissionAPI extends Request {
protected function checkStaticPermission() {
if (!$this->user->isLoggedIn() || !$this->user->hasGroup(USER_GROUP_ADMIN)) {
return $this->createError("Permission denied.");
}
return true;
}
}
}
namespace Api\Permission {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\PermissionAPI;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondIn;
use Driver\SQL\Condition\CondNot;
use Driver\SQL\Strategy\UpdateStrategy;
use Objects\User;
class Check extends PermissionAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'method' => new StringType('method', 323)
));
$this->isPublic = false;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$method = $this->getParam("method");
$sql = $this->user->getSQL();
$res = $sql->select("groups")
->from("ApiPermission")
->where(new Compare("method", $method))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if (empty($res)) {
return true;
}
$groups = json_decode($res[0]["groups"]);
if (empty($groups)) {
return true;
}
if (!$this->user->isLoggedIn() || empty(array_intersect($groups, array_keys($this->user->getGroups())))) {
header('HTTP 1.1 401 Unauthorized');
return $this->createError("Permission denied.");
}
}
return $this->success;
}
}
class Fetch extends PermissionAPI {
private array $groups;
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
private function fetchGroups() {
$sql = $this->user->getSQL();
$res = $sql->select("uid", "name", "color")
->from("Group")
->orderBy("uid")
->ascending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->groups = array();
foreach($res as $row) {
$groupId = $row["uid"];
$groupName = $row["name"];
$groupColor = $row["color"];
$this->groups[$groupId] = array("name" => $groupName, "color" => $groupColor);
}
}
return $this->success;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
if (!$this->checkStaticPermission()) {
return false;
}
if (!$this->fetchGroups()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("method", "groups", "description")
->from("ApiPermission")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$permissions = array();
foreach ($res as $row) {
$method = $row["method"];
$description = $row["description"];
$groups = json_decode($row["groups"]);
$permissions[] = array(
"method" => $method,
"groups" => $groups,
"description" => $description
);
}
$this->result["permissions"] = $permissions;
$this->result["groups"] = $this->groups;
}
return $this->success;
}
}
class Save extends PermissionAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'permissions' => new Parameter('permissions', Parameter::TYPE_ARRAY)
));
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
if (!$this->checkStaticPermission()) {
return false;
}
$permissions = $this->getParam("permissions");
$sql = $this->user->getSQL();
$methodParam = new StringType('method', 32);
$groupsParam = new Parameter('groups', Parameter::TYPE_ARRAY);
$updateQuery = $sql->insert("ApiPermission", array("method", "groups"))
->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array( "groups" => new Column("groups") )));
$insertedMethods = array();
foreach($permissions as $permission) {
if (!is_array($permission)) {
return $this->createError("Invalid data type found in parameter: permissions, expected: object");
} else if(!isset($permission["method"]) || !array_key_exists("groups", $permission)) {
return $this->createError("Invalid object found in parameter: permissions, expected keys 'method' and 'groups'");
} else if (!$methodParam->parseParam($permission["method"])) {
$expectedType = $methodParam->getTypeName();
return $this->createError("Invalid data type found for attribute 'method', expected: $expectedType");
} else if(!$groupsParam->parseParam($permission["groups"])) {
$expectedType = $groupsParam->getTypeName();
return $this->createError("Invalid data type found for attribute 'groups', expected: $expectedType");
} else if(empty(trim($methodParam->value))) {
return $this->createError("Method cannot be empty.");
} else {
$method = $methodParam->value;
$groups = $groupsParam->value;
$updateQuery->addRow($method, $groups);
$insertedMethods[] = $method;
}
}
if (!empty($permissions)) {
$res = $updateQuery->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
}
if ($this->success) {
$res = $sql->delete("ApiPermission")
->where(new Compare("description", "")) // only delete non default permissions
->where(new CondNot(new CondIn("method", $insertedMethods)))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
}
}

@ -2,36 +2,41 @@
namespace Api; namespace Api;
use Objects\User;
class Request { class Request {
protected $user; protected User $user;
protected $params; protected array $params;
protected $lastError; protected string $lastError;
protected $result; protected array $result;
protected $success; protected bool $success;
protected $isPublic; protected bool $isPublic;
protected $loginRequired; protected bool $loginRequired;
protected $variableParamCount; protected bool $variableParamCount;
protected $isDisabled; protected bool $isDisabled;
protected $apiKeyAllowed; protected bool $apiKeyAllowed;
protected bool $csrfTokenRequired;
private $aDefaultParams; private array $aDefaultParams;
private $allowedMethods; private array $allowedMethods;
private $externCall; private bool $externalCall;
public function __construct($user, $externCall = false, $params = array()) { public function __construct(User $user, bool $externalCall = false, array $params = array()) {
$this->user = $user; $this->user = $user;
$this->aDefaultParams = $params; $this->aDefaultParams = $params;
$this->lastError = '';
$this->success = false; $this->success = false;
$this->result = array(); $this->result = array();
$this->externCall = $externCall; $this->externalCall = $externalCall;
$this->isPublic = true; $this->isPublic = true;
$this->isDisabled = false; $this->isDisabled = false;
$this->loginRequired = false; $this->loginRequired = false;
$this->variableParamCount = false; $this->variableParamCount = false;
$this->apiKeyAllowed = true; $this->apiKeyAllowed = true;
$this->allowedMethods = array("GET", "POST"); $this->allowedMethods = array("GET", "POST");
$this->lastError = "";
$this->csrfTokenRequired = true;
} }
protected function forbidMethod($method) { protected function forbidMethod($method) {
@ -40,33 +45,20 @@ class Request {
} }
} }
public function getParamsString() {
$str = "";
$count = count($this->params);
$i = 0;
foreach($this->params as $param) {
$str .= $param->toString();
if($i < $count - 1) $str .= ", ";
$i++;
}
return "($str)";
}
public function parseParams($values) { public function parseParams($values) {
foreach($this->params as $name => $param) {
$value = (isset($values[$name]) ? $values[$name] : NULL);
if(!$param->optional && is_null($value)) { foreach($this->params as $name => $param) {
$this->lastError = 'Missing parameter: ' . $name; $value = $values[$name] ?? NULL;
return false;
$isEmpty = (is_string($value) || is_array($value)) && empty($value);
if(!$param->optional && (is_null($value) || $isEmpty)) {
return $this->createError("Missing parameter: $name");
} }
if(!is_null($value)) { if(!is_null($value) && !$isEmpty) {
if(!$param->parseParam($value)) { if(!$param->parseParam($value)) {
$value = print_r($value, true); $value = print_r($value, true);
$this->lastError = "Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")"; return $this->createError("Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")");
return false;
} }
} }
} }
@ -93,11 +85,17 @@ class Request {
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds(); $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
} }
if($this->externCall) { if($this->externalCall) {
$values = $_REQUEST; $values = $_REQUEST;
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) { if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) {
$jsonData = json_decode(file_get_contents('php://input'), true); $jsonData = json_decode(file_get_contents('php://input'), true);
if ($jsonData) {
$values = array_merge($values, $jsonData); $values = array_merge($values, $jsonData);
} else {
$this->lastError = 'Invalid request body.';
header('HTTP 1.1 400 Bad Request');
return false;
}
} }
} }
@ -106,7 +104,7 @@ class Request {
return false; return false;
} }
if($this->externCall && !$this->isPublic) { if($this->externalCall && !$this->isPublic) {
$this->lastError = 'This function is private.'; $this->lastError = 'This function is private.';
header('HTTP 1.1 403 Forbidden'); header('HTTP 1.1 403 Forbidden');
return false; return false;
@ -118,21 +116,45 @@ class Request {
return false; return false;
} }
if($this->externalCall) {
$apiKeyAuthorized = false;
if($this->loginRequired) { // Logged in or api key authorized?
$authorized = false; if ($this->loginRequired) {
if(isset($values['api_key']) && $this->apiKeyAllowed) { if(isset($values['api_key']) && $this->apiKeyAllowed) {
$apiKey = $values['api_key']; $apiKey = $values['api_key'];
$authorized = $this->user->authorize($apiKey); $apiKeyAuthorized = $this->user->authorize($apiKey);
} }
if(!$this->user->isLoggedIn() && !$authorized) { if(!$this->user->isLoggedIn() && !$apiKeyAuthorized) {
$this->lastError = 'You are not logged in.'; $this->lastError = 'You are not logged in.';
header('HTTP 1.1 401 Unauthorized'); header('HTTP 1.1 401 Unauthorized');
return false; return false;
} }
} }
// CSRF Token
if($this->csrfTokenRequired && $this->user->isLoggedIn()) {
// csrf token required + external call
// if it's not a call with API_KEY, check for csrf_token
if (!isset($values["csrf_token"]) || strcmp($values["csrf_token"], $this->user->getSession()->getCsrfToken()) !== 0) {
$this->lastError = "CSRF-Token mismatch";
header('HTTP 1.1 403 Forbidden');
return false;
}
}
// Check for permission
if (!($this instanceof \Api\Permission\Save)) {
$req = new \Api\Permission\Check($this->user);
$this->success = $req->execute(array("method" => $this->getMethod()));
$this->lastError = $req->getLastError();
if (!$this->success) {
return false;
}
}
}
if(!$this->parseParams($values)) if(!$this->parseParams($values))
return false; return false;
@ -149,47 +171,32 @@ class Request {
return true; return true;
} }
protected function isValidString($str, $regex) {
return preg_replace($regex, "", $str) === $str;
}
protected function createError($err) { protected function createError($err) {
$this->success = false; $this->success = false;
$this->lastError = $err; $this->lastError = $err;
return false; return false;
} }
//
// public static function callDirectly($class, $db) {
// header('Content-Type: application/json');
// require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/api/objects/User.php';
// require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/sql.php';
// require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/conf/sql.php';
//
// $sql = connectSQL(getSqlData($db));
// $user = new CUser($sql);
// $request = new $class($user, true);
// $request->execute();
// $sql->close();
// $user->sendCookies();
// return $request->getJsonResult();
// }
protected function getParam($name) { return isset($this->params[$name]) ? $this->params[$name]->value : NULL; } protected function getParam($name) {
return isset($this->params[$name]) ? $this->params[$name]->value : NULL;
}
public function isPublic() { return $this->isPublic; } public function isPublic() { return $this->isPublic; }
public function getDescription() { return ''; }
public function getSection() { return 'Default'; }
public function getLastError() { return $this->lastError; } public function getLastError() { return $this->lastError; }
public function getResult() { return $this->result; } public function getResult() { return $this->result; }
public function success() { return $this->success; } public function success() { return $this->success; }
public function loginRequired() { return $this->loginRequired; } public function loginRequired() { return $this->loginRequired; }
public function isExternCall() { return $this->externCall; } public function isExternalCall() { return $this->externalCall; }
private function getMethod() {
$class = str_replace("\\", "/", get_class($this));
$class = substr($class, strlen("api/"));
return $class;
}
public function getJsonResult() { public function getJsonResult() {
$this->result['success'] = $this->success; $this->result['success'] = $this->success;
$this->result['msg'] = $this->lastError; $this->result['msg'] = $this->lastError;
return json_encode($this->result); return json_encode($this->result);
} }
}; }
?>

@ -0,0 +1,217 @@
<?php
namespace Api {
abstract class RoutesAPI extends Request {
protected function formatRegex(string $input, bool $append) : string {
$start = startsWith($input, "^");
$end = endsWith($input, "$");
if ($append) {
if (!$start) $input = "^$input";
if (!$end) $input = "$input$";
} else {
if ($start) $input = substr($input, 1);
if ($end) $input = substr($input, 0, strlen($input)-1);
}
return $input;
}
}
}
namespace Api\Routes {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\RoutesAPI;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\CondRegex;
class Fetch extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra", "active")
->from("Route")
->orderBy("uid")
->ascending()
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
$routes = array();
foreach($res as $row) {
$routes[] = array(
"uid" => intval($row["uid"]),
"request" => $this->formatRegex($row["request"], false),
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"] ?? "",
"active" => intval($sql->parseBool($row["active"])),
);
}
$this->result["routes"] = $routes;
}
return $this->success;
}
}
class Find extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'request' => new StringType('request', 128, true, '/')
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$request = $this->getParam('request');
if (!startsWith($request, '/')) {
$request = "/$request";
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra")
->from("Route")
->where(new CondBool("active"))
->where(new CondRegex($request, new Column("request")))
->limit(1)
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
if (!empty($res)) {
$row = $res[0];
$this->result["route"] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"]
);
} else {
$this->result["route"] = NULL;
}
}
return $this->success;
}
}
class Save extends RoutesAPI {
private array $routes;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'routes' => new Parameter('routes',Parameter::TYPE_ARRAY, false)
));
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if (!$this->validateRoutes()) {
return false;
}
$sql = $this->user->getSQL();
// DELETE old rules
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
$this->lastError = $sql->getLastError();
// INSERT new routes
if ($this->success) {
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active"));
foreach($this->routes as $route) {
$stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"]);
}
$this->success = ($stmt->execute() !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
private function validateRoutes() {
$this->routes = array();
$keys = array(
"request" => Parameter::TYPE_STRING,
"action" => Parameter::TYPE_STRING,
"target" => Parameter::TYPE_STRING,
"extra" => Parameter::TYPE_STRING,
"active" => Parameter::TYPE_BOOLEAN
);
$actions = array(
"redirect_temporary", "redirect_permanently", "static", "dynamic"
);
foreach($this->getParam("routes") as $index => $route) {
foreach($keys as $key => $expectedType) {
if (!array_key_exists($key, $route)) {
return $this->createError("Route $index missing key: $key");
}
$value = $route[$key];
$type = Parameter::parseType($value);
if ($type !== $expectedType) {
$expectedTypeName = Parameter::names[$expectedType];
$gotTypeName = Parameter::names[$type];
return $this->createError("Route $index has invalid value for key: $key, expected: $expectedTypeName, got: $gotTypeName");
}
}
$action = $route["action"];
if (!in_array($action, $actions)) {
return $this->createError("Invalid action: $action");
}
if(empty($route["request"])) {
return $this->createError("Request cannot be empty.");
}
if(empty($route["target"])) {
return $this->createError("Target cannot be empty.");
}
// add start- and end pattern for database queries
$route["request"] = $this->formatRegex($route["request"], true);
$this->routes[] = $route;
}
return true;
}
}
}

@ -1,58 +0,0 @@
<?php
namespace Api;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
class SendMail extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'from' => new Parameter('from', Parameter::TYPE_EMAIL),
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
'subject' => new StringType('subject', -1),
'body' => new StringType('body', -1),
'fromName' => new StringType('fromName', -1, true, ''),
'replyTo' => new Parameter('to', Parameter::TYPE_EMAIL, true, ''),
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$mailConfig = $this->user->getConfiguration()->getMail();
$mail = new \External\PHPMailer\PHPMailer;
$mail->IsSMTP();
$mail->setFrom($this->getParam('from'), $this->getParam('fromName'));
$mail->addAddress($this->getParam('to'));
$mail->Subject = $this->getParam('subject');
$mail->SMTPDebug = 0;
$mail->Host = $mailConfig->getHost();
$mail->Port = $mailConfig->getPort();
$mail->SMTPAuth = true;
$mail->Username = $mailConfig->getLogin();
$mail->Password = $mailConfig->getPassword();
$mail->SMTPSecure = 'tls';
$mail->IsHTML(true);
$mail->CharSet = 'UTF-8';
$mail->Body = $this->getParam('body');
$replyTo = $this->getParam('replyTo');
if(!is_null($replyTo) && !empty($replyTo)) {
$mail->AddReplyTo($replyTo, $this->getParam('fromName'));
}
$this->success = @$mail->Send();
if (!$this->success) {
$this->lastError = 'Error sending Mail: ' . $mail->ErrorInfo;
error_log("sendMail() failed: " . $mail->ErrorInfo);
}
return $this->success;
}
};
?>

@ -1,83 +0,0 @@
<?php
namespace Api;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Condition\Compare;
class SetLanguage extends Request {
private $language;
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
'langCode' => new StringType('langCode', 5, true, NULL),
));
}
private function checkLanguage() {
$langId = $this->getParam("langId");
$langCode = $this->getParam("langCode");
if(is_null($langId) && is_null($langCode)) {
return $this->createError(L("Either langId or langCode must be given"));
}
$res = $this->user->getSQL()
->select("uid", "code", "name")
->from("Language")
->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(count($res) == 0) {
return $this->createError(L("This Language does not exist"));
} else {
$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"));
}
}
}
return $this->success;
}
private function updateLanguage() {
$languageId = $this->language->getId();
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$this->success = $sql->update("User")
->set("language_id", $languageId)
->where(new Compare("uid", $userId))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if(!$this->checkLanguage())
return false;
if($this->user->isLoggedIn()) {
$this->updateLanguage();
}
$this->user->setLangauge($this->language);
return $this->success;
}
};
?>

@ -0,0 +1,169 @@
<?php
namespace Api {
abstract class SettingsAPI extends Request {
}
}
namespace Api\Settings {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\SettingsAPI;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\CondIn;
use Driver\SQL\Condition\CondNot;
use Driver\SQL\Condition\CondRegex;
use Driver\SQL\Strategy\UpdateStrategy;
use Objects\User;
class Get extends SettingsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'key' => new StringType('key', -1, true, NULL)
));
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$key = $this->getParam("key");
$sql = $this->user->getSQL();
$query = $sql->select("name", "value") ->from("Settings");
if (!is_null($key) && !empty($key)) {
$query->where(new CondRegex(new Column("name"), $key));
}
// filter sensitive values, if called from outside
if ($this->isExternalCall()) {
$query->where(new CondNot("private"));
}
$res = $query->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$settings = array();
foreach($res as $row) {
$settings[$row["name"]] = $row["value"];
}
$this->result["settings"] = $settings;
}
return $this->success;
}
}
class Set extends SettingsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'settings' => new Parameter('settings', Parameter::TYPE_ARRAY)
));
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$values = $this->getParam("settings");
if (empty($values)) {
return $this->createError("No values given.");
}
$paramKey = new StringType('key', 32);
$paramValue = new StringType('value', 1024, true, NULL);
$sql = $this->user->getSQL();
$query = $sql->insert("Settings", array("name", "value"));
$keys = array();
$deleteKeys = array();
foreach($values as $key => $value) {
if (!$paramKey->parseParam($key)) {
$key = print_r($key, true);
return $this->createError("Invalid Type for key in parameter settings: '$key' (Required: " . $paramKey->getTypeName() . ")");
} else if(!is_null($value) && !$paramValue->parseParam($value)) {
$value = print_r($value, true);
return $this->createError("Invalid Type for value in parameter settings: '$value' (Required: " . $paramValue->getTypeName() . ")");
} else if(preg_match("/^[a-zA-Z_][a-zA-Z_0-9-]*$/", $paramKey->value) !== 1) {
return $this->createError("The property key should only contain alphanumeric characters, underscores and dashes");
} else {
if (!is_null($paramValue->value)) {
$query->addRow($paramKey->value, $paramValue->value);
} else {
$deleteKeys[] = $paramKey->value;
}
$keys[] = $paramKey->value;
}
}
if ($this->isExternalCall()) {
$column = $this->checkReadonly($keys);
if(!$this->success) {
return false;
} else if($column !== null) {
return $this->createError("Column '$column' is readonly.");
}
}
if (!empty($deleteKeys) && !$this->deleteKeys($keys)) {
return false;
}
if (count($deleteKeys) !== count($keys)) {
$query->onDuplicateKeyStrategy(new UpdateStrategy(
array("name"),
array("value" => new Column("value")))
);
$this->success = ($query->execute() !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
private function checkReadonly(array $keys) {
$sql = $this->user->getSQL();
$res = $sql->select("name")
->from("Settings")
->where(new CondBool("readonly"))
->where(new CondIn("name", $keys))
->limit(1)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success && !empty($res)) {
return $res[0]["name"];
}
return null;
}
private function deleteKeys(array $keys) {
$sql = $this->user->getSQL();
$res = $sql->delete("Settings")
->where(new CondIn("name", $keys))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
}
}

90
core/Api/Stats.class.php Normal file

@ -0,0 +1,90 @@
<?php
namespace Api;
use Driver\SQL\Condition\CondBool;
class Stats extends Request {
private bool $mailConfigured;
private bool $recaptchaConfigured;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
private function getUserCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("User")->execute();
$this->success = $this->success && ($res !== FALSE);
$this->lastError = $sql->getLastError();
return ($this->success ? $res[0]["count"] : 0);
}
private function getPageCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("Route")
->where(new CondBool("active"))
->execute();
$this->success = $this->success && ($res !== FALSE);
$this->lastError = $sql->getLastError();
return ($this->success ? $res[0]["count"] : 0);
}
private function checkSettings() {
$req = new \Api\Settings\Get($this->user);
$this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$"));
$this->lastError = $req->getLastError();
if ($this->success) {
$settings = $req->getResult()["settings"];
$this->mailConfigured = ($settings["mail_enabled"] ?? "0") === "1";
$this->recaptchaConfigured = ($settings["recaptcha_enabled"] ?? "0") === "1";
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userCount = $this->getUserCount();
$pageCount = $this->getPageCount();
$req = new \Api\Visitors\Stats($this->user);
$this->success = $req->execute(array("type"=>"monthly"));
$this->lastError = $req->getLastError();
if (!$this->success) {
return false;
}
$visitorStatistics = $req->getResult()["visitors"];
$loadAvg = "Unknown";
if (function_exists("sys_getloadavg")) {
$loadAvg = sys_getloadavg();
}
if (!$this->checkSettings()) {
return false;
}
$this->result["userCount"] = $userCount;
$this->result["pageCount"] = $pageCount;
$this->result["visitors"] = $visitorStatistics;
$this->result["server"] = array(
"version" => WEBBASE_VERSION,
"server" => $_SERVER["SERVER_SOFTWARE"] ?? "Unknown",
"memory_usage" => memory_get_usage(),
"load_avg" => $loadAvg,
"database" => $this->user->getSQL()->getStatus(),
"mail" => $this->mailConfigured,
"reCaptcha" => $this->recaptchaConfigured
);
return $this->success;
}
}

@ -1,82 +0,0 @@
<?php
namespace Api\User;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Login extends Request {
private $startedAt;
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'username' => new StringType('username', 32),
'password' => new StringType('password'),
'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, true)
));
$this->forbidMethod("GET");
}
private function wrongCredentials() {
$runtime = microtime(true) - $this->startedAt;
$sleepTime = round(3e6 - $runtime);
if($sleepTime > 0) usleep($sleepTime);
return $this->createError(L('Wrong username or password'));
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if($this->user->isLoggedIn()) {
$this->lastError = L('You are already logged in');
$this->success = true;
return true;
}
$this->startedAt = microtime(true);
$this->success = false;
$username = $this->getParam('username');
$password = $this->getParam('password');
$stayLoggedIn = $this->getParam('stayLoggedIn');
$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) {
if(count($res) === 0) {
return $this->wrongCredentials();
} else {
$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: " . $sql->getLastError());
} else {
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
$this->success = true;
}
}
else {
return $this->wrongCredentials();
}
}
}
return $this->success;
}
};
?>

@ -1,26 +0,0 @@
<?php
namespace Api\User;
use \Api\Request;
class Logout extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall);
$this->loginRequired = true;
$this->apiKeyAllowed = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->success = $this->user->logout();
$this->lastError = $this->user->getSQL()->getLastError();
return $this->success;
}
};
?>

1140
core/Api/UserAPI.class.php Normal file

File diff suppressed because it is too large Load Diff

@ -0,0 +1,67 @@
<?php
namespace Api;
use Api\Parameter\StringType;
use Objects\User;
class VerifyCaptcha extends Request {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"captcha" => new StringType("captcha"),
"action" => new StringType("action"),
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$settings = $this->user->getConfiguration()->getSettings();
if (!$settings->isRecaptchaEnabled()) {
return $this->createError("Google reCaptcha is not enabled.");
}
$url = "https://www.google.com/recaptcha/api/siteverify";
$secret = $settings->getRecaptchaSecretKey();
$captcha = $this->getParam("captcha");
$action = $this->getParam("action");
$params = array(
"secret" => $secret,
"response" => $captcha
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = @json_decode(curl_exec($ch), true);
curl_close ($ch);
$this->success = false;
$this->lastError = "Could not verify captcha: No response from google received.";
if($response) {
$this->success = $response["success"];
if(!$this->success) {
$this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]);
} else {
$score = $response["score"];
if($action !== $response["action"]) {
$this->createError("Could not verify captcha: Action does not match");
}
else if($score < 0.7) {
$this->createError("Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot");
}
}
}
return $this->success;
}
}

@ -0,0 +1,88 @@
<?php
namespace Api {
abstract class VisitorsAPI extends Request {
}
}
namespace Api\Visitors {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\VisitorsAPI;
use DateTime;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Query\Select;
use Objects\User;
class Stats extends VisitorsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'type' => new StringType('type', 32),
'date' => new Parameter('date', Parameter::TYPE_DATE, true, new DateTime())
));
}
private function setConditions(string $type, DateTime $date, Select $query) {
if ($type === "yearly") {
$yearStart = $date->format("Y0000");
$yearEnd = $date->modify("+1 year")->format("Y0000");
$query->where(new Compare("day", $yearStart, ">="));
$query->where(new Compare("day", $yearEnd, "<"));
} else if($type === "monthly") {
$monthStart = $date->format("Ym00");
$monthEnd = $date->modify("+1 month")->format("Ym00");
$query->where(new Compare("day", $monthStart, ">="));
$query->where(new Compare("day", $monthEnd, "<"));
} else if($type === "weekly") {
$weekStart = ($date->modify("monday this week"))->format("Ymd");
$weekEnd = ($date->modify("sunday this week"))->format("Ymd");
$query->where(new Compare("day", $weekStart, ">="));
$query->where(new Compare("day", $weekEnd, "<="));
} else {
$this->createError("Invalid scope: $type");
}
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$date = $this->getParam("date");
$type = $this->getParam("type");
$sql = $this->user->getSQL();
$query = $sql->select($sql->count(), "day")
->from("Visitor")
->where(new Compare("count", 1, ">"))
->groupBy("day")
->orderBy("day")
->ascending();
$this->setConditions($type, $date, $query);
if (!$this->success) {
return false;
}
$res = $query->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["type"] = $type;
$this->result["visitors"] = array();
foreach($res as $row) {
$day = DateTime::createFromFormat("Ymd", $row["day"])->format("Y/m/d");
$count = $row["count"];
$this->result["visitors"][$day] = $count;
}
}
return $this->success;
}
}
}

@ -1,3 +1 @@
Mail\.class\.php
JWT\.class\.php
Database\.class\.php Database\.class\.php

@ -2,53 +2,36 @@
namespace Configuration; namespace Configuration;
use Objects\ConnectionData;
class Configuration { class Configuration {
private $database; private ?ConnectionData $database;
private $mail; private Settings $settings;
private $jwt;
function __construct() { function __construct() {
} $this->database = null;
$this->settings = Settings::loadDefaults();
public function load() { $class = \Configuration\Database::class;
try { $path = getClassPath($class, true);
if(file_exists($path) && is_readable($path)) {
$classes = array(
\Configuration\Database::class => &$this->database,
\Configuration\Mail::class => &$this->mail,
\Configuration\JWT::class => &$this->jwt
);
$success = true;
foreach($classes as $class => &$ref) {
$path = getClassPath($class);
if(!file_exists($path)) {
$success = false;
} else {
include_once $path; include_once $path;
if(class_exists($class)) { if(class_exists($class)) {
$ref = new $class(); $this->database = new \Configuration\Database();
} }
} }
} }
return $success; public function getDatabase() : ?ConnectionData {
} catch(\Error $e) { return $this->database;
die($e);
}
} }
public function getDatabase() { return $this->database; } public function getSettings() : Settings {
public function getJWT() { return $this->jwt; } return $this->settings;
public function getMail() { return $this->mail; }
public function isFilePresent($className) {
$path = getClassPath("\\Configuration\\$className");
return file_exists($path);
} }
public function create($className, $data) { public function create(string $className, $data) {
$path = getClassPath("\\Configuration\\$className"); $path = getClassPath("\\Configuration\\$className");
if($data) { if($data) {
@ -59,22 +42,15 @@ class Configuration {
namespace Configuration; namespace Configuration;
class $className { class $className extends KeyData {
private \$key;
public function __construct() { public function __construct() {
\$this->key = '$key'; parent::__construct('$key');
} }
public function getKey() { }", false
return \$this->key;
}
}
?>", false
); );
} else { } else if($data instanceof ConnectionData) {
$superClass = get_class($data); $superClass = get_class($data);
$host = addslashes($data->getHost()); $host = addslashes($data->getHost());
$port = intval($data->getPort()); $port = intval($data->getPort());
@ -98,22 +74,19 @@ class Configuration {
public function __construct() { public function __construct() {
parent::__construct('$host', $port, '$login', '$password');$properties parent::__construct('$host', $port, '$login', '$password');$properties
} }
} }", false
?>", false
); );
} else {
return false;
} }
} else { } else {
$code = intendCode( $code = "<?php";
"<?php
?>", false);
} }
return @file_put_contents($path, $code); return @file_put_contents($path, $code);
} }
public function delete($className) { public function delete(string $className) {
$path = getClassPath("\\Configuration\\$className"); $path = getClassPath("\\Configuration\\$className");
if(file_exists($path)) { if(file_exists($path)) {
return unlink($path); return unlink($path);
@ -121,6 +94,4 @@ class Configuration {
return true; return true;
} }
}; }
?>

@ -2,16 +2,16 @@
namespace Configuration; namespace Configuration;
use \Driver\SQL\Query\CreateTable; use Driver\SQL\SQL;
use \Driver\SQL\Query\Insert;
use \Driver\SQL\Column\Column;
use \Driver\SQL\Strategy\UpdateStrategy;
use \Driver\SQL\Strategy\SetNullStrategy; use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\CascadeStrategy; use \Driver\SQL\Strategy\CascadeStrategy;
class CreateDatabase { class CreateDatabase {
public static function createQueries($sql) { // NOTE:
// explicit serial ids removed due to postgres' serial implementation
public static function createQueries(SQL $sql) {
$queries = array(); $queries = array();
// Language // Language
@ -23,17 +23,18 @@ class CreateDatabase {
->unique("code") ->unique("code")
->unique("name"); ->unique("name");
$queries[] = $sql->insert("Language", array("uid", "code", "name")) $queries[] = $sql->insert("Language", array("code", "name"))
->addRow(1, "en_US", 'American English') ->addRow( "en_US", 'American English')
->addRow(2, "de_DE", 'Deutsch Standard'); ->addRow( "de_DE", 'Deutsch Standard');
$queries[] = $sql->createTable("User") $queries[] = $sql->createTable("User")
->addSerial("uid") ->addSerial("uid")
->addString("email", 64, true) ->addString("email", 64, true)
->addString("name", 32) ->addString("name", 32)
->addString("salt", 16) ->addString("password", 128)
->addString("password", 64) ->addBool("confirmed", false)
->addInt("language_id", true, 1) ->addInt("language_id", true, 1)
->addDateTime("registered_at", false, $sql->currentTimestamp())
->primaryKey("uid") ->primaryKey("uid")
->unique("email") ->unique("email")
->unique("name") ->unique("name")
@ -49,35 +50,39 @@ class CreateDatabase {
->addString("browser", 64) ->addString("browser", 64)
->addJson("data", false, '{}') ->addJson("data", false, '{}')
->addBool("stay_logged_in", true) ->addBool("stay_logged_in", true)
->addString("csrf_token", 16 )
->primaryKey("uid", "user_id") ->primaryKey("uid", "user_id")
->foreignKey("user_id", "User", "uid", new CascadeStrategy()); ->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("UserToken") $queries[] = $sql->createTable("UserToken")
->addInt("user_id") ->addInt("user_id")
->addString("token", 36) ->addString("token", 36)
->addEnum("token_type", array("password_reset", "confirmation")) ->addEnum("token_type", array("password_reset", "email_confirm", "invite"))
->addDateTime("valid_until") ->addDateTime("valid_until")
->addBool("used", false)
->foreignKey("user_id", "User", "uid", new CascadeStrategy()); ->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("Group") $queries[] = $sql->createTable("Group")
->addSerial("uid") ->addSerial("uid")
->addString("name", 32) ->addString("name", 32)
->addString("color", 10)
->primaryKey("uid") ->primaryKey("uid")
->unique("name"); ->unique("name");
$queries[] = $sql->insert("Group", array("uid", "name")) $queries[] = $sql->insert("Group", array("name", "color"))
->addRow(USER_GROUP_DEFAULT, "Default") ->addRow(USER_GROUP_MODERATOR_NAME, "#007bff")
->addRow(USER_GROUP_ADMIN, "Administrator"); ->addRow(USER_GROUP_SUPPORT_NAME, "#28a745")
->addRow(USER_GROUP_ADMIN_NAME, "#dc3545");
$queries[] = $sql->createTable("UserGroup") $queries[] = $sql->createTable("UserGroup")
->addInt("user_id") ->addInt("user_id")
->addInt("group_id") ->addInt("group_id")
->unique("user_id", "group_id") ->unique("user_id", "group_id")
->foreignKey("user_id", "User", "uid") ->foreignKey("user_id", "User", "uid", new CascadeStrategy())
->foreignKey("group_id", "Group", "uid"); ->foreignKey("group_id", "Group", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("Notification") $queries[] = $sql->createTable("Notification")
->addSerial("uid") ->addSerial("uid")
->addEnum("type", array("default","message","warning"), false, "default")
->addDateTime("created_at", false, $sql->currentTimestamp()) ->addDateTime("created_at", false, $sql->currentTimestamp())
->addString("title", 32) ->addString("title", 32)
->addString("message", 256) ->addString("message", 256)
@ -86,7 +91,7 @@ class CreateDatabase {
$queries[] = $sql->createTable("UserNotification") $queries[] = $sql->createTable("UserNotification")
->addInt("user_id") ->addInt("user_id")
->addInt("notification_id") ->addInt("notification_id")
->addBool("seen") ->addBool("seen", false)
->foreignKey("user_id", "User", "uid") ->foreignKey("user_id", "User", "uid")
->foreignKey("notification_id", "Notification", "uid") ->foreignKey("notification_id", "Notification", "uid")
->unique("user_id", "notification_id"); ->unique("user_id", "notification_id");
@ -94,7 +99,7 @@ class CreateDatabase {
$queries[] = $sql->createTable("GroupNotification") $queries[] = $sql->createTable("GroupNotification")
->addInt("group_id") ->addInt("group_id")
->addInt("notification_id") ->addInt("notification_id")
->addBool("seen") ->addBool("seen", false)
->foreignKey("group_id", "Group", "uid") ->foreignKey("group_id", "Group", "uid")
->foreignKey("notification_id", "Notification", "uid") ->foreignKey("notification_id", "Notification", "uid")
->unique("group_id", "notification_id"); ->unique("group_id", "notification_id");
@ -108,8 +113,116 @@ class CreateDatabase {
->primaryKey("uid") ->primaryKey("uid")
->foreignKey("user_id", "User", "uid"); ->foreignKey("user_id", "User", "uid");
$queries[] = $sql->createTable("Visitor")
->addInt("day")
->addInt("count", false, 1)
->addString("cookie", 26)
->unique("day", "cookie");
$queries[] = $sql->createTable("Route")
->addSerial("uid")
->addString("request", 128)
->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic"))
->addString("target", 128)
->addString("extra", 64, true)
->addBool("active", true)
->primaryKey("uid");
$queries[] = $sql->insert("Route", array("request", "action", "target", "extra"))
->addRow("^/admin(/.*)?$", "dynamic", "\\Documents\\Admin", NULL)
->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
->addRow("^/resetPassword(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword")
->addRow("^/$", "static", "/static/welcome.html", NULL);
$queries[] = $sql->createTable("Settings")
->addString("name", 32)
->addString("value", 1024, true)
->addBool("private", false) // these values are not returned from '/api/settings/get', but can be changed
->addBool("readonly", false) // these values are neither returned, nor can be changed from outside
->primaryKey("name");
$settingsQuery = $sql->insert("Settings", array("name", "value", "private", "readonly"))
// ->addRow("mail_enabled", "0") # this key will be set during installation
->addRow("mail_host", "", false, false)
->addRow("mail_port", "", false, false)
->addRow("mail_username", "", false, false)
->addRow("mail_password", "", true, false)
->addRow("mail_from", "", false, false)
->addRow("message_confirm_email", self::MessageConfirmEmail(), false, false)
->addRow("message_accept_invite", self::MessageAcceptInvite(), false, false)
->addRow("message_reset_password", self::MessageResetPassword(), false, false);
(Settings::loadDefaults())->addRows($settingsQuery);
$queries[] = $settingsQuery;
$queries[] = $sql->createTable("ContactRequest")
->addSerial("uid")
->addString("from_name", 32)
->addString("from_email", 64)
->addString("message", 512)
->addDateTime("created_at", false, $sql->currentTimestamp())
->primaryKey("uid");
$queries[] = $sql->createTable("ApiPermission")
->addString("method", 32)
->addJson("groups", true, '[]')
->addString("description", 128, false, "")
->primaryKey("method");
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves")
->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys")
->addRow("ApiKey/refresh", array(), "Allows users to refresh their API-Keys")
->addRow("ApiKey/revoke", array(), "Allows users to revoke their API-Keys")
->addRow("Groups/fetch", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to list all available groups")
->addRow("Groups/create", array(USER_GROUP_ADMIN), "Allows users to create a new groups")
->addRow("Groups/delete", array(USER_GROUP_ADMIN), "Allows users to delete a group")
->addRow("Routes/fetch", array(USER_GROUP_ADMIN), "Allows users to list all configured routes")
->addRow("Routes/save", array(USER_GROUP_ADMIN), "Allows users to create, delete and modify routes")
->addRow("Mail/test", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to send a test email to a given address")
->addRow("Settings/get", array(USER_GROUP_ADMIN), "Allows users to fetch server settings")
->addRow("Settings/set", array(USER_GROUP_ADMIN), "Allows users create, delete or modify server settings")
->addRow("Stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch server stats")
->addRow("User/create", array(USER_GROUP_ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
->addRow("User/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to list all registered users")
->addRow("User/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to get information about a single user")
->addRow("User/invite", array(USER_GROUP_ADMIN), "Allows users to create a new user and send them an invitation link")
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user")
->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions")
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics");
return $queries; return $queries;
} }
}
?> private static function MessageConfirmEmail() : string {
return "Hello {{username}},<br>" .
"You recently created an account on {{site_name}}. Please click on the following link to " .
"confirm your email address and complete your registration. If you haven't registered an " .
"account, you can simply ignore this email. The link is valid for the next 48 hours:<br><br> " .
"<a href=\"{{link}}\">{{link}}</a><br><br> " .
"Best Regards<br> " .
"{{site_name}} Administration";
}
private static function MessageAcceptInvite() : string {
return "Hello {{username}},<br>" .
"You were invited to create an account on {{site_name}}. Please click on the following link to " .
"confirm your email address and complete your registration by choosing a new password. " .
"If you want to decline the invitation, you can simply ignore this email. The link is valid for the next 7 days:<br><br>" .
"<a href=\"{{link}}\">{{link}}</a><br><br>" .
"Best Regards<br>" .
"{{site_name}} Administration";
}
private static function MessageResetPassword() : string {
return "Hello {{username}},<br>" .
"you requested a password reset on {{site_name}}. Please click on the following link to " .
"choose a new password. If this request was not intended, you can simply ignore the email. The Link is valid for one hour:<br><br>" .
"<a href=\"{{link}}\">{{link}}</a><br><br>" .
"Best Regards<br>" .
"{{site_name}} Administration";
}
}

@ -0,0 +1,107 @@
<?php
/**
* Do not change settings here, they are dynamically loaded from database.
*/
namespace Configuration;
use Driver\SQL\Query\Insert;
use Objects\User;
class Settings {
private string $siteName;
private string $baseUrl;
private string $jwtSecret;
private bool $installationComplete;
private bool $registrationAllowed;
private bool $recaptchaEnabled;
private string $recaptchaPublicKey;
private string $recaptchaPrivateKey;
public function getJwtSecret(): string {
return $this->jwtSecret;
}
public function isInstalled() {
return $this->installationComplete;
}
public static function loadDefaults() : Settings {
$hostname = $_SERVER["SERVER_NAME"];
$protocol = getProtocol();
$jwt = generateRandomString(32);
$settings = new Settings();
$settings->siteName = "WebBase";
$settings->baseUrl = "$protocol://$hostname";
$settings->jwtSecret = $jwt;
$settings->installationComplete = false;
$settings->registrationAllowed = false;
$settings->recaptchaPublicKey = "";
$settings->recaptchaPrivateKey = "";
$settings->recaptchaEnabled = false;
return $settings;
}
public function loadFromDatabase(User $user) {
$req = new \Api\Settings\Get($user);
$success = $req->execute();
if ($success) {
$result = $req->getResult()["settings"];
$this->siteName = $result["site_name"] ?? $this->siteName;
$this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed;
$this->installationComplete = $result["installation_completed"] ?? $this->installationComplete;
$this->jwtSecret = $result["jwt_secret"] ?? $this->jwtSecret;
$this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled;
$this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey;
$this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey;
if (!isset($result["jwt_secret"])) {
$req = new \Api\Settings\Set($user);
$req->execute(array("settings" => array(
"jwt_secret" => $this->jwtSecret
)));
}
}
return false;
}
public function addRows(Insert $query) {
$query->addRow("site_name", $this->siteName, false, false)
->addRow("base_url", $this->baseUrl, false, false)
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false)
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true)
->addRow("jwt_secret", $this->jwtSecret, true, true)
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false);
}
public function getSiteName() : string {
return $this->siteName;
}
public function getBaseUrl() : string {
return $this->baseUrl;
}
public function isRecaptchaEnabled() : bool {
return $this->recaptchaEnabled;
}
public function getRecaptchaSiteKey() : string {
return $this->recaptchaPublicKey;
}
public function getRecaptchaSecretKey() : string {
return $this->recaptchaPrivateKey;
}
public function isRegistrationAllowed() : bool {
return $this->registrationAllowed;
}
}

@ -0,0 +1,72 @@
<?php
namespace Documents {
use Documents\Account\AccountBody;
use Documents\Account\AccountHead;
use Elements\Document;
use Objects\User;
class Account extends Document {
public function __construct(User $user, ?string $view) {
parent::__construct($user, AccountHead::class, AccountBody::class, $view);
}
}
}
namespace Documents\Account {
use Elements\Head;
use Elements\Script;
use Elements\SimpleBody;
class AccountHead extends Head {
public function __construct($document) {
parent::__construct($document);
}
protected function initSources() {
$this->loadJQuery();
$this->addJS(Script::CORE);
$this->addJS(Script::ACCOUNT);
$this->loadBootstrap();
$this->loadFontawesome();
}
protected function initMetas() {
return array(
array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'),
array('name' => 'format-detection', 'content' => 'telephone=yes'),
array('charset' => 'utf-8'),
array("http-equiv" => 'expires', 'content' => '0'),
array("name" => 'robots', 'content' => 'noarchive'),
);
}
protected function initRawFields() {
return array();
}
protected function initTitle() {
return "Account";
}
}
class AccountBody extends SimpleBody {
public function __construct($document) {
parent::__construct($document);
}
protected function getContent() {
$view = $this->getDocument()->getView();
if ($view === null) {
return "The page you does not exist or is no longer valid. <a href='/'>Return to start page</a>";
}
return $view->getCode();
}
}
}

@ -1,29 +1,35 @@
<?php <?php
namespace Documents { namespace Documents {
class Admin extends \Elements\Document {
public function __construct($user) { use Documents\Admin\AdminHead;
parent::__construct($user, Admin\Head::class, Admin\Body::class); use Elements\Document;
use Objects\User;
use Views\Admin\AdminDashboardBody;
use Views\Admin\LoginBody;
class Admin extends Document {
public function __construct(User $user, ?string $view = NULL) {
$body = $user->isLoggedIn() ? AdminDashboardBody::class : LoginBody::class;
parent::__construct($user, AdminHead::class, $body, $view);
} }
} }
} }
namespace Documents\Admin { namespace Documents\Admin {
class Head extends \Elements\Head { use Elements\Head;
use Elements\Link;
use Elements\Script;
class AdminHead extends Head {
public function __construct($document) { public function __construct($document) {
parent::__construct($document); parent::__construct($document);
} }
protected function initSources() { protected function initSources() {
$this->loadJQuery();
$this->loadBootstrap();
$this->loadFontawesome(); $this->loadFontawesome();
$this->addJS(\Elements\Script::CORE);
$this->addCSS(\Elements\Link::CORE);
$this->addJS(\Elements\Script::ADMIN);
$this->addCSS(\Elements\Link::ADMIN);
} }
protected function initMetas() { protected function initMetas() {
@ -44,26 +50,4 @@ namespace Documents\Admin {
return "WebBase - Administration"; return "WebBase - Administration";
} }
} }
class Body extends \Elements\Body {
public function __construct($document) {
parent::__construct($document);
}
public function getCode() {
$html = parent::getCode();
$document = $this->getDocument();
if(!$document->getUser()->isLoggedIn()) {
$html .= new \Views\Login($document);
} else {
$html .= new \Views\Admin($document);
}
return $html;
}
}
} }
?>

@ -1,27 +1,32 @@
<?php <?php
namespace Documents { namespace Documents {
class Document404 extends \Elements\Document {
public function __construct($user) { use Documents\Document404\Body404;
parent::__construct($user, Document404\Head404::class, Document404\Body404::class); use Documents\Document404\Head404;
use Elements\Document;
class Document404 extends Document {
public function __construct($user, ?string $view = NULL) {
parent::__construct($user, Head404::class, Body404::class, $view);
} }
} }
} }
namespace Documents\Document404 { namespace Documents\Document404 {
class Head404 extends \Elements\Head { use Elements\Body;
use Elements\Head;
use Elements\SimpleBody;
use Views\View404;
class Head404 extends Head {
public function __construct($document) { public function __construct($document) {
parent::__construct($document); parent::__construct($document);
} }
protected function initSources() { protected function initSources() {
// $this->loadJQuery();
// $this->loadBootstrap();
// $this->loadFontawesome();
// $this->addJS(\Elements\Script::CORE);
// $this->addCSS(\Elements\Link::CORE);
} }
protected function initMetas() { protected function initMetas() {
@ -43,18 +48,18 @@ namespace Documents\Document404 {
} }
} }
class Body404 extends \Elements\Body { class Body404 extends SimpleBody {
public function __construct($document) { public function __construct($document) {
parent::__construct($document); parent::__construct($document);
} }
public function getCode() { public function loadView() {
$html = parent::getCode(); http_response_code(404);
$html .= "<b>404 Not Found</b>"; }
return $html;
protected function getContent() {
return $this->load(View404::class);
} }
} }
} }
?>

@ -1,9 +1,14 @@
<?php <?php
namespace Documents { namespace Documents {
class Install extends \Elements\Document {
use Documents\Install\InstallBody;
use Documents\Install\InstallHead;
use Elements\Document;
class Install extends Document {
public function __construct($user) { public function __construct($user) {
parent::__construct($user, Install\Head::class, Install\Body::class); parent::__construct($user, InstallHead::class, InstallBody::class);
$this->databaseRequired = false; $this->databaseRequired = false;
} }
} }
@ -11,7 +16,17 @@ namespace Documents {
namespace Documents\Install { namespace Documents\Install {
class Head extends \Elements\Head { use Configuration\CreateDatabase;
use Driver\SQL\SQL;
use Elements\Body;
use Elements\Head;
use Elements\Link;
use Elements\Script;
use External\PHPMailer\Exception;
use External\PHPMailer\PHPMailer;
use Objects\ConnectionData;
class InstallHead extends Head {
public function __construct($document) { public function __construct($document) {
parent::__construct($document); parent::__construct($document);
@ -21,9 +36,9 @@ namespace Documents\Install {
$this->loadJQuery(); $this->loadJQuery();
$this->loadBootstrap(); $this->loadBootstrap();
$this->loadFontawesome(); $this->loadFontawesome();
$this->addJS(\Elements\Script::CORE); $this->addJS(Script::CORE);
$this->addCSS(\Elements\Link::CORE); $this->addCSS(Link::CORE);
$this->addJS(\Elements\Script::INSTALL); $this->addJS(Script::INSTALL);
} }
protected function initMetas() { protected function initMetas() {
@ -46,27 +61,31 @@ namespace Documents\Install {
} }
class Body extends \Elements\Body { class InstallBody extends Body {
// Status enum // Status enum
const NOT_STARTED = 0; const NOT_STARTED = 0;
const PENDING = 1; const PENDING = 1;
const SUCCESFULL = 2; const SUCCESSFUL = 2;
const ERROR = 3; const ERROR = 3;
// Step enum // Step enum
const CHECKING_REQUIRMENTS = 1; const CHECKING_REQUIREMENTS = 1;
const DATABASE_CONFIGURATION = 2; const DATABASE_CONFIGURATION = 2;
const CREATE_USER = 3; const CREATE_USER = 3;
const ADD_MAIL_SERVICE = 4; const ADD_MAIL_SERVICE = 4;
const FINISH_INSTALLATION = 5; const FINISH_INSTALLATION = 5;
// //
private $errorString; private string $errorString;
private int $currentStep;
private array $steps;
function __construct($document) { function __construct($document) {
parent::__construct($document); parent::__construct($document);
$this->errorString = ""; $this->errorString = "";
$this->currentStep = InstallBody::CHECKING_REQUIREMENTS;
$this->steps = array();
} }
private function getParameter($name) { private function getParameter($name) {
@ -80,7 +99,7 @@ namespace Documents\Install {
private function getCurrentStep() { private function getCurrentStep() {
if(!$this->checkRequirements()["success"]) { if(!$this->checkRequirements()["success"]) {
return self::CHECKING_REQUIRMENTS; return self::CHECKING_REQUIREMENTS;
} }
$user = $this->getDocument()->getUser(); $user = $this->getDocument()->getUser();
@ -92,6 +111,10 @@ namespace Documents\Install {
} }
$sql = $user->getSQL(); $sql = $user->getSQL();
if(!$sql || !$sql->isConnected()) {
return self::DATABASE_CONFIGURATION;
}
$countKeyword = $sql->count(); $countKeyword = $sql->count();
$res = $sql->select($countKeyword)->from("User")->execute(); $res = $sql->select($countKeyword)->from("User")->execute();
if ($res === FALSE) { if ($res === FALSE) {
@ -104,18 +127,27 @@ namespace Documents\Install {
} }
} }
if($step === self::ADD_MAIL_SERVICE && $config->isFilePresent("Mail")) { if ($step === self::ADD_MAIL_SERVICE) {
$req = new \Api\Settings\Get($user);
$success = $req->execute(array("key" => "^mail_enabled$"));
if (!$success) {
$this->errorString = $req->getLastError();
return self::DATABASE_CONFIGURATION;
} else if (isset($req->getResult()["settings"]["mail_enabled"])) {
$step = self::FINISH_INSTALLATION; $step = self::FINISH_INSTALLATION;
if(!$config->isFilePresent("JWT") && !$config->create("JWT", generateRandomString(32))) {
$this->errorString = "Unable to create jwt file"; $req = new \Api\Settings\Set($user);
$success = $req->execute(array("settings" => array("installation_completed" => "1")));
if (!$success) {
$this->errorString = $req->getLastError();
} else { } else {
$req = new \Api\Notifications\Create($user); $req = new \Api\Notifications\Create($user);
$success = $req->execute(array( $req->execute(array(
"title" => "Welcome", "title" => "Welcome",
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!", "message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
"groupId" => USER_GROUP_ADMIN) "groupId" => USER_GROUP_ADMIN
)
); );
if (!$success) {
$this->errorString = $req->getLastError(); $this->errorString = $req->getLastError();
} }
} }
@ -145,8 +177,8 @@ namespace Documents\Install {
} }
} }
if(version_compare(PHP_VERSION, '7.1', '<')) { if(version_compare(PHP_VERSION, '7.4', '<')) {
$failedRequirements[] = "PHP Version <b>>= 7.1</b> is required. Got: <b>" . PHP_VERSION . "</b>"; $failedRequirements[] = "PHP Version <b>>= 7.4</b> is required. Got: <b>" . PHP_VERSION . "</b>";
$success = false; $success = false;
} }
@ -213,16 +245,16 @@ namespace Documents\Install {
$msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes); $msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes);
$success = false; $success = false;
} else { } else {
$connectionData = new \Objects\ConnectionData($host, $port, $username, $password); $connectionData = new ConnectionData($host, $port, $username, $password);
$connectionData->setProperty('database', $database); $connectionData->setProperty('database', $database);
$connectionData->setProperty('encoding', $encoding); $connectionData->setProperty('encoding', $encoding);
$connectionData->setProperty('type', $type); $connectionData->setProperty('type', $type);
$sql = \Driver\SQL\SQL::createConnection($connectionData); $sql = SQL::createConnection($connectionData);
$success = false; $success = false;
if(!($sql instanceof \Driver\SQL\SQL)) { if(is_string($sql)) {
$msg = "Error connecting to database: " . str($sql); $msg = "Error connecting to database: $sql";
} else if(!$sql->isConnected()) { } else if(!$sql->isConnected()) {
if (!$sql->checkRequirements()["success"]) { if (!$sql->checkRequirements()) {
$driverName = $sql->getDriverName(); $driverName = $sql->getDriverName();
$installLink = "https://www.php.net/manual/en/$driverName.setup.php"; $installLink = "https://www.php.net/manual/en/$driverName.setup.php";
$link = $this->createExternalLink($installLink); $link = $this->createExternalLink($installLink);
@ -234,7 +266,7 @@ namespace Documents\Install {
$msg = ""; $msg = "";
$success = true; $success = true;
$queries = \Configuration\CreateDatabase::createQueries($sql); $queries = CreateDatabase::createQueries($sql);
foreach($queries as $query) { foreach($queries as $query) {
if (!($res = $query->execute())) { if (!($res = $query->execute())) {
$msg = "Error creating tables: " . $sql->getLastError(); $msg = "Error creating tables: " . $sql->getLastError();
@ -243,7 +275,8 @@ namespace Documents\Install {
} }
} }
if($success && !$this->getDocument()->getUser()->getConfiguration()->create("Database", $connectionData)) { $config = $this->getDocument()->getUser()->getConfiguration();
if(!$config->create("Database", $connectionData)) {
$success = false; $success = false;
$msg = "Unable to write file"; $msg = "Unable to write file";
} }
@ -269,8 +302,8 @@ namespace Documents\Install {
$username = $this->getParameter("username"); $username = $this->getParameter("username");
$password = $this->getParameter("password"); $password = $this->getParameter("password");
$confirmPassword = $this->getParameter("confirmPassword"); $confirmPassword = $this->getParameter("confirmPassword");
$email = $this->getParameter("email") ?? "";
$msg = $this->errorString;
$success = true; $success = true;
$missingInputs = array(); $missingInputs = array();
@ -292,30 +325,24 @@ namespace Documents\Install {
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(strlen($username) < 5 || strlen($username) > 32) {
$msg = "The username should be between 5 and 32 characters long";
$success = false;
} else if(strcmp($password, $confirmPassword) !== 0) {
$msg = "The given passwords do not match";
$success = false;
} else if(strlen($password) < 6) {
$msg = "The password should be at least 6 characters long";
$success = false;
} else { } else {
$salt = generateRandomString(16);
$hash = hash('sha256', $password . $salt);
$sql = $user->getSQL(); $sql = $user->getSQL();
$req = new \Api\User\Create($user);
$success = $req->execute(array(
'username' => $username,
'email' => $email,
'password' => $password,
'confirmPassword' => $confirmPassword,
));
$success = $sql->insert("User", array("name", "salt", "password")) $msg = $req->getLastError();
->addRow($username, $salt, $hash) if ($success) {
->returning("uid") $success = $sql->insert("UserGroup", array("group_id", "user_id"))
->execute() ->addRow(USER_GROUP_ADMIN, $req->getResult()["userId"])
&& $sql->insert("UserGroup", array("group_id", "user_id"))
->addRow(USER_GROUP_ADMIN, $sql->getLastInsertId())
->execute(); ->execute();
$msg = $sql->getLastError(); $msg = $sql->getLastError();
} }
}
return array("msg" => $msg, "success" => $success); return array("msg" => $msg, "success" => $success);
} }
@ -333,10 +360,9 @@ namespace Documents\Install {
$success = true; $success = true;
$msg = $this->errorString; $msg = $this->errorString;
if($this->getParameter("skip") === "true") { if($this->getParameter("skip") === "true") {
if(!$user->getConfiguration()->create("Mail", null)) { $req = new \Api\Settings\Set($user);
$success = false; $success = $req->execute(array("settings" => array( "mail_enabled" => "0" )));
$msg = "Unable to create file"; $msg = $req->getLastError();
}
} else { } else {
$address = $this->getParameter("address"); $address = $this->getParameter("address");
@ -375,7 +401,7 @@ namespace Documents\Install {
} else { } else {
$success = false; $success = false;
$mail = new \External\PHPMailer\PHPMailer(true); $mail = new PHPMailer(true);
$mail->IsSMTP(); $mail->IsSMTP();
$mail->SMTPAuth = true; $mail->SMTPAuth = true;
$mail->Username = $username; $mail->Username = $username;
@ -395,16 +421,20 @@ namespace Documents\Install {
$msg = ""; $msg = "";
$mail->smtpClose(); $mail->smtpClose();
} }
} catch(\External\PHPMailer\Exception $error) { } catch(Exception $error) {
$msg = "Could not connect to SMTP Server: " . $error->errorMessage(); $msg = "Could not connect to SMTP Server: " . $error->errorMessage();
} }
if($success) { if($success) {
$connectionData = new \Objects\ConnectionData($address, $port, $username, $password); $req = new \Api\Settings\Set($user);
if(!$user->getConfiguration()->create("Mail", $connectionData)) { $success = $req->execute(array("settings" => array(
$success = false; "mail_enabled" => "1",
$msg = "Unable to create file"; "mail_host" => "$address",
} "mail_port" => "$port",
"mail_username" => "$username",
"mail_password" => "$password",
)));
$msg = $req->getLastError();
} }
} }
} }
@ -416,7 +446,7 @@ namespace Documents\Install {
switch($this->currentStep) { switch($this->currentStep) {
case self::CHECKING_REQUIRMENTS: case self::CHECKING_REQUIREMENTS:
return $this->checkRequirements(); return $this->checkRequirements();
case self::DATABASE_CONFIGURATION: case self::DATABASE_CONFIGURATION:
@ -446,26 +476,26 @@ namespace Documents\Install {
switch($status) { switch($status) {
case self::PENDING: case self::PENDING:
$statusIcon = '<i class="fas fa-spin fa-spinner"></i>'; $statusIcon = $this->createIcon("spinner");
$statusText = "Loading…"; $statusText = "Loading…";
$statusColor = "muted"; $statusColor = "muted";
break; break;
case self::SUCCESFULL: case self::SUCCESSFUL:
$statusIcon = '<i class="fas fa-check-circle"></i>'; $statusIcon = $this->createIcon("check-circle");
$statusText = "Successfull"; $statusText = "Successful";
$statusColor = "success"; $statusColor = "success";
break; break;
case self::ERROR: case self::ERROR:
$statusIcon = '<i class="fas fa-times-circle"></i>'; $statusIcon = $this->createIcon("times-circle");
$statusText = "Failed"; $statusText = "Failed";
$statusColor = "danger"; $statusColor = "danger";
break; break;
case self::NOT_STARTED: case self::NOT_STARTED:
default: default:
$statusIcon = '<i class="far fa-circle"></i>'; $statusIcon = $this->createIcon("circle", "far");
$statusText = "Pending"; $statusText = "Pending";
$statusColor = "muted"; $statusColor = "muted";
break; break;
@ -552,7 +582,7 @@ namespace Documents\Install {
private function createProgessMainview() { private function createProgessMainview() {
$views = array( $views = array(
self::CHECKING_REQUIRMENTS => array( self::CHECKING_REQUIREMENTS => array(
"title" => "Application Requirements", "title" => "Application Requirements",
"progressText" => "Checking requirements, please wait a moment…" "progressText" => "Checking requirements, please wait a moment…"
), ),
@ -585,6 +615,7 @@ namespace Documents\Install {
"title" => "Create a User", "title" => "Create a User",
"form" => array( "form" => array(
array("title" => "Username", "name" => "username", "type" => "text", "required" => true), array("title" => "Username", "name" => "username", "type" => "text", "required" => true),
array("title" => "Email", "name" => "email", "type" => "text"),
array("title" => "Password", "name" => "password", "type" => "password", "required" => true), array("title" => "Password", "name" => "password", "type" => "password", "required" => true),
array("title" => "Confirm Password", "name" => "confirmPassword", "type" => "password", "required" => true), array("title" => "Confirm Password", "name" => "confirmPassword", "type" => "password", "required" => true),
), ),
@ -661,7 +692,7 @@ namespace Documents\Install {
); );
if($this->currentStep != self::FINISH_INSTALLATION) { if($this->currentStep != self::FINISH_INSTALLATION) {
if ($this->currentStep == self::CHECKING_REQUIRMENTS) { if ($this->currentStep == self::CHECKING_REQUIREMENTS) {
$buttons[] = array("title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right"); $buttons[] = array("title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right");
} else { } else {
$buttons[] = array("title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right"); $buttons[] = array("title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right");
@ -683,7 +714,7 @@ namespace Documents\Install {
$id = $button["id"]; $id = $button["id"];
$float = $button["float"]; $float = $button["float"];
$disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : ""; $disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : "";
$button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type margin-xs\"$disabled>$title</button>"; $button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type m-1\"$disabled>$title</button>";
if($float === "left") { if($float === "left") {
$buttonsLeft .= $button; $buttonsLeft .= $button;
@ -701,12 +732,11 @@ namespace Documents\Install {
return $html; return $html;
} }
function getCode() { function getCode() {
$html = parent::getCode(); $html = parent::getCode();
$this->steps = array( $this->steps = array(
self::CHECKING_REQUIRMENTS => array( self::CHECKING_REQUIREMENTS => array(
"title" => "Checking requirements", "title" => "Checking requirements",
"status" => self::ERROR "status" => self::ERROR
), ),
@ -731,12 +761,12 @@ namespace Documents\Install {
$this->currentStep = $this->getCurrentStep(); $this->currentStep = $this->getCurrentStep();
// set status // set status
for($step = self::CHECKING_REQUIRMENTS; $step < $this->currentStep; $step++) { for($step = self::CHECKING_REQUIREMENTS; $step < $this->currentStep; $step++) {
$this->steps[$step]["status"] = self::SUCCESFULL; $this->steps[$step]["status"] = self::SUCCESSFUL;
} }
if($this->currentStep == self::FINISH_INSTALLATION) { if($this->currentStep == self::FINISH_INSTALLATION) {
$this->steps[$this->currentStep]["status"] = self::SUCCESFULL; $this->steps[$this->currentStep]["status"] = self::SUCCESSFUL;
} }
// POST // POST
@ -748,6 +778,7 @@ namespace Documents\Install {
$progressSidebar = $this->createProgressSidebar(); $progressSidebar = $this->createProgressSidebar();
$progressMainview = $this->createProgessMainview(); $progressMainview = $this->createProgessMainview();
$errorStyle = ($this->errorString ? '' : ' style="display:none"'); $errorStyle = ($this->errorString ? '' : ' style="display:none"');
$errorClass = ($this->errorString ? ' alert-danger' : ''); $errorClass = ($this->errorString ? ' alert-danger' : '');
@ -773,7 +804,7 @@ namespace Documents\Install {
</div> </div>
<div class=\"col-md-8 order-md-1\"> <div class=\"col-md-8 order-md-1\">
$progressMainview $progressMainview
<div class=\"alert$errorClass margin-top-m\" id=\"status\"$errorStyle>$this->errorString</div> <div class=\"alert$errorClass mt-4\" id=\"status\"$errorStyle>$this->errorString</div>
</div> </div>
</div> </div>
</div> </div>
@ -781,9 +812,5 @@ namespace Documents\Install {
return $html; return $html;
} }
} }
} }
?>

@ -9,5 +9,3 @@ class BoolColumn extends Column {
} }
} }
?>

@ -4,8 +4,8 @@ namespace Driver\SQL\Column;
class Column { class Column {
private $name; private string $name;
private $nullable; private bool $nullable;
private $defaultValue; private $defaultValue;
public function __construct($name, $nullable = false, $defaultValue = NULL) { public function __construct($name, $nullable = false, $defaultValue = NULL) {
@ -19,5 +19,3 @@ class Column {
public function getDefaultValue() { return $this->defaultValue; } public function getDefaultValue() { return $this->defaultValue; }
} }
?>

@ -8,5 +8,3 @@ class DateTimeColumn extends Column {
parent::__construct($name, $nullable, $defaultValue); parent::__construct($name, $nullable, $defaultValue);
} }
} }
?>

@ -4,7 +4,7 @@ namespace Driver\SQL\Column;
class EnumColumn extends Column { class EnumColumn extends Column {
private $values; private array $values;
public function __construct($name, $values, $nullable=false, $defaultValue=NULL) { public function __construct($name, $values, $nullable=false, $defaultValue=NULL) {
parent::__construct($name, $nullable, $defaultValue); parent::__construct($name, $nullable, $defaultValue);
@ -13,5 +13,3 @@ class EnumColumn extends Column {
public function getValues() { return $this->values; } public function getValues() { return $this->values; }
} }
?>

@ -9,5 +9,3 @@ class IntColumn extends Column {
} }
} }
?>

@ -9,5 +9,3 @@ class JsonColumn extends Column {
} }
} }
?>

@ -9,5 +9,3 @@ class SerialColumn extends Column {
} }
} }
?>

@ -4,7 +4,7 @@ namespace Driver\SQL\Column;
class StringColumn extends Column { class StringColumn extends Column {
private $maxSize; private ?int $maxSize;
public function __construct($name, $maxSize=null, $nullable=false, $defaultValue=null) { public function __construct($name, $maxSize=null, $nullable=false, $defaultValue=null) {
parent::__construct($name, $nullable, $defaultValue); parent::__construct($name, $nullable, $defaultValue);
@ -13,5 +13,3 @@ class StringColumn extends Column {
public function getMaxSize() { return $this->maxSize; } public function getMaxSize() { return $this->maxSize; }
} }
?>

@ -4,6 +4,10 @@ namespace Driver\SQL\Condition;
class Compare extends Condition { class Compare extends Condition {
private string $operator;
private string $column;
private $value;
public function __construct($col, $val, $operator='=') { public function __construct($col, $val, $operator='=') {
$this->operator = $operator; $this->operator = $operator;
$this->column = $col; $this->column = $col;
@ -15,5 +19,3 @@ class Compare extends Condition {
public function getOperator() { return $this->operator; } public function getOperator() { return $this->operator; }
} }
?>

@ -4,7 +4,7 @@ namespace Driver\SQL\Condition;
class CondAnd extends Condition { class CondAnd extends Condition {
private $conditions; private array $conditions;
public function __construct(...$conditions) { public function __construct(...$conditions) {
$this->conditions = $conditions; $this->conditions = $conditions;
@ -12,5 +12,3 @@ class CondAnd extends Condition {
public function getConditions() { return $this->conditions; } public function getConditions() { return $this->conditions; }
} }
?>

@ -4,6 +4,8 @@ namespace Driver\SQL\Condition;
class CondBool extends Condition { class CondBool extends Condition {
private $value;
public function __construct($val) { public function __construct($val) {
$this->value = $val; $this->value = $val;
} }
@ -11,5 +13,3 @@ class CondBool extends Condition {
public function getValue() { return $this->value; } public function getValue() { return $this->value; }
} }
?>

@ -0,0 +1,17 @@
<?php
namespace Driver\SQL\Condition;
class CondIn extends Condition {
private string $column;
private $expression;
public function __construct(string $column, $expression) {
$this->column = $column;
$this->expression = $expression;
}
public function getColumn() { return $this->column; }
public function getExpression() { return $this->expression; }
}

@ -0,0 +1,20 @@
<?php
namespace Driver\SQL\Condition;
abstract class CondKeyword extends Condition {
private $leftExpression;
private $rightExpression;
private string $keyword;
public function __construct($keyword, $leftExpression, $rightExpression) {
$this->leftExpression = $leftExpression;
$this->rightExpression = $rightExpression;
$this->keyword = $keyword;
}
public function getLeftExp() { return $this->leftExpression; }
public function getRightExp() { return $this->rightExpression; }
public function getKeyword() { return $this->keyword; }
}

@ -0,0 +1,10 @@
<?php
namespace Driver\SQL\Condition;
class CondLike extends CondKeyword {
public function __construct($leftExpression, $rightExpression) {
parent::__construct("LIKE", $leftExpression, $rightExpression);
}
}

@ -0,0 +1,16 @@
<?php
namespace Driver\SQL\Condition;
class CondNot extends Condition {
private $expression; // string or condition
public function __construct($expression) {
$this->expression = $expression;
}
public function getExpression() {
return $this->expression;
}
}

@ -4,13 +4,11 @@ namespace Driver\SQL\Condition;
class CondOr extends Condition { class CondOr extends Condition {
private $conditions; private array $conditions;
public function __construct(...$conditions) { public function __construct(...$conditions) {
$this->conditions = $conditions; $this->conditions = (!empty($conditions) && is_array($conditions[0])) ? $conditions[0] : $conditions;
} }
public function getConditions() { return $this->conditions; } public function getConditions() { return $this->conditions; }
} }
?>

@ -0,0 +1,11 @@
<?php
namespace Driver\SQL\Condition;
class CondRegex extends CondKeyword {
public function __construct($leftExpression, $rightExpression) {
parent::__construct("REGEXP", $leftExpression, $rightExpression);
}
}

@ -5,5 +5,3 @@ namespace Driver\SQL\Condition;
abstract class Condition { abstract class Condition {
} }
?>

@ -4,13 +4,11 @@ namespace Driver\SQL\Constraint;
abstract class Constraint { abstract class Constraint {
private $columnName; private array $columnNames;
public function __construct($columnName) { public function __construct($columnNames) {
$this->columnName = $columnName; $this->columnNames = (!is_array($columnNames) ? array($columnNames) : $columnNames);
} }
public function getColumnName() { return $this->columnName; } public function getColumnNames() { return $this->columnNames; }
}; }
?>

@ -2,11 +2,13 @@
namespace Driver\SQL\Constraint; namespace Driver\SQL\Constraint;
use Driver\SQL\Strategy\Strategy;
class ForeignKey extends Constraint { class ForeignKey extends Constraint {
private $referencedTable; private string $referencedTable;
private $referencedColumn; private string $referencedColumn;
private $strategy; private ?Strategy $strategy;
public function __construct($name, $refTable, $refColumn, $strategy = NULL) { public function __construct($name, $refTable, $refColumn, $strategy = NULL) {
parent::__construct($name); parent::__construct($name);
@ -18,6 +20,4 @@ class ForeignKey extends Constraint {
public function getReferencedTable() { return $this->referencedTable; } public function getReferencedTable() { return $this->referencedTable; }
public function getReferencedColumn() { return $this->referencedColumn; } public function getReferencedColumn() { return $this->referencedColumn; }
public function onDelete() { return $this->strategy; } public function onDelete() { return $this->strategy; }
}; }
?>

@ -8,6 +8,4 @@ class PrimaryKey extends Constraint {
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names); parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
} }
}; }
?>

@ -8,6 +8,4 @@ class Unique extends Constraint {
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names); parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
} }
}; }
?>

@ -0,0 +1,13 @@
<?php
namespace Driver\SQL\Expression;
use Driver\SQL\Condition\Compare;
class Add extends Compare {
public function __construct($col, $val) {
parent::__construct($col, $val, "+");
}
}

@ -4,13 +4,13 @@ namespace Driver\SQL;
class Join { class Join {
private $type; private string $type;
private $table; private string $table;
private $columnA; private string $columnA;
private $columnB; private string $columnB;
public function __construct($type, $table, $columnA, $columnB) { public function __construct($type, $table, $columnA, $columnB) {
$this->tpye = $type; $this->type = $type;
$this->table = $table; $this->table = $table;
$this->columnA = $columnA; $this->columnA = $columnA;
$this->columnB = $columnB; $this->columnB = $columnB;
@ -22,5 +22,3 @@ class Join {
public function getColumnB() { return $this->columnB; } public function getColumnB() { return $this->columnB; }
} }
?>

@ -4,7 +4,7 @@ namespace Driver\SQL;
class Keyword { class Keyword {
private $value; private string $value;
public function __construct($value) { public function __construct($value) {
$this->value = $value; $this->value = $value;
@ -13,5 +13,3 @@ class Keyword {
public function getValue() { return $this->value; } public function getValue() { return $this->value; }
} }
?>

@ -13,9 +13,9 @@ use \Driver\SQL\Column\DateTimeColumn;
use Driver\SQL\Column\BoolColumn; use Driver\SQL\Column\BoolColumn;
use Driver\SQL\Column\JsonColumn; use Driver\SQL\Column\JsonColumn;
use \Driver\SQL\Strategy\CascadeStrategy; use Driver\SQL\Condition\CondRegex;
use \Driver\SQL\Strategy\SetDefaultStrategy; use Driver\SQL\Expression\Add;
use \Driver\SQL\Strategy\SetNullStrategy; use Driver\SQL\Strategy\Strategy;
use \Driver\SQL\Strategy\UpdateStrategy; use \Driver\SQL\Strategy\UpdateStrategy;
class MySQL extends SQL { class MySQL extends SQL {
@ -32,7 +32,7 @@ class MySQL extends SQL {
return 'mysqli'; return 'mysqli';
} }
// Connection Managment // Connection Management
public function connect() { public function connect() {
if(!is_null($this->connection)) { if(!is_null($this->connection)) {
@ -47,7 +47,7 @@ class MySQL extends SQL {
$this->connectionData->getPort() $this->connectionData->getPort()
); );
if (mysqli_connect_errno($this->connection)) { if (mysqli_connect_errno()) {
$this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error(); $this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error();
$this->connection = NULL; $this->connection = NULL;
return false; return false;
@ -63,6 +63,7 @@ class MySQL extends SQL {
} }
mysqli_close($this->connection); mysqli_close($this->connection);
return true;
} }
public function getLastError() { public function getLastError() {
@ -81,6 +82,8 @@ class MySQL extends SQL {
switch($paramType) { switch($paramType) {
case Parameter::TYPE_BOOLEAN: case Parameter::TYPE_BOOLEAN:
$value = $value ? 1 : 0; $value = $value ? 1 : 0;
$sqlParams[0] .= 'i';
break;
case Parameter::TYPE_INT: case Parameter::TYPE_INT:
$sqlParams[0] .= 'i'; $sqlParams[0] .= 'i';
break; break;
@ -99,6 +102,10 @@ class MySQL extends SQL {
$value = $value->format('Y-m-d H:i:s'); $value = $value->format('Y-m-d H:i:s');
$sqlParams[0] .= 's'; $sqlParams[0] .= 's';
break; break;
case Parameter::TYPE_ARRAY:
$value = json_encode($value);
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_EMAIL: case Parameter::TYPE_EMAIL:
default: default:
$sqlParams[0] .= 's'; $sqlParams[0] .= 's';
@ -161,64 +168,39 @@ class MySQL extends SQL {
return ($success && $returnValues) ? $resultRows : $success; return ($success && $returnValues) ? $resultRows : $success;
} }
public function executeInsert($insert) { protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) {
$tableName = $this->tableName($insert->getTableName()); if (is_null($strategy)) {
$columns = $insert->getColumns(); return "";
$rows = $insert->getRows(); } else if ($strategy instanceof UpdateStrategy) {
$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 = " (" . $this->columnName($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(); $updateValues = array();
foreach($onDuplicateKey->getValues() as $key => $value) { foreach($strategy->getValues() as $key => $value) {
$leftColumn = $this->columnName($key);
if ($value instanceof Column) { if ($value instanceof Column) {
$columnName = $value->getName(); $columnName = $this->columnName($value->getName());
$updateValues[] = "`$key`=`$columnName`"; $updateValues[] = "$leftColumn=VALUES($columnName)";
} else if($value instanceof Add) {
$columnName = $this->columnName($value->getColumn());
$operator = $value->getOperator();
$value = $value->getValue();
$updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params);
} else { } else {
$updateValues[] = "`$key`=" . $this->addValue($value, $parameters); $updateValues[] = "$leftColumn=" . $this->addValue($value, $params);
} }
} }
$onDuplicateKey = " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues); return " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues);
} else { } else {
$strategy = get_class($onDuplicateKey); $strategyClass = get_class($strategy);
$this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet."; $this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
return false; return false;
} }
} }
$query = "INSERT INTO $tableName$columns VALUES$values$onDuplicateKey"; protected function fetchReturning($res, string $returningCol) {
$success = $this->execute($query, $parameters);
if($success) {
$this->lastInsertId = mysqli_insert_id($this->connection); $this->lastInsertId = mysqli_insert_id($this->connection);
} }
return $success; public function getColumnDefinition(Column $column) {
}
public function getColumnDefinition($column) {
$columnName = $this->columnName($column->getName()); $columnName = $this->columnName($column->getName());
$defaultValue = $column->getDefaultValue(); $defaultValue = $column->getDefaultValue();
@ -255,7 +237,7 @@ class MySQL extends SQL {
$notNull = $column->notNull() ? " NOT NULL" : ""; $notNull = $column->notNull() ? " NOT NULL" : "";
if (!is_null($defaultValue) || !$column->notNull()) { if (!is_null($defaultValue) || !$column->notNull()) {
$defaultValue = " DEFAULT " . $this->getValueDefinition($column->getDefaultValue()); $defaultValue = " DEFAULT " . $this->getValueDefinition($defaultValue);
} else { } else {
$defaultValue = ""; $defaultValue = "";
} }
@ -323,4 +305,7 @@ class MySQL extends SQL {
return new Keyword("NOW()"); return new Keyword("NOW()");
} }
}; public function getStatus() {
return mysqli_stat($this->connection);
}
}

@ -4,7 +4,7 @@ namespace Driver\SQL;
use \Api\Parameter\Parameter; use \Api\Parameter\Parameter;
use \Driver\SQL\Column\Column; use Driver\SQL\Column\Column;
use \Driver\SQL\Column\IntColumn; use \Driver\SQL\Column\IntColumn;
use \Driver\SQL\Column\SerialColumn; use \Driver\SQL\Column\SerialColumn;
use \Driver\SQL\Column\StringColumn; use \Driver\SQL\Column\StringColumn;
@ -13,10 +13,10 @@ use \Driver\SQL\Column\DateTimeColumn;
use Driver\SQL\Column\BoolColumn; use Driver\SQL\Column\BoolColumn;
use Driver\SQL\Column\JsonColumn; use Driver\SQL\Column\JsonColumn;
use \Driver\SQL\Strategy\CascadeStrategy; use Driver\SQL\Condition\CondRegex;
use \Driver\SQL\Strategy\SetDefaultStrategy; use Driver\SQL\Expression\Add;
use \Driver\SQL\Strategy\SetNullStrategy; use Driver\SQL\Strategy\Strategy;
use \Driver\SQL\Strategy\UpdateStrategy; use Driver\SQL\Strategy\UpdateStrategy;
class PostgreSQL extends SQL { class PostgreSQL extends SQL {
@ -32,7 +32,7 @@ class PostgreSQL extends SQL {
return 'pgsql'; return 'pgsql';
} }
// Connection Managment // Connection Management
public function connect() { public function connect() {
if(!is_null($this->connection)) { if(!is_null($this->connection)) {
return true; return true;
@ -68,13 +68,13 @@ class PostgreSQL extends SQL {
if(is_null($this->connection)) if(is_null($this->connection))
return; return;
pg_close($this->connection); @pg_close($this->connection);
} }
public function getLastError() { public function getLastError() {
$lastError = parent::getLastError(); $lastError = parent::getLastError();
if (empty($lastError)) { if (empty($lastError)) {
$lastError = pg_last_error($this->connection) . " " . pg_last_error($this->connection); $lastError = trim(pg_last_error($this->connection) . " " . pg_last_error($this->connection));
} }
return $lastError; return $lastError;
@ -99,6 +99,9 @@ class PostgreSQL extends SQL {
case Parameter::TYPE_DATE_TIME: case Parameter::TYPE_DATE_TIME:
$value = $value->format("Y-m-d H:i:s"); $value = $value->format("Y-m-d H:i:s");
break; break;
case Parameter::TYPE_ARRAY:
$value = json_encode($value);
break;
default: default:
break; break;
} }
@ -131,76 +134,48 @@ class PostgreSQL extends SQL {
} }
} }
// Querybuilder protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) {
public function executeInsert($insert) { if (!is_null($strategy)) {
if ($strategy instanceof UpdateStrategy) {
$tableName = $this->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)) {
$columnStr = "";
} else {
$columnStr = " (" . $this->columnName($columns) . ")";
}
$numRows = count($rows);
$parameters = array();
$values = array();
foreach($rows as $row) {
$rowPlaceHolder = array();
foreach($row as $val) {
$rowPlaceHolder[] = $this->addValue($val, $parameters);
}
$values[] = "(" . implode(",", $rowPlaceHolder) . ")";
}
$values = implode(",", $values);
if ($onDuplicateKey) {
/*if ($onDuplicateKey instanceof UpdateStrategy) {
$updateValues = array(); $updateValues = array();
foreach($onDuplicateKey->getValues() as $key => $value) { foreach($strategy->getValues() as $key => $value) {
$leftColumn = $this->columnName($key);
if ($value instanceof Column) { if ($value instanceof Column) {
$columnName = $value->getName(); $columnName = $this->columnName($value->getName());
$updateValues[] = "\"$key\"=\"$columnName\""; $updateValues[] = "$leftColumn=EXCLUDED.$columnName";
} else if ($value instanceof Add) {
$columnName = $this->columnName($value->getColumn());
$operator = $value->getOperator();
$value = $value->getValue();
$updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params);
} else { } else {
$updateValues[] = "\"$key\"=" . $this->addValue($value, $parameters); $updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters);
} }
} }
$onDuplicateKey = " ON CONFLICT DO UPDATE SET " . implode(",", $updateValues); $conflictingColumns = $this->columnName($strategy->getConflictingColumns());
} else*/ { $updateValues = implode(",", $updateValues);
$strategy = get_class($onDuplicateKey); return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues";
$this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet."; } else {
$strategyClass = get_class($strategy);
$this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
return false; return false;
} }
} else {
return "";
}
} }
$returningCol = $insert->getReturning(); protected function getReturning(?string $columns) {
$returning = $returningCol ? (" RETURNING " . $this->columnName($returningCol)) : ""; return $columns ? (" RETURNING " . $this->columnName($columns)) : "";
}
$query = "INSERT INTO $tableName$columnStr VALUES$values$onDuplicateKey$returning"; protected function fetchReturning($res, string $returningCol) {
$res = $this->execute($query, $parameters, !empty($returning));
$success = ($res !== FALSE);
if($success && !empty($returning)) {
$this->lastInsertId = $res[0][$returningCol]; $this->lastInsertId = $res[0][$returningCol];
} }
return $success;
}
// UGLY but.. what should i do? // UGLY but.. what should i do?
private function createEnum($enumColumn) { private function createEnum(EnumColumn $enumColumn) {
$typeName = $enumColumn->getName(); $typeName = $enumColumn->getName();
if(!endsWith($typeName, "_type")) { if(!endsWith($typeName, "_type")) {
$typeName = "${typeName}_type"; $typeName = "${typeName}_type";
@ -319,5 +294,27 @@ class PostgreSQL extends SQL {
public function currentTimestamp() { public function currentTimestamp() {
return new Keyword("CURRENT_TIMESTAMP"); return new Keyword("CURRENT_TIMESTAMP");
} }
public function getStatus() {
$version = pg_version($this->connection)["client"] ?? "??";
$status = pg_connection_status($this->connection);
static $statusTexts = array(
PGSQL_CONNECTION_OK => "PGSQL_CONNECTION_OK",
PGSQL_CONNECTION_BAD => "PGSQL_CONNECTION_BAD",
);
return ($statusTexts[$status] ?? "Unknown") . " (v$version)";
}
protected function buildCondition($condition, &$params) {
if($condition instanceof CondRegex) {
$left = $condition->getLeftExp();
$right = $condition->getRightExp();
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
return $left . " ~ " . $right;
} else {
return parent::buildCondition($condition, $params);
}
}
} }
?>

@ -16,10 +16,10 @@ use Driver\SQL\Constraint\ForeignKey;
class CreateTable extends Query { class CreateTable extends Query {
private $tableName; private string $tableName;
private $columns; private array $columns;
private $constraints; private array $constraints;
private $ifNotExists; private bool $ifNotExists;
public function __construct($sql, $name) { public function __construct($sql, $name) {
parent::__construct($sql); parent::__construct($sql);
@ -92,6 +92,4 @@ class CreateTable extends Query {
public function getTableName() { return $this->tableName; } public function getTableName() { return $this->tableName; }
public function getColumns() { return $this->columns; } public function getColumns() { return $this->columns; }
public function getConstraints() { return $this->constraints; } public function getConstraints() { return $this->constraints; }
}; }
?>

@ -2,10 +2,12 @@
namespace Driver\SQL\Query; namespace Driver\SQL\Query;
use Driver\SQL\Condition\CondOr;
class Delete extends Query { class Delete extends Query {
private $table; private string $table;
private $conditions; private array $conditions;
public function __construct($sql, $table) { public function __construct($sql, $table) {
parent::__construct($sql); parent::__construct($sql);
@ -14,7 +16,7 @@ class Delete extends Query {
} }
public function where(...$conditions) { public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions); $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
return $this; return $this;
} }
@ -24,6 +26,4 @@ class Delete extends Query {
public function getTable() { return $this->table; } public function getTable() { return $this->table; }
public function getConditions() { return $this->conditions; } public function getConditions() { return $this->conditions; }
}; }
?>

@ -2,13 +2,15 @@
namespace Driver\SQL\Query; namespace Driver\SQL\Query;
use Driver\SQL\Strategy\Strategy;
class Insert extends Query { class Insert extends Query {
private $tableName; private string $tableName;
private $columns; private array $columns;
private $rows; private array $rows;
private $onDuplicateKey; private ?Strategy $onDuplicateKey;
private $returning; private ?string $returning;
public function __construct($sql, $name, $columns=array()) { public function __construct($sql, $name, $columns=array()) {
parent::__construct($sql); parent::__construct($sql);
@ -43,6 +45,4 @@ class Insert extends Query {
public function getRows() { return $this->rows; } public function getRows() { return $this->rows; }
public function onDuplicateKey() { return $this->onDuplicateKey; } public function onDuplicateKey() { return $this->onDuplicateKey; }
public function getReturning() { return $this->returning; } public function getReturning() { return $this->returning; }
}; }
?>

@ -2,16 +2,23 @@
namespace Driver\SQL\Query; namespace Driver\SQL\Query;
use Driver\SQL\SQL;
abstract class Query { abstract class Query {
protected $sql; protected SQL $sql;
public bool $dump;
public function __construct($sql) { public function __construct($sql) {
$this->sql = $sql; $this->sql = $sql;
$this->dump = false;
}
public function dump() {
$this->dump = true;
return $this;
} }
public abstract function execute(); public abstract function execute();
}; }
?>

@ -2,16 +2,20 @@
namespace Driver\SQL\Query; namespace Driver\SQL\Query;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Join;
class Select extends Query { class Select extends Query {
private $columns; private array $columns;
private $tables; private array $tables;
private $conditions; private array $conditions;
private $joins; private array $joins;
private $orderColumns; private array $orderColumns;
private $sortAscending; private array $groupColumns;
private $limit; private bool $sortAscending;
private $offset; private int $limit;
private int $offset;
public function __construct($sql, ...$columns) { public function __construct($sql, ...$columns) {
parent::__construct($sql); parent::__construct($sql);
@ -20,6 +24,7 @@ class Select extends Query {
$this->conditions = array(); $this->conditions = array();
$this->joins = array(); $this->joins = array();
$this->orderColumns = array(); $this->orderColumns = array();
$this->groupColumns = array();
$this->limit = 0; $this->limit = 0;
$this->offset = 0; $this->offset = 0;
$this->sortAscending = true; $this->sortAscending = true;
@ -31,17 +36,22 @@ class Select extends Query {
} }
public function where(...$conditions) { public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions); $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
return $this; return $this;
} }
public function innerJoin($table, $columnA, $columnB) { public function innerJoin($table, $columnA, $columnB) {
$this->joins[] = new \Driver\SQL\Join("INNER", $table, $columnA, $columnB); $this->joins[] = new Join("INNER", $table, $columnA, $columnB);
return $this; return $this;
} }
public function leftJoin($table, $columnA, $columnB) { public function leftJoin($table, $columnA, $columnB) {
$this->joins[] = new \Driver\SQL\Join("LEFT", $table, $columnA, $columnB); $this->joins[] = new Join("LEFT", $table, $columnA, $columnB);
return $this;
}
public function groupBy(...$columns) {
$this->groupColumns = $columns;
return $this; return $this;
} }
@ -51,12 +61,12 @@ class Select extends Query {
} }
public function ascending() { public function ascending() {
$this->ascending = true; $this->sortAscending = true;
return $this; return $this;
} }
public function descending() { public function descending() {
$this->ascending = false; $this->sortAscending = false;
return $this; return $this;
} }
@ -78,11 +88,10 @@ class Select extends Query {
public function getTables() { return $this->tables; } public function getTables() { return $this->tables; }
public function getConditions() { return $this->conditions; } public function getConditions() { return $this->conditions; }
public function getJoins() { return $this->joins; } public function getJoins() { return $this->joins; }
public function isOrderedAscending() { return $this->ascending; } public function isOrderedAscending() { return $this->sortAscending; }
public function getOrderBy() { return $this->orderColumns; } public function getOrderBy() { return $this->orderColumns; }
public function getLimit() { return $this->limit; } public function getLimit() { return $this->limit; }
public function getOffset() { return $this->offset; } public function getOffset() { return $this->offset; }
public function getGroupBy() { return $this->groupColumns; }
}; }
?>

@ -4,7 +4,7 @@ namespace Driver\SQL\Query;
class Truncate extends Query { class Truncate extends Query {
private $tableName; private string $tableName;
public function __construct($sql, $name) { public function __construct($sql, $name) {
parent::__construct($sql); parent::__construct($sql);
@ -15,7 +15,5 @@ class Truncate extends Query {
return $this->sql->executeTruncate($this); return $this->sql->executeTruncate($this);
} }
public function getTableName() { return $this->tableName; } public function getTable() { return $this->tableName; }
}; }
?>

@ -2,11 +2,13 @@
namespace Driver\SQL\Query; namespace Driver\SQL\Query;
use Driver\SQL\Condition\CondOr;
class Update extends Query { class Update extends Query {
private $values; private array $values;
private $table; private string $table;
private $conditions; private array $conditions;
public function __construct($sql, $table) { public function __construct($sql, $table) {
parent::__construct($sql); parent::__construct($sql);
@ -16,7 +18,7 @@ class Update extends Query {
} }
public function where(...$conditions) { public function where(...$conditions) {
$this->conditions = array_merge($this->conditions, $conditions); $this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
return $this; return $this;
} }
@ -32,6 +34,4 @@ class Update extends Query {
public function getTable() { return $this->table; } public function getTable() { return $this->table; }
public function getConditions() { return $this->conditions; } public function getConditions() { return $this->conditions; }
public function getValues() { return $this->values; } public function getValues() { return $this->values; }
}; }
?>

@ -2,16 +2,37 @@
namespace Driver\SQL; namespace Driver\SQL;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\CondIn;
use Driver\SQL\Condition\Condition;
use Driver\SQL\Condition\CondKeyword;
use Driver\SQL\Condition\CondNot;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Constraint\Constraint;
use \Driver\SQL\Constraint\Unique; use \Driver\SQL\Constraint\Unique;
use \Driver\SQL\Constraint\PrimaryKey; use \Driver\SQL\Constraint\PrimaryKey;
use \Driver\SQL\Constraint\ForeignKey; use \Driver\SQL\Constraint\ForeignKey;
use Driver\SQL\Query\CreateTable;
use Driver\SQL\Query\Delete;
use Driver\SQL\Query\Insert;
use Driver\SQL\Query\Query;
use Driver\SQL\Query\Select;
use Driver\SQL\Query\Truncate;
use Driver\SQL\Query\Update;
use Driver\SQL\Strategy\CascadeStrategy;
use Driver\SQL\Strategy\SetDefaultStrategy;
use Driver\SQL\Strategy\SetNullStrategy;
use Driver\SQL\Strategy\Strategy;
use Objects\ConnectionData;
abstract class SQL { abstract class SQL {
protected $lastError; protected string $lastError;
protected $connection; protected $connection;
protected $connectionData; protected ConnectionData $connectionData;
protected $lastInsertId; protected int $lastInsertId;
public function __construct($connectionData) { public function __construct($connectionData) {
$this->connection = NULL; $this->connection = NULL;
@ -29,27 +50,27 @@ abstract class SQL {
} }
public function createTable($tableName) { public function createTable($tableName) {
return new Query\CreateTable($this, $tableName); return new CreateTable($this, $tableName);
} }
public function insert($tableName, $columns=array()) { public function insert($tableName, $columns=array()) {
return new Query\Insert($this, $tableName, $columns); return new Insert($this, $tableName, $columns);
} }
public function select(...$columNames) { public function select(...$columNames) {
return new Query\Select($this, $columNames); return new Select($this, $columNames);
} }
public function truncate($table) { public function truncate($table) {
return new Query\Truncate($this, $table); return new Truncate($this, $table);
} }
public function delete($table) { public function delete($table) {
return new Query\Delete($this, $table); return new Delete($this, $table);
} }
public function update($table) { public function update($table) {
return new Query\Update($this, $table); return new Update($this, $table);
} }
// #################### // ####################
@ -65,7 +86,54 @@ abstract class SQL {
public abstract function disconnect(); public abstract function disconnect();
// Querybuilder // Querybuilder
public function executeCreateTable($createTable) { protected function buildQuery(Query $query, array &$params) {
if ($query instanceof Select) {
$select = $query;
$columns = $this->columnName($select->getColumns());
$tables = $select->getTables();
if (!$tables) {
return $this->execute("SELECT $columns", $params, true);
}
$tables = $this->tableName($tables);
$where = $this->getWhereClause($select->getConditions(), $params);
$joinStr = "";
$joins = $select->getJoins();
if (!empty($joins)) {
foreach($joins as $join) {
$type = $join->getType();
$joinTable = $this->tableName($join->getTable());
$columnA = $this->columnName($join->getColumnA());
$columnB = $this->columnName($join->getColumnB());
$joinStr .= " $type JOIN $joinTable ON $columnA=$columnB";
}
}
$groupBy = "";
$groupColumns = $select->getGroupBy();
if (!empty($groupColumns)) {
$groupBy = " GROUP BY " . $this->columnName($groupColumns);
}
$orderBy = "";
$orderColumns = $select->getOrderBy();
if (!empty($orderColumns)) {
$orderBy = " ORDER BY " . $this->columnName($orderColumns);
$orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC");
}
$limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : "");
$offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : "");
return "SELECT $columns FROM $tables$joinStr$where$groupBy$orderBy$limit$offset";
} else {
$this->lastError = "buildQuery() not implemented for type: " . get_class($query);
return FALSE;
}
}
public function executeCreateTable(CreateTable $createTable) {
$tableName = $this->tableName($createTable->getTableName()); $tableName = $this->tableName($createTable->getTableName());
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": ""; $ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
@ -89,73 +157,94 @@ abstract class SQL {
return $this->execute($query); return $this->execute($query);
} }
// TODO pull this function up public function executeInsert(Insert $insert) {
public abstract function executeInsert($query);
public function executeSelect($select) { $tableName = $this->tableName($insert->getTableName());
$columns = $insert->getColumns();
$rows = $insert->getRows();
$columns = $this->columnName($select->getColumns()); if (empty($rows)) {
$tables = $select->getTables(); $this->lastError = "No rows to insert given.";
return false;
}
if (is_null($columns) || empty($columns)) {
$columnStr = "";
} else {
$columnStr = " (" . $this->columnName($columns) . ")";
}
$parameters = array();
$values = array();
foreach($rows as $row) {
$rowPlaceHolder = array();
foreach($row as $val) {
$rowPlaceHolder[] = $this->addValue($val, $parameters);
}
$values[] = "(" . implode(",", $rowPlaceHolder) . ")";
}
$values = implode(",", $values);
$onDuplicateKey = $this->getOnDuplicateStrategy($insert->onDuplicateKey(), $parameters);
if ($onDuplicateKey === FALSE) {
return false;
}
$returningCol = $insert->getReturning();
$returning = $this->getReturning($returningCol);
$query = "INSERT INTO $tableName$columnStr VALUES $values$onDuplicateKey$returning";
if($insert->dump) { var_dump($query); var_dump($parameters); }
$res = $this->execute($query, $parameters, !empty($returning));
$success = ($res !== FALSE);
if($success && $returningCol) {
$this->fetchReturning($res, $returningCol);
}
return $success;
}
public function executeSelect(Select $select) {
$params = array(); $params = array();
$query = $this->buildQuery($select, $params);
if (!$tables) { if($select->dump) { var_dump($query); var_dump($params); }
return "SELECT $columns";
}
$tables = $this->tableName($tables);
$where = $this->getWhereClause($select->getConditions(), $params);
$joinStr = "";
$joins = $select->getJoins();
if (!empty($joins)) {
foreach($joins as $join) {
$type = $join->getType();
$joinTable = $this->tableName($join->getTable());
$columnA = $this->columnName($join->getColumnA());
$columnB = $this->columnName($join->getColumnB());
$joinStr .= " $type JOIN $joinTable ON $columnA=$columnB";
}
}
$orderBy = "";
$orderColumns = $select->getOrderBy();
if (!empty($orderColumns)) {
$orderBy = " ORDER BY " . $this->columnName($orderColumns);
$orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC");
}
$limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : "");
$offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : "");
$query = "SELECT $columns FROM $tables$joinStr$where$orderBy$limit$offset";
return $this->execute($query, $params, true); return $this->execute($query, $params, true);
} }
public function executeDelete($delete) { public function executeDelete(Delete $delete) {
$params = array();
$table = $this->tableName($delete->getTable()); $table = $this->tableName($delete->getTable());
$where = $this->getWhereClause($delete->getConditions(), $params); $where = $this->getWhereClause($delete->getConditions(), $params);
$query = "DELETE FROM $table$where"; $query = "DELETE FROM $table$where";
if($delete->dump) { var_dump($query); }
return $this->execute($query, $params);
}
public function executeTruncate(Truncate $truncate) {
$query = "TRUNCATE " . $this->tableName($truncate->getTable());
if ($truncate->dump) { var_dump($query); }
return $this->execute($query); return $this->execute($query);
} }
public function executeTruncate($truncate) { public function executeUpdate(Update $update) {
return $this->execute("TRUNCATE " . $truncate->getTable());
}
public function executeUpdate($update) {
$params = array(); $params = array();
$table = $this->tableName($update->getTable()); $table = $this->tableName($update->getTable());
$valueStr = array(); $valueStr = array();
foreach($update->getValues() as $key => $val) { foreach($update->getValues() as $key => $val) {
$valueStr[] = "$key=" . $this->addValue($val, $params); $valueStr[] = $this->columnName($key) . "=" . $this->addValue($val, $params);
} }
$valueStr = implode(",", $valueStr); $valueStr = implode(",", $valueStr);
$where = $this->getWhereClause($update->getConditions(), $params); $where = $this->getWhereClause($update->getConditions(), $params);
$query = "UPDATE $table SET $valueStr$where"; $query = "UPDATE $table SET $valueStr$where";
if($update->dump) { var_dump($query); var_dump($params); }
return $this->execute($query, $params); return $this->execute($query, $params);
} }
@ -167,10 +256,8 @@ abstract class SQL {
} }
} }
protected abstract function getColumnDefinition($column); public function getConstraintDefinition(Constraint $constraint) {
$columnName = $this->columnName($constraint->getColumnNames());
public function getConstraintDefinition($constraint) {
$columnName = $this->columnName($constraint->getColumnName());
if ($constraint instanceof PrimaryKey) { if ($constraint instanceof PrimaryKey) {
return "PRIMARY KEY ($columnName)"; return "PRIMARY KEY ($columnName)";
} else if ($constraint instanceof Unique) { } else if ($constraint instanceof Unique) {
@ -190,10 +277,19 @@ abstract class SQL {
return $code; return $code;
} else { } else {
$this->lastError = "Unsupported constraint type: " . get_class($strategy); $this->lastError = "Unsupported constraint type: " . get_class($constraint);
return false;
} }
} }
protected function getReturning(?string $columns) {
return "";
}
protected abstract function getColumnDefinition(Column $column);
protected abstract function fetchReturning($res, string $returningCol);
protected abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params);
protected abstract function getValueDefinition($val); protected abstract function getValueDefinition($val);
protected abstract function addValue($val, &$params); protected abstract function addValue($val, &$params);
@ -201,17 +297,25 @@ abstract class SQL {
protected abstract function columnName($col); protected abstract function columnName($col);
// Special Keywords and functions // Special Keywords and functions
public function now() { return $this->currentTimestamp(); }
public abstract function currentTimestamp(); public abstract function currentTimestamp();
public function count($col = NULL) { public function count($col = NULL) {
if (is_null($col)) { if (is_null($col)) {
return new Keyword("COUNT(*) AS count"); return new Keyword("COUNT(*) AS count");
} else { } else {
$countCol = strtolower(str_replace(".","_", $col)) . "_count";
$col = $this->columnName($col); $col = $this->columnName($col);
return new Keyword("COUNT($col) AS count"); return new Keyword("COUNT($col) AS $countCol");
} }
} }
public function sum($col) {
$sumCol = strtolower(str_replace(".","_", $col)) . "_sum";
$col = $this->columnName($col);
return new Keyword("SUM($col) AS $sumCol");
}
public function distinct($col) { public function distinct($col) {
$col = $this->columnName($col); $col = $this->columnName($col);
return new Keyword("DISTINCT($col)"); return new Keyword("DISTINCT($col)");
@ -221,29 +325,67 @@ abstract class SQL {
protected abstract function execute($query, $values=NULL, $returnValues=false); protected abstract function execute($query, $values=NULL, $returnValues=false);
protected function buildCondition($condition, &$params) { protected function buildCondition($condition, &$params) {
if ($condition instanceof \Driver\SQL\Condition\CondOr) {
if ($condition instanceof CondOr) {
$conditions = array(); $conditions = array();
foreach($condition->getConditions() as $cond) { foreach($condition->getConditions() as $cond) {
$conditions[] = $this->buildCondition($cond, $params); $conditions[] = $this->buildCondition($cond, $params);
} }
return "(" . implode(" OR ", $conditions) . ")"; return "(" . implode(" OR ", $conditions) . ")";
} else if ($condition instanceof \Driver\SQL\Condition\Compare) { } else if ($condition instanceof Compare) {
$column = $this->columnName($condition->getColumn()); $column = $this->columnName($condition->getColumn());
$value = $condition->getValue(); $value = $condition->getValue();
$operator = $condition->getOperator(); $operator = $condition->getOperator();
return $column . $operator . $this->addValue($value, $params); return $column . $operator . $this->addValue($value, $params);
} else if ($condition instanceof \Driver\SQL\Condition\CondBool) { } else if ($condition instanceof CondBool) {
return $this->columnName($condition->getValue()); return $this->columnName($condition->getValue());
} else if (is_array($condition)) { } else if (is_array($condition)) {
if (count($condition) == 1) { if (count($condition) === 1) {
return $this->buildCondition($condition[0], $params); return $this->buildCondition($condition[0], $params);
} else { } else {
$conditions = array(); $conditions = array();
foreach($condition as $cond) { foreach ($condition as $cond) {
$conditions[] = $this->buildCondition($cond, $params); $conditions[] = $this->buildCondition($cond, $params);
} }
return implode(" AND ", $conditions); return implode(" AND ", $conditions);
} }
} else if($condition instanceof CondIn) {
$expression = $condition->getExpression();
if (is_array($expression)) {
$values = array();
foreach ($expression as $value) {
$values[] = $this->addValue($value, $params);
}
$values = implode(",", $values);
} else if($expression instanceof Select) {
$values = $this->buildQuery($expression, $params);
} else {
$this->lastError = "Unsupported in-expression value: " . get_class($condition);
return false;
}
return $this->columnName($condition->getColumn()) . " IN ($values)";
} else if($condition instanceof CondKeyword) {
$left = $condition->getLeftExp();
$right = $condition->getRightExp();
$keyword = $condition->getKeyword();
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
return "$left $keyword $right ";
} else if($condition instanceof CondNot) {
$expression = $condition->getExpression();
if ($expression instanceof Condition) {
$expression = $this->buildCondition($expression, $params);
} else {
$expression = $this->columnName($expression);
}
return "NOT $expression";
} else {
$this->lastError = "Unsupported condition type: " . get_class($condition);
return false;
} }
} }
@ -260,7 +402,7 @@ abstract class SQL {
$this->connection = NULL; $this->connection = NULL;
} }
public static function createConnection($connectionData) { public static function createConnection(ConnectionData $connectionData) {
$type = $connectionData->getProperty("type"); $type = $connectionData->getProperty("type");
if ($type === "mysql") { if ($type === "mysql") {
$sql = new MySQL($connectionData); $sql = new MySQL($connectionData);
@ -279,6 +421,10 @@ abstract class SQL {
return $sql; return $sql;
} }
}
?> public abstract function getStatus();
public function parseBool($val) : bool {
return in_array($val, array(true, 1, '1', 't', 'true', 'TRUE'), true);
}
}

@ -7,6 +7,4 @@ class CascadeStrategy extends Strategy {
public function __construct() { public function __construct() {
} }
}; }
?>

@ -7,6 +7,4 @@ class SetDefaultStrategy extends Strategy {
public function __construct() { public function __construct() {
} }
}; }
?>

@ -7,6 +7,4 @@ class SetNullStrategy extends Strategy {
public function __construct() { public function __construct() {
} }
}; }
?>

@ -4,6 +4,4 @@ namespace Driver\SQL\Strategy;
abstract class Strategy { abstract class Strategy {
}; }
?>

@ -4,13 +4,17 @@ namespace Driver\SQL\Strategy;
class UpdateStrategy extends Strategy { class UpdateStrategy extends Strategy {
private $values; private array $values;
private array $conflictingColumns;
public function __construct($values) { public function __construct(array $conflictingColumns, array $values) {
$this->conflictingColumns = $conflictingColumns;
$this->values = $values; $this->values = $values;
} }
public function getValues() { return $this->values; } public function getConflictingColumns() {
}; return $this->conflictingColumns;
}
?> public function getValues() { return $this->values; }
}

@ -2,9 +2,8 @@
namespace Elements; namespace Elements;
abstract class Body extends \View { abstract class Body extends View {
public function __construct($document) { public function __construct($document) {
parent::__construct($document); parent::__construct($document);
} }
}; }
?>

@ -2,18 +2,22 @@
namespace Elements; namespace Elements;
use Objects\User;
abstract class Document { abstract class Document {
protected $head; protected Head $head;
protected $body; protected Body $body;
protected $user; protected User $user;
protected $databaseRequired; protected bool $databaseRequired;
private ?string $activeView;
public function __construct($user, $headClass, $bodyClass) { public function __construct(User $user, $headClass, $bodyClass, ?string $view = NULL) {
$this->head = new $headClass($this); $this->head = new $headClass($this);
$this->body = new $bodyClass($this); $this->body = new $bodyClass($this);
$this->user = $user; $this->user = $user;
$this->databaseRequired = true; $this->databaseRequired = true;
$this->activeView = $view;
} }
public function getHead() { return $this->head; } public function getHead() { return $this->head; }
@ -21,36 +25,14 @@ abstract class Document {
public function getSQL() { return $this->user->getSQL(); } public function getSQL() { return $this->user->getSQL(); }
public function getUser() { return $this->user; } public function getUser() { return $this->user; }
protected function sendHeaders() { public function getView() : ?View {
header("X-Frame-Options: DENY");
$file = getClassPath($this->activeView);
if(!file_exists($file) || !is_subclass_of($this->activeView, View::class)) {
return null;
} }
public static function createSearchableDocument($documentClass, $user) { return new $this->activeView($this);
return new $documentClass($user);
}
public static function createDocument($class) {
// TODO: check instance, configuration, ..
require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/sql.php';
// require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/conf/config.php';
// require_once realpath($_SERVER['DOCUMENT_ROOT']) . "/php/pages/$file.php";
require_once realpath($_SERVER['DOCUMENT_ROOT']) . '/php/api/objects/User.php';
$connectionData = getSqlData($database);
$sql = connectSQL($connectionData);
if(!$sql->isConnected()) {
http_response_code(500);
die('Internal Database error');
}
$user = new CUser($sql);
$document = new $class($user);
$code = $document->getCode();
$document->sendHeaders();
$user->sendCookies();
die($code);
} }
function getCode() { function getCode() {
@ -66,15 +48,14 @@ abstract class Document {
$body = $this->body->getCode(); $body = $this->body->getCode();
$head = $this->head->getCode(); $head = $this->head->getCode();
$lang = $this->user->getLanguage()->getShortCode();
$html = "<!DOCTYPE html>"; $html = "<!DOCTYPE html>";
$html .= "<html>"; $html .= "<html lang=\"$lang\">";
$html .= $head; $html .= $head;
$html .= $body; $html .= $body;
$html .= "</html>"; $html .= "</html>";
return $html; return $html;
} }
}; }
?>

@ -2,19 +2,20 @@
namespace Elements; namespace Elements;
abstract class Head extends \View { abstract class Head extends View {
protected $sources; protected array $sources;
protected $title; protected string $title;
protected $metas; protected array $metas;
protected $rawFields; protected array $rawFields;
protected $keywords; protected array $keywords;
protected $description; protected string $description;
protected $baseUrl; protected string $baseUrl;
function __construct($document) { function __construct($document) {
parent::__construct($document); parent::__construct($document);
$this->sources = array(); $this->sources = array();
$this->searchable = false;
$this->metas = $this->initMetas(); $this->metas = $this->initMetas();
$this->rawFields = $this->initRawFields(); $this->rawFields = $this->initRawFields();
$this->title = $this->initTitle(); $this->title = $this->initTitle();
@ -45,7 +46,7 @@ abstract class Head extends \View {
public function addKeywords($keywords) { array_merge($this->keywords, $keywords); } public function addKeywords($keywords) { array_merge($this->keywords, $keywords); }
public function getTitle() { return $this->title; } public function getTitle() { return $this->title; }
public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link("stylesheet", $href, $type); } public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link(Link::STYLESHEET, $href, $type); }
public function addStyle($style) { $this->sources[] = new Style($style); } public function addStyle($style) { $this->sources[] = new Style($style); }
public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); } public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); }
public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); } public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); }
@ -54,19 +55,6 @@ abstract class Head extends \View {
$this->addCSS(Link::FONTAWESOME); $this->addCSS(Link::FONTAWESOME);
} }
public function loadSyntaxHighlighting() {
$this->addJS(Script::HIGHLIGHT);
$this->addJSCode(Script::HIGHLIGHT_JS_LOADER);
$this->addCSS(Link::HIGHLIGHT);
$this->addCSS(Link::HIGHLIGHT_THEME);
}
public function loadJQueryTerminal($unixFormatting = true) {
$this->addJS(Script::JQUERY_TERMINAL);
if($unixFormatting) $this->addJS(Script::JQUERY_TERMINAL_UNIX);
$this->addCSS(Link::JQUERY_TERMINAL);
}
public function loadGoogleRecaptcha($siteKey) { public function loadGoogleRecaptcha($siteKey) {
$this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey"); $this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey");
} }
@ -80,11 +68,6 @@ abstract class Head extends \View {
$this->addJS(Script::BOOTSTRAP); $this->addJS(Script::BOOTSTRAP);
} }
public function loadChartJS() {
$this->addJS(Script::MOMENT);
$this->addJS(Script::CHART);
}
public function getCode() { public function getCode() {
$header = "<head>"; $header = "<head>";
@ -123,4 +106,3 @@ abstract class Head extends \View {
return $header; return $header;
} }
} }
?>

@ -2,41 +2,28 @@
namespace Elements; namespace Elements;
class Link extends Source { class Link extends StaticView {
const STYLESHEET = "stylesheet"; const STYLESHEET = "stylesheet";
const MIME_TEXT_CSS = "text/css"; const MIME_TEXT_CSS = "text/css";
const FONTAWESOME = '/css/fontawesome.min.css'; const FONTAWESOME = "/css/fontawesome.min.css";
// const JQUERY_UI = '/css/jquery-ui.css'; const BOOTSTRAP = "/css/bootstrap.min.css";
// const JQUERY_TERMINAL = '/css/jquery.terminal.min.css';
const BOOTSTRAP = '/css/bootstrap.min.css';
// const BOOTSTRAP_THEME = '/css/bootstrap-theme.min.css';
// const BOOTSTRAP_DATEPICKER_CSS = '/css/bootstrap-datepicker.standalone.min.css';
// const BOOTSTRAP_DATEPICKER3_CSS = '/css/bootstrap-datepicker.standalone.min.css';
// const HIGHLIGHT = '/css/highlight.css';
// const HIGHLIGHT_THEME = '/css/theme.css';
const CORE = "/css/style.css"; const CORE = "/css/style.css";
const ADMIN = "/css/admin.css"; const ACCOUNT = "/css/account.css";
// const HOME = "/css/home.css";
// const REVEALJS = "/css/reveal.css";
// const REVEALJS_THEME_MOON = "/css/reveal_moon.css";
// const REVEALJS_THEME_BLACK = "/css/reveal_black.css";
private $type; private string $type;
private $rel; private string $rel;
private string $href;
function __construct($rel, $href, $type = "") { function __construct($rel, $href, $type = "") {
parent::__construct('link', $href); $this->href = $href;
$this->type = $type; $this->type = $type;
$this->rel = $rel; $this->rel = $rel;
} }
function getCode() { function getCode() {
$type = (empty($this->type) ? "" : " type=\"$this->type\""); $type = (empty($this->type) ? "" : " type=\"$this->type\"");
$link = "<link rel=\"$this->rel\" href=\"$this->url\" $type/>"; return "<link rel=\"$this->rel\" href=\"$this->href\"$type/>";
return $link;
} }
} }
?>

@ -2,52 +2,28 @@
namespace Elements; namespace Elements;
class Script extends Source { class Script extends StaticView {
const MIME_TEXT_JAVASCRIPT = "text/javascript"; const MIME_TEXT_JAVASCRIPT = "text/javascript";
const CORE = "/js/script.js"; const CORE = "/js/script.js";
// const HOME = "/js/home.js";
const ADMIN = "/js/admin.js";
// const SORTTABLE = "/js/sorttable.js";
const JQUERY = "/js/jquery.min.js"; const JQUERY = "/js/jquery.min.js";
// const JQUERY_UI = "/js/jquery-ui.js";
// const JQUERY_MASKED_INPUT = "/js/jquery.maskedinput.min.js";
// const JQUERY_CONTEXT_MENU = "/js/jquery.contextmenu.min.js";
// const JQUERY_TERMINAL = "/js/jquery.terminal.min.js";
// const JQUERY_TERMINAL_UNIX = "/js/unix_formatting.js";
// const JSCOLOR = "/js/jscolor.min.js";
// const SYNTAX_HIGHLIGHTER = "/js/syntaxhighlighter.js";
// const HIGHLIGHT = "/js/highlight.pack.js";
// const GOOGLE_CHARTS = "/js/loader.js";
const BOOTSTRAP = "/js/bootstrap.min.js";
// const BOOTSTRAP_DATEPICKER_JS = "/js/bootstrap-datepicker.min.js";
// const POPPER = "/js/popper.min.js";
// const JSMPEG = "/js/jsmpeg.min.js";
// const MOMENT = "/js/moment.min.js";
// const CHART = "/js/chart.js";
// const REVEALJS = "/js/reveal.js";
// const REVEALJS_PLUGIN_NOTES = "/js/reveal_notes.js";
const INSTALL = "/js/install.js"; const INSTALL = "/js/install.js";
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
const ACCOUNT = "/js/account.js";
const HIGHLIGHT_JS_LOADER = "\$(document).ready(function(){\$('code').each(function(i, block) { hljs.highlightBlock(block); }); })"; private string $type;
private string $content;
private $type; private string $src;
private $content;
function __construct($type, $src, $content = "") { function __construct($type, $src, $content = "") {
parent::__construct('script', $src); $this->src = $src;
$this->type = $type; $this->type = $type;
$this->content = $content; $this->content = $content;
} }
function getCode() { function getCode() {
$src = (empty($this->url) ? "" : " src=\"$this->url\""); $src = (empty($this->src) ? "" : " src=\"$this->src\"");
$script = "<script type=\"$this->type\"$src>"; return "<script type=\"$this->type\"$src>$this->content</script>";
$script .= $this->content;
$script .= '</script>';
return $script;
} }
} }
?>

@ -0,0 +1,16 @@
<?php
namespace Elements;
abstract class SimpleBody extends Body {
public function __construct($document) {
parent::__construct($document);
}
public function getCode() {
$content = $this->getContent();
return parent::getCode() . "<body>$content</body>";
}
protected abstract function getContent();
}

@ -1,22 +0,0 @@
<?php
namespace Elements;
class Source extends \View {
protected $sourceType;
protected $url;
public function __construct($sourceType, $url) {
$this->sourceType = $sourceType;
$this->url = $url;
}
public function getCode() {
return "<$sourceType />";
}
public function getUrl() { return $this->url; }
}
?>

@ -0,0 +1,13 @@
<?php
namespace Elements;
abstract class StaticView {
public abstract function getCode();
public function __toString() {
return $this->getCode();
}
}

@ -2,12 +2,11 @@
namespace Elements; namespace Elements;
class Style extends Source { class Style extends StaticView {
private $style; private string $style;
function __construct($style) { function __construct($style) {
parent::__construct('style', '');
$this->style = $style; $this->style = $style;
} }
@ -15,5 +14,3 @@ class Style extends Source {
return "<style>$this->style</style>"; return "<style>$this->style</style>";
} }
} }
?>

@ -0,0 +1,136 @@
<?php
namespace Elements;
use External\PHPMailer\Exception;
abstract class View extends StaticView {
private Document $document;
private bool $loadView;
protected bool $searchable;
protected string $reference;
protected string $title;
protected array $langModules;
public function __construct(Document $document, $loadView = true) {
$this->document = $document;
$this->searchable = false;
$this->reference = "";
$this->title = "Untitled View";
$this->langModules = array();
$this->loadView = $loadView;
}
public function getTitle() { return $this->title; }
public function getDocument() { return $this->document; }
public function isSearchable() { return $this->searchable; }
public function getReference() { return $this->reference; }
protected function load(string $viewClass) : string {
try {
$reflectionClass = new \ReflectionClass($viewClass);
if ($reflectionClass->isSubclassOf(View::class) && $reflectionClass->isInstantiable()) {
$view = $reflectionClass->newInstanceArgs(array($this->getDocument()));
$view->loadView();
return $view;
}
} catch(\ReflectionException $e) {
error_log($e->getMessage());
}
return "";
}
private function loadLanguageModules() {
$lang = $this->document->getUser()->getLanguage();
foreach($this->langModules as $langModule) {
$lang->loadModule($langModule);
}
}
// Virtual Methods
public function loadView() { }
public function getCode() {
// Load translations
$this->loadLanguageModules();
// Load Meta Data + Head (title, scripts, includes, ...)
if($this->loadView) {
$this->loadView();
}
return '';
}
// UI Functions
private function createList($items, $tag) {
if(count($items) === 0)
return "<$tag></$tag>";
else
return "<$tag><li>" . implode("</li><li>", $items) . "</li></$tag>";
}
public function createOrderedList($items=array()) {
return $this->createList($items, "ol");
}
public function createUnorderedList($items=array()) {
return $this->createList($items, "ul");
}
protected function createLink($link, $title=null) {
if(is_null($title)) $title=$link;
return "<a href=\"$link\">$title</a>";
}
protected function createExternalLink($link, $title=null) {
if(is_null($title)) $title=$link;
return "<a href=\"$link\" target=\"_blank\" class=\"external\">$title</a>";
}
protected function createIcon($icon, $type = "fas", $classes = "") {
$iconClass = "$type fa-$icon";
if($icon === "spinner" || $icon === "circle-notch")
$iconClass .= " fa-spin";
if($classes)
$iconClass .= " $classes";
return "<i class=\"$iconClass\" ></i>";
}
protected function createErrorText($text, $id="", $hidden=false) {
return $this->createStatusText("danger", $text, $id, $hidden);
}
protected function createWarningText($text, $id="", $hidden=false) {
return $this->createStatusText("warning", $text, $id, $hidden);
}
protected function createSuccessText($text, $id="", $hidden=false) {
return $this->createStatusText("success", $text, $id, $hidden);
}
protected function createSecondaryText($text, $id="", $hidden=false) {
return $this->createStatusText("secondary", $text, $id, $hidden);
}
protected function createInfoText($text, $id="", $hidden=false) {
return $this->createStatusText("info", $text, $id, $hidden);
}
protected function createStatusText($type, $text, $id="", $hidden=false) {
if(strlen($id) > 0) $id = " id=\"$id\"";
$hidden = ($hidden?" hidden" : "");
return "<div class=\"alert alert-$type$hidden\" role=\"alert\"$id>$text</div>";
}
protected function createBadge($type, $text) {
$text = htmlspecialchars($text);
return "<span class=\"badge badge-$type\">$text</span>";
}
}

File diff suppressed because it is too large Load Diff

@ -6,9 +6,6 @@ abstract class ApiObject implements \JsonSerializable {
public abstract function jsonSerialize(); public abstract function jsonSerialize();
public function __construct() { }
public function __toString() { return json_encode($this); } public function __toString() { return json_encode($this); }
} }
?>

@ -4,11 +4,11 @@ namespace Objects;
class ConnectionData { class ConnectionData {
private $host; private string $host;
private $port; private int $port;
private $login; private string $login;
private $password; private string $password;
private $properties; private array $properties;
public function __construct($host, $port, $login, $password) { public function __construct($host, $port, $login, $password) {
$this->host = $host; $this->host = $host;
@ -32,6 +32,7 @@ class ConnectionData {
} }
$this->properties[$key] = $val; $this->properties[$key] = $val;
return true;
} }
public function getHost() { return $this->host; } public function getHost() { return $this->host; }
@ -39,5 +40,3 @@ class ConnectionData {
public function getLogin() { return $this->login; } public function getLogin() { return $this->login; }
public function getPassword() { return $this->password; } public function getPassword() { return $this->password; }
} }
?>

@ -2,16 +2,18 @@
namespace Objects { namespace Objects {
use Objects\lang\LanguageModule;
class Language extends ApiObject { class Language extends ApiObject {
const LANG_CODE_PATTERN = "/^[a-zA-Z]+_[a-zA-Z]+$/"; const LANG_CODE_PATTERN = "/^[a-zA-Z]+_[a-zA-Z]+$/";
private $languageId; private int $languageId;
private $langCode; private string $langCode;
private $langName; private string $langName;
private $modules; private array $modules;
protected $entries; protected array $entries;
public function __construct($languageId, $langCode, $langName) { public function __construct($languageId, $langCode, $langName) {
$this->languageId = $languageId; $this->languageId = $languageId;
@ -29,7 +31,7 @@ namespace Objects {
public function getEntries() { return $this->entries; } public function getEntries() { return $this->entries; }
public function getModules() { return $this->modules; } public function getModules() { return $this->modules; }
public function loadModule($module) { public function loadModule(LanguageModule $module) {
if(!is_object($module)) if(!is_object($module))
$module = new $module; $module = new $module;
@ -100,6 +102,7 @@ namespace Objects {
} }
namespace { namespace {
function L($key) { function L($key) {
if(!array_key_exists('LANGUAGE', $GLOBALS)) if(!array_key_exists('LANGUAGE', $GLOBALS))
return $key; return $key;
@ -132,4 +135,3 @@ namespace {
return $LANGUAGE->getShortCode(); return $LANGUAGE->getShortCode();
} }
} }
?>

@ -2,28 +2,34 @@
namespace Objects; namespace Objects;
use DateTime;
use \Driver\SQL\Condition\Compare; use \Driver\SQL\Condition\Compare;
use Exception;
use External\JWT;
class Session extends ApiObject { class Session extends ApiObject {
const DURATION = 120; # in minutes
const DURATION = 60*24;
private $sessionId; private ?int $sessionId;
private $user; private User $user;
private $expires; private int $expires;
private $ipAddress; private string $ipAddress;
private $os; private ?string $os;
private $browser; private ?string $browser;
private $stayLoggedIn; private bool $stayLoggedIn;
private string $csrfToken;
public function __construct($user, $sessionId) { public function __construct(User $user, ?int $sessionId, ?string $csrfToken) {
$this->user = $user; $this->user = $user;
$this->sessionId = $sessionId; $this->sessionId = $sessionId;
$this->stayLoggedIn = true; $this->stayLoggedIn = true;
$this->csrfToken = $csrfToken ?? generateRandomString(16);
} }
public static function create($user, $stayLoggedIn) { public static function create($user, $stayLoggedIn) {
$session = new Session($user, null); $session = new Session($user, null, null);
if($session->insert($stayLoggedIn)) { if($session->insert($stayLoggedIn)) {
return $session; return $session;
} }
@ -38,7 +44,7 @@ class Session extends ApiObject {
$userAgent = @get_browser($_SERVER['HTTP_USER_AGENT'], true); $userAgent = @get_browser($_SERVER['HTTP_USER_AGENT'], true);
$this->os = $userAgent['platform'] ?? "Unknown"; $this->os = $userAgent['platform'] ?? "Unknown";
$this->browser = $userAgent['parent'] ?? "Unknown"; $this->browser = $userAgent['parent'] ?? "Unknown";
} catch(\Exception $ex) { } catch(Exception $ex) {
$this->os = "Unknown"; $this->os = "Unknown";
$this->browser = "Unknown"; $this->browser = "Unknown";
} }
@ -56,14 +62,12 @@ class Session extends ApiObject {
public function sendCookie() { public function sendCookie() {
$this->updateMetaData(); $this->updateMetaData();
$jwt = $this->user->getConfiguration()->getJwt(); $settings = $this->user->getConfiguration()->getSettings();
if($jwt) {
$token = array('userId' => $this->user->getId(), 'sessionId' => $this->sessionId); $token = array('userId' => $this->user->getId(), 'sessionId' => $this->sessionId);
$sessionCookie = \External\JWT::encode($token, $jwt->getKey()); $sessionCookie = JWT::encode($token, $settings->getJwtSecret());
$secure = strcmp(getProtocol(), "https") === 0; $secure = strcmp(getProtocol(), "https") === 0;
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure); setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure);
} }
}
public function getExpiresTime() { public function getExpiresTime() {
return ($this->stayLoggedIn == 0 ? 0 : $this->expires); return ($this->stayLoggedIn == 0 ? 0 : $this->expires);
@ -81,6 +85,7 @@ class Session extends ApiObject {
'ipAddress' => $this->ipAddress, 'ipAddress' => $this->ipAddress,
'os' => $this->os, 'os' => $this->os,
'browser' => $this->browser, 'browser' => $this->browser,
'csrf_token' => $this->csrfToken
); );
} }
@ -88,19 +93,20 @@ class Session extends ApiObject {
$this->updateMetaData(); $this->updateMetaData();
$sql = $this->user->getSQL(); $sql = $this->user->getSQL();
$hours = Session::DURATION; $minutes = Session::DURATION;
$columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in"); $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in", "csrf_token");
$success = $sql $success = $sql
->insert("Session", $columns) ->insert("Session", $columns)
->addRow( ->addRow(
(new \DateTime)->modify("+$hours hour"), (new DateTime())->modify("+$minutes minute"),
$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,
$this->csrfToken)
->returning("uid") ->returning("uid")
->execute(); ->execute();
@ -113,32 +119,31 @@ class Session extends ApiObject {
} }
public function destroy() { public function destroy() {
$success = $this->user->getSQL()->update("Session") return $this->user->getSQL()->update("Session")
->set("active", false) ->set("active", false)
->where(new Compare("Session.uid", $this->sessionId)) ->where(new Compare("Session.uid", $this->sessionId))
->where(new Compare("Session.user_id", $this->user->getId())) ->where(new Compare("Session.user_id", $this->user->getId()))
->execute(); ->execute();
return $success;
} }
public function update() { public function update() {
$this->updateMetaData(); $this->updateMetaData();
$hours = Session::DURATION; $minutes = Session::DURATION;
$sql = $this->user->getSQL(); $sql = $this->user->getSQL();
$success = $sql->update("Session") return $sql->update("Session")
->set("Session.expires", (new \DateTime)->modify("+$hours hour")) ->set("Session.expires", (new DateTime())->modify("+$minutes minute"))
->set("Session.ipAddress", $this->ipAddress) ->set("Session.ipAddress", $this->ipAddress)
->set("Session.os", $this->os) ->set("Session.os", $this->os)
->set("Session.browser", $this->browser) ->set("Session.browser", $this->browser)
->set("Session.data", json_encode($_SESSION)) ->set("Session.data", json_encode($_SESSION))
->set("Session.csrf_token", $this->csrfToken)
->where(new Compare("Session.uid", $this->sessionId)) ->where(new Compare("Session.uid", $this->sessionId))
->where(new Compare("Session.user_id", $this->user->getId())) ->where(new Compare("Session.user_id", $this->user->getId()))
->execute(); ->execute();
}
return $success; public function getCsrfToken(): string {
return $this->csrfToken;
} }
} }
?>

Some files were not shown because too many files have changed in this diff Show More