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
Options -Indexes
RedirectMatch 404 /\.git
DirectorySlash Off
RewriteEngine On
RewriteRule ^api/(.*)?$ index.php?api=$1&$2 [L,QSA]
RewriteRule ^((?!((js|css|img|fonts|api)($|\/)))(.*)?)$ index.php?site=$1&$2 [L,QSA]
RewriteRule ^api(/.*)?$ /index.php?api=$1 [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 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:
- Installation Guide with automatic database setup
- REST API
- Account managment
- New: Now supporting MySQL + PostgreSQL
- Account management
- Supporting MySQL + PostgreSQL
- New: Page Routing
- New: Admin Dashboard
- New: Account & User functions
### 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, ..

@ -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;
use DateTime;
class Parameter {
const TYPE_INT = 0;
const TYPE_FLOAT = 1;
@ -14,15 +16,17 @@ class Parameter {
// only internal access
const TYPE_RAW = 8;
// only json will work here i guess
const TYPE_ARRAY = 9;
const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array');
public $name;
public string $name;
public $value;
public $optional;
public $type;
public $typeName;
public int $type;
public string $typeName;
public function __construct($name, $type, $optional = FALSE, $defaultValue = NULL) {
$this->name = $name;
@ -59,11 +63,11 @@ class Parameter {
return Parameter::TYPE_BOOLEAN;
else if(is_a($value, 'DateTime'))
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;
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;
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;
else if (filter_var($value, FILTER_VALIDATE_EMAIL))
return Parameter::TYPE_EMAIL;
@ -156,6 +160,4 @@ class Parameter {
return true;
}
}
}
?>
}

@ -4,10 +4,10 @@ namespace Api\Parameter;
class StringType extends Parameter {
public $maxLength;
public int $maxLength;
public function __construct($name, $maxLength = -1, $optional = FALSE, $defaultValue = NULL) {
parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue);
$this->maxLength = $maxLength;
parent::__construct($name, Parameter::TYPE_STRING, $optional, $defaultValue);
}
public function parseParam($value) {
@ -38,6 +38,4 @@ class StringType extends Parameter {
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;
use Objects\User;
class Request {
protected $user;
protected $params;
protected $lastError;
protected $result;
protected $success;
protected $isPublic;
protected $loginRequired;
protected $variableParamCount;
protected $isDisabled;
protected $apiKeyAllowed;
protected User $user;
protected array $params;
protected string $lastError;
protected array $result;
protected bool $success;
protected bool $isPublic;
protected bool $loginRequired;
protected bool $variableParamCount;
protected bool $isDisabled;
protected bool $apiKeyAllowed;
protected bool $csrfTokenRequired;
private $aDefaultParams;
private $allowedMethods;
private $externCall;
private array $aDefaultParams;
private array $allowedMethods;
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->aDefaultParams = $params;
$this->lastError = '';
$this->success = false;
$this->result = array();
$this->externCall = $externCall;
$this->externalCall = $externalCall;
$this->isPublic = true;
$this->isDisabled = false;
$this->loginRequired = false;
$this->variableParamCount = false;
$this->apiKeyAllowed = true;
$this->allowedMethods = array("GET", "POST");
$this->lastError = "";
$this->csrfTokenRequired = true;
}
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) {
foreach($this->params as $name => $param) {
$value = (isset($values[$name]) ? $values[$name] : NULL);
if(!$param->optional && is_null($value)) {
$this->lastError = 'Missing parameter: ' . $name;
return false;
foreach($this->params as $name => $param) {
$value = $values[$name] ?? NULL;
$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)) {
$value = print_r($value, true);
$this->lastError = "Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")";
return false;
return $this->createError("Invalid Type for parameter: $name '$value' (Required: " . $param->getTypeName() . ")");
}
}
}
@ -93,11 +85,17 @@ class Request {
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
}
if($this->externCall) {
if($this->externalCall) {
$values = $_REQUEST;
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);
$values = array_merge($values, $jsonData);
if ($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;
}
if($this->externCall && !$this->isPublic) {
if($this->externalCall && !$this->isPublic) {
$this->lastError = 'This function is private.';
header('HTTP 1.1 403 Forbidden');
return false;
@ -118,18 +116,42 @@ class Request {
return false;
}
if($this->externalCall) {
$apiKeyAuthorized = false;
if($this->loginRequired) {
$authorized = false;
if(isset($values['api_key']) && $this->apiKeyAllowed) {
$apiKey = $values['api_key'];
$authorized = $this->user->authorize($apiKey);
// Logged in or api key authorized?
if ($this->loginRequired) {
if(isset($values['api_key']) && $this->apiKeyAllowed) {
$apiKey = $values['api_key'];
$apiKeyAuthorized = $this->user->authorize($apiKey);
}
if(!$this->user->isLoggedIn() && !$apiKeyAuthorized) {
$this->lastError = 'You are not logged in.';
header('HTTP 1.1 401 Unauthorized');
return false;
}
}
if(!$this->user->isLoggedIn() && !$authorized) {
$this->lastError = 'You are not logged in.';
header('HTTP 1.1 401 Unauthorized');
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;
}
}
}
@ -149,47 +171,32 @@ class Request {
return true;
}
protected function isValidString($str, $regex) {
return preg_replace($regex, "", $str) === $str;
}
protected function createError($err) {
$this->success = false;
$this->lastError = $err;
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 getDescription() { return ''; }
public function getSection() { return 'Default'; }
public function getLastError() { return $this->lastError; }
public function getResult() { return $this->result; }
public function success() { return $this->success; }
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() {
$this->result['success'] = $this->success;
$this->result['msg'] = $this->lastError;
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

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

@ -2,16 +2,16 @@
namespace Configuration;
use \Driver\SQL\Query\CreateTable;
use \Driver\SQL\Query\Insert;
use \Driver\SQL\Column\Column;
use \Driver\SQL\Strategy\UpdateStrategy;
use Driver\SQL\SQL;
use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\CascadeStrategy;
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();
// Language
@ -23,17 +23,18 @@ class CreateDatabase {
->unique("code")
->unique("name");
$queries[] = $sql->insert("Language", array("uid", "code", "name"))
->addRow(1, "en_US", 'American English')
->addRow(2, "de_DE", 'Deutsch Standard');
$queries[] = $sql->insert("Language", array("code", "name"))
->addRow( "en_US", 'American English')
->addRow( "de_DE", 'Deutsch Standard');
$queries[] = $sql->createTable("User")
->addSerial("uid")
->addString("email", 64, true)
->addString("name", 32)
->addString("salt", 16)
->addString("password", 64)
->addString("password", 128)
->addBool("confirmed", false)
->addInt("language_id", true, 1)
->addDateTime("registered_at", false, $sql->currentTimestamp())
->primaryKey("uid")
->unique("email")
->unique("name")
@ -49,35 +50,39 @@ class CreateDatabase {
->addString("browser", 64)
->addJson("data", false, '{}')
->addBool("stay_logged_in", true)
->addString("csrf_token", 16 )
->primaryKey("uid", "user_id")
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("UserToken")
->addInt("user_id")
->addString("token", 36)
->addEnum("token_type", array("password_reset", "confirmation"))
->addEnum("token_type", array("password_reset", "email_confirm", "invite"))
->addDateTime("valid_until")
->addBool("used", false)
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("Group")
->addSerial("uid")
->addString("name", 32)
->addString("color", 10)
->primaryKey("uid")
->unique("name");
$queries[] = $sql->insert("Group", array("uid", "name"))
->addRow(USER_GROUP_DEFAULT, "Default")
->addRow(USER_GROUP_ADMIN, "Administrator");
$queries[] = $sql->insert("Group", array("name", "color"))
->addRow(USER_GROUP_MODERATOR_NAME, "#007bff")
->addRow(USER_GROUP_SUPPORT_NAME, "#28a745")
->addRow(USER_GROUP_ADMIN_NAME, "#dc3545");
$queries[] = $sql->createTable("UserGroup")
->addInt("user_id")
->addInt("group_id")
->unique("user_id", "group_id")
->foreignKey("user_id", "User", "uid")
->foreignKey("group_id", "Group", "uid");
->foreignKey("user_id", "User", "uid", new CascadeStrategy())
->foreignKey("group_id", "Group", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("Notification")
->addSerial("uid")
->addEnum("type", array("default","message","warning"), false, "default")
->addDateTime("created_at", false, $sql->currentTimestamp())
->addString("title", 32)
->addString("message", 256)
@ -86,7 +91,7 @@ class CreateDatabase {
$queries[] = $sql->createTable("UserNotification")
->addInt("user_id")
->addInt("notification_id")
->addBool("seen")
->addBool("seen", false)
->foreignKey("user_id", "User", "uid")
->foreignKey("notification_id", "Notification", "uid")
->unique("user_id", "notification_id");
@ -94,7 +99,7 @@ class CreateDatabase {
$queries[] = $sql->createTable("GroupNotification")
->addInt("group_id")
->addInt("notification_id")
->addBool("seen")
->addBool("seen", false)
->foreignKey("group_id", "Group", "uid")
->foreignKey("notification_id", "Notification", "uid")
->unique("group_id", "notification_id");
@ -108,8 +113,116 @@ class CreateDatabase {
->primaryKey("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;
}
}
?>
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
namespace Documents {
class Admin extends \Elements\Document {
public function __construct($user) {
parent::__construct($user, Admin\Head::class, Admin\Body::class);
use Documents\Admin\AdminHead;
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 {
class Head extends \Elements\Head {
use Elements\Head;
use Elements\Link;
use Elements\Script;
class AdminHead extends Head {
public function __construct($document) {
parent::__construct($document);
}
protected function initSources() {
$this->loadJQuery();
$this->loadBootstrap();
$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() {
@ -44,26 +50,4 @@ namespace Documents\Admin {
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
namespace Documents {
class Document404 extends \Elements\Document {
public function __construct($user) {
parent::__construct($user, Document404\Head404::class, Document404\Body404::class);
use Documents\Document404\Body404;
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 {
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) {
parent::__construct($document);
}
protected function initSources() {
// $this->loadJQuery();
// $this->loadBootstrap();
// $this->loadFontawesome();
// $this->addJS(\Elements\Script::CORE);
// $this->addCSS(\Elements\Link::CORE);
}
protected function initMetas() {
@ -43,18 +48,18 @@ namespace Documents\Document404 {
}
}
class Body404 extends \Elements\Body {
class Body404 extends SimpleBody {
public function __construct($document) {
parent::__construct($document);
}
public function getCode() {
$html = parent::getCode();
$html .= "<b>404 Not Found</b>";
return $html;
public function loadView() {
http_response_code(404);
}
protected function getContent() {
return $this->load(View404::class);
}
}
}
?>

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

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

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

@ -7,6 +7,4 @@ class DateTimeColumn extends Column {
public function __construct($name, $nullable=false, $defaultValue=NULL) {
parent::__construct($name, $nullable, $defaultValue);
}
}
?>
}

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

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

@ -8,6 +8,4 @@ class JsonColumn extends Column {
parent::__construct($name, $nullable, $defaultValue);
}
}
?>
}

@ -8,6 +8,4 @@ class SerialColumn extends Column {
parent::__construct($name, false, $defaultValue); # not nullable
}
}
?>
}

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

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

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

@ -4,12 +4,12 @@ namespace Driver\SQL\Condition;
class CondBool extends Condition {
private $value;
public function __construct($val) {
$this->value = $val;
}
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 {
private $conditions;
private array $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; }
}
?>
}

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

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

@ -4,13 +4,11 @@ namespace Driver\SQL\Constraint;
abstract class Constraint {
private $columnName;
private array $columnNames;
public function __construct($columnName) {
$this->columnName = $columnName;
public function __construct($columnNames) {
$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;
use Driver\SQL\Strategy\Strategy;
class ForeignKey extends Constraint {
private $referencedTable;
private $referencedColumn;
private $strategy;
private string $referencedTable;
private string $referencedColumn;
private ?Strategy $strategy;
public function __construct($name, $refTable, $refColumn, $strategy = NULL) {
parent::__construct($name);
@ -18,6 +20,4 @@ class ForeignKey extends Constraint {
public function getReferencedTable() { return $this->referencedTable; }
public function getReferencedColumn() { return $this->referencedColumn; }
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);
}
};
?>
}

@ -8,6 +8,4 @@ class Unique extends Constraint {
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 {
private $type;
private $table;
private $columnA;
private $columnB;
private string $type;
private string $table;
private string $columnA;
private string $columnB;
public function __construct($type, $table, $columnA, $columnB) {
$this->tpye = $type;
$this->type = $type;
$this->table = $table;
$this->columnA = $columnA;
$this->columnB = $columnB;
@ -21,6 +21,4 @@ class Join {
public function getColumnA() { return $this->columnA; }
public function getColumnB() { return $this->columnB; }
}
?>
}

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

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

@ -4,7 +4,7 @@ namespace Driver\SQL;
use \Api\Parameter\Parameter;
use \Driver\SQL\Column\Column;
use Driver\SQL\Column\Column;
use \Driver\SQL\Column\IntColumn;
use \Driver\SQL\Column\SerialColumn;
use \Driver\SQL\Column\StringColumn;
@ -13,10 +13,10 @@ use \Driver\SQL\Column\DateTimeColumn;
use Driver\SQL\Column\BoolColumn;
use Driver\SQL\Column\JsonColumn;
use \Driver\SQL\Strategy\CascadeStrategy;
use \Driver\SQL\Strategy\SetDefaultStrategy;
use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\UpdateStrategy;
use Driver\SQL\Condition\CondRegex;
use Driver\SQL\Expression\Add;
use Driver\SQL\Strategy\Strategy;
use Driver\SQL\Strategy\UpdateStrategy;
class PostgreSQL extends SQL {
@ -32,7 +32,7 @@ class PostgreSQL extends SQL {
return 'pgsql';
}
// Connection Managment
// Connection Management
public function connect() {
if(!is_null($this->connection)) {
return true;
@ -68,13 +68,13 @@ class PostgreSQL extends SQL {
if(is_null($this->connection))
return;
pg_close($this->connection);
@pg_close($this->connection);
}
public function getLastError() {
$lastError = parent::getLastError();
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;
@ -99,6 +99,9 @@ class PostgreSQL extends SQL {
case Parameter::TYPE_DATE_TIME:
$value = $value->format("Y-m-d H:i:s");
break;
case Parameter::TYPE_ARRAY:
$value = json_encode($value);
break;
default:
break;
}
@ -131,76 +134,48 @@ class PostgreSQL extends SQL {
}
}
// Querybuilder
public function executeInsert($insert) {
protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) {
if (!is_null($strategy)) {
if ($strategy instanceof UpdateStrategy) {
$updateValues = array();
foreach($strategy->getValues() as $key => $value) {
$leftColumn = $this->columnName($key);
if ($value instanceof Column) {
$columnName = $this->columnName($value->getName());
$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 {
$updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters);
}
}
$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();
foreach($onDuplicateKey->getValues() as $key => $value) {
if ($value instanceof Column) {
$columnName = $value->getName();
$updateValues[] = "\"$key\"=\"$columnName\"";
$conflictingColumns = $this->columnName($strategy->getConflictingColumns());
$updateValues = implode(",", $updateValues);
return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues";
} else {
$updateValues[] = "\"$key\"=" . $this->addValue($value, $parameters);
$strategyClass = get_class($strategy);
$this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
return false;
}
}
$onDuplicateKey = " ON CONFLICT DO UPDATE SET " . implode(",", $updateValues);
} else*/ {
$strategy = get_class($onDuplicateKey);
$this->lastError = "ON DUPLICATE Strategy $strategy is not supported yet.";
return false;
} else {
return "";
}
}
}
$returningCol = $insert->getReturning();
$returning = $returningCol ? (" RETURNING " . $this->columnName($returningCol)) : "";
protected function getReturning(?string $columns) {
return $columns ? (" RETURNING " . $this->columnName($columns)) : "";
}
$query = "INSERT INTO $tableName$columnStr VALUES$values$onDuplicateKey$returning";
$res = $this->execute($query, $parameters, !empty($returning));
$success = ($res !== FALSE);
if($success && !empty($returning)) {
$this->lastInsertId = $res[0][$returningCol];
}
return $success;
protected function fetchReturning($res, string $returningCol) {
$this->lastInsertId = $res[0][$returningCol];
}
// UGLY but.. what should i do?
private function createEnum($enumColumn) {
private function createEnum(EnumColumn $enumColumn) {
$typeName = $enumColumn->getName();
if(!endsWith($typeName, "_type")) {
$typeName = "${typeName}_type";
@ -319,5 +294,27 @@ class PostgreSQL extends SQL {
public function currentTimestamp() {
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 {
private $tableName;
private $columns;
private $constraints;
private $ifNotExists;
private string $tableName;
private array $columns;
private array $constraints;
private bool $ifNotExists;
public function __construct($sql, $name) {
parent::__construct($sql);
@ -92,6 +92,4 @@ class CreateTable extends Query {
public function getTableName() { return $this->tableName; }
public function getColumns() { return $this->columns; }
public function getConstraints() { return $this->constraints; }
};
?>
}

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

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

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

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

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

@ -2,16 +2,37 @@
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\PrimaryKey;
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 {
protected $lastError;
protected string $lastError;
protected $connection;
protected $connectionData;
protected $lastInsertId;
protected ConnectionData $connectionData;
protected int $lastInsertId;
public function __construct($connectionData) {
$this->connection = NULL;
@ -29,27 +50,27 @@ abstract class SQL {
}
public function createTable($tableName) {
return new Query\CreateTable($this, $tableName);
return new CreateTable($this, $tableName);
}
public function insert($tableName, $columns=array()) {
return new Query\Insert($this, $tableName, $columns);
return new Insert($this, $tableName, $columns);
}
public function select(...$columNames) {
return new Query\Select($this, $columNames);
return new Select($this, $columNames);
}
public function truncate($table) {
return new Query\Truncate($this, $table);
return new Truncate($this, $table);
}
public function delete($table) {
return new Query\Delete($this, $table);
return new Delete($this, $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();
// 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());
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
@ -89,73 +157,94 @@ abstract class SQL {
return $this->execute($query);
}
// TODO pull this function up
public abstract function executeInsert($query);
public function executeInsert(Insert $insert) {
public function executeSelect($select) {
$tableName = $this->tableName($insert->getTableName());
$columns = $insert->getColumns();
$rows = $insert->getRows();
$columns = $this->columnName($select->getColumns());
$tables = $select->getTables();
$params = array();
if (!$tables) {
return "SELECT $columns";
if (empty($rows)) {
$this->lastError = "No rows to insert given.";
return false;
}
$tables = $this->tableName($tables);
$where = $this->getWhereClause($select->getConditions(), $params);
if (is_null($columns) || empty($columns)) {
$columnStr = "";
} else {
$columnStr = " (" . $this->columnName($columns) . ")";
}
$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";
$parameters = array();
$values = array();
foreach($rows as $row) {
$rowPlaceHolder = array();
foreach($row as $val) {
$rowPlaceHolder[] = $this->addValue($val, $parameters);
}
$values[] = "(" . implode(",", $rowPlaceHolder) . ")";
}
$orderBy = "";
$orderColumns = $select->getOrderBy();
if (!empty($orderColumns)) {
$orderBy = " ORDER BY " . $this->columnName($orderColumns);
$orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC");
$values = implode(",", $values);
$onDuplicateKey = $this->getOnDuplicateStrategy($insert->onDuplicateKey(), $parameters);
if ($onDuplicateKey === FALSE) {
return false;
}
$limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : "");
$offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : "");
$query = "SELECT $columns FROM $tables$joinStr$where$orderBy$limit$offset";
$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();
$query = $this->buildQuery($select, $params);
if($select->dump) { var_dump($query); var_dump($params); }
return $this->execute($query, $params, true);
}
public function executeDelete($delete) {
public function executeDelete(Delete $delete) {
$params = array();
$table = $this->tableName($delete->getTable());
$where = $this->getWhereClause($delete->getConditions(), $params);
$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);
}
public function executeTruncate($truncate) {
return $this->execute("TRUNCATE " . $truncate->getTable());
}
public function executeUpdate($update) {
public function executeUpdate(Update $update) {
$params = array();
$table = $this->tableName($update->getTable());
$valueStr = array();
foreach($update->getValues() as $key => $val) {
$valueStr[] = "$key=" . $this->addValue($val, $params);
$valueStr[] = $this->columnName($key) . "=" . $this->addValue($val, $params);
}
$valueStr = implode(",", $valueStr);
$where = $this->getWhereClause($update->getConditions(), $params);
$query = "UPDATE $table SET $valueStr$where";
if($update->dump) { var_dump($query); var_dump($params); }
return $this->execute($query, $params);
}
@ -167,10 +256,8 @@ abstract class SQL {
}
}
protected abstract function getColumnDefinition($column);
public function getConstraintDefinition($constraint) {
$columnName = $this->columnName($constraint->getColumnName());
public function getConstraintDefinition(Constraint $constraint) {
$columnName = $this->columnName($constraint->getColumnNames());
if ($constraint instanceof PrimaryKey) {
return "PRIMARY KEY ($columnName)";
} else if ($constraint instanceof Unique) {
@ -190,10 +277,19 @@ abstract class SQL {
return $code;
} 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 addValue($val, &$params);
@ -201,17 +297,25 @@ abstract class SQL {
protected abstract function columnName($col);
// Special Keywords and functions
public function now() { return $this->currentTimestamp(); }
public abstract function currentTimestamp();
public function count($col = NULL) {
if (is_null($col)) {
return new Keyword("COUNT(*) AS count");
} else {
$countCol = strtolower(str_replace(".","_", $col)) . "_count";
$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) {
$col = $this->columnName($col);
return new Keyword("DISTINCT($col)");
@ -221,29 +325,67 @@ abstract class SQL {
protected abstract function execute($query, $values=NULL, $returnValues=false);
protected function buildCondition($condition, &$params) {
if ($condition instanceof \Driver\SQL\Condition\CondOr) {
if ($condition instanceof CondOr) {
$conditions = array();
foreach($condition->getConditions() as $cond) {
$conditions[] = $this->buildCondition($cond, $params);
}
return "(" . implode(" OR ", $conditions) . ")";
} else if ($condition instanceof \Driver\SQL\Condition\Compare) {
} else if ($condition instanceof Compare) {
$column = $this->columnName($condition->getColumn());
$value = $condition->getValue();
$operator = $condition->getOperator();
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());
} else if (is_array($condition)) {
if (count($condition) == 1) {
if (count($condition) === 1) {
return $this->buildCondition($condition[0], $params);
} else {
$conditions = array();
foreach($condition as $cond) {
foreach ($condition as $cond) {
$conditions[] = $this->buildCondition($cond, $params);
}
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;
}
public static function createConnection($connectionData) {
public static function createConnection(ConnectionData $connectionData) {
$type = $connectionData->getProperty("type");
if ($type === "mysql") {
$sql = new MySQL($connectionData);
@ -279,6 +421,10 @@ abstract class 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() {
}
};
?>
}

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

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

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

@ -4,13 +4,17 @@ namespace Driver\SQL\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;
}
public function getValues() { return $this->values; }
};
public function getConflictingColumns() {
return $this->conflictingColumns;
}
?>
public function getValues() { return $this->values; }
}

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

@ -2,18 +2,22 @@
namespace Elements;
use Objects\User;
abstract class Document {
protected $head;
protected $body;
protected $user;
protected $databaseRequired;
protected Head $head;
protected Body $body;
protected User $user;
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->body = new $bodyClass($this);
$this->user = $user;
$this->databaseRequired = true;
$this->activeView = $view;
}
public function getHead() { return $this->head; }
@ -21,36 +25,14 @@ abstract class Document {
public function getSQL() { return $this->user->getSQL(); }
public function getUser() { return $this->user; }
protected function sendHeaders() {
header("X-Frame-Options: DENY");
}
public function getView() : ?View {
public static function createSearchableDocument($documentClass, $user) {
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');
$file = getClassPath($this->activeView);
if(!file_exists($file) || !is_subclass_of($this->activeView, View::class)) {
return null;
}
$user = new CUser($sql);
$document = new $class($user);
$code = $document->getCode();
$document->sendHeaders();
$user->sendCookies();
die($code);
return new $this->activeView($this);
}
function getCode() {
@ -66,15 +48,14 @@ abstract class Document {
$body = $this->body->getCode();
$head = $this->head->getCode();
$lang = $this->user->getLanguage()->getShortCode();
$html = "<!DOCTYPE html>";
$html .= "<html>";
$html .= "<html lang=\"$lang\">";
$html .= $head;
$html .= $body;
$html .= "</html>";
return $html;
}
};
?>
}

@ -2,19 +2,20 @@
namespace Elements;
abstract class Head extends \View {
abstract class Head extends View {
protected $sources;
protected $title;
protected $metas;
protected $rawFields;
protected $keywords;
protected $description;
protected $baseUrl;
protected array $sources;
protected string $title;
protected array $metas;
protected array $rawFields;
protected array $keywords;
protected string $description;
protected string $baseUrl;
function __construct($document) {
parent::__construct($document);
$this->sources = array();
$this->searchable = false;
$this->metas = $this->initMetas();
$this->rawFields = $this->initRawFields();
$this->title = $this->initTitle();
@ -45,7 +46,7 @@ abstract class Head extends \View {
public function addKeywords($keywords) { array_merge($this->keywords, $keywords); }
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 addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); }
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);
}
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) {
$this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey");
}
@ -80,11 +68,6 @@ abstract class Head extends \View {
$this->addJS(Script::BOOTSTRAP);
}
public function loadChartJS() {
$this->addJS(Script::MOMENT);
$this->addJS(Script::CHART);
}
public function getCode() {
$header = "<head>";
@ -123,4 +106,3 @@ abstract class Head extends \View {
return $header;
}
}
?>

@ -2,41 +2,28 @@
namespace Elements;
class Link extends Source {
class Link extends StaticView {
const STYLESHEET = "stylesheet";
const MIME_TEXT_CSS = "text/css";
const FONTAWESOME = '/css/fontawesome.min.css';
// const JQUERY_UI = '/css/jquery-ui.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 ADMIN = "/css/admin.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";
const FONTAWESOME = "/css/fontawesome.min.css";
const BOOTSTRAP = "/css/bootstrap.min.css";
const CORE = "/css/style.css";
const ACCOUNT = "/css/account.css";
private $type;
private $rel;
private string $type;
private string $rel;
private string $href;
function __construct($rel, $href, $type = "") {
parent::__construct('link', $href);
$this->href = $href;
$this->type = $type;
$this->rel = $rel;
}
function getCode() {
$type = (empty($this->type) ? "" : " type=\"$this->type\"");
$link = "<link rel=\"$this->rel\" href=\"$this->url\" $type/>";
return $link;
return "<link rel=\"$this->rel\" href=\"$this->href\"$type/>";
}
}
?>

@ -2,52 +2,28 @@
namespace Elements;
class Script extends Source {
class Script extends StaticView {
const MIME_TEXT_JAVASCRIPT = "text/javascript";
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_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 CORE = "/js/script.js";
const JQUERY = "/js/jquery.min.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 $type;
private $content;
private string $type;
private string $content;
private string $src;
function __construct($type, $src, $content = "") {
parent::__construct('script', $src);
$this->src = $src;
$this->type = $type;
$this->content = $content;
}
function getCode() {
$src = (empty($this->url) ? "" : " src=\"$this->url\"");
$script = "<script type=\"$this->type\"$src>";
$script .= $this->content;
$script .= '</script>';
return $script;
$src = (empty($this->src) ? "" : " src=\"$this->src\"");
return "<script type=\"$this->type\"$src>$this->content</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;
class Style extends Source {
class Style extends StaticView {
private $style;
private string $style;
function __construct($style) {
parent::__construct('style', '');
$this->style = $style;
}
@ -15,5 +14,3 @@ class Style extends Source {
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 function __construct() { }
public function __toString() { return json_encode($this); }
}
?>

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

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

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