1.5.2: html functions, DB Row Iterator, and more
This commit is contained in:
parent
bce59c5f92
commit
d8605597f6
@ -8,8 +8,7 @@ export default function Sidebar(props) {
|
|||||||
showDialog: props.showDialog || function() {},
|
showDialog: props.showDialog || function() {},
|
||||||
api: props.api,
|
api: props.api,
|
||||||
notifications: props.notifications || [ ],
|
notifications: props.notifications || [ ],
|
||||||
contactRequests: props.contactRequests || [ ],
|
contactRequests: props.contactRequests || [ ]
|
||||||
filesPath: props.filesPath || null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function onLogout() {
|
function onLogout() {
|
||||||
@ -86,16 +85,6 @@ export default function Sidebar(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filePath = parent.filesPath;
|
|
||||||
if (filePath) {
|
|
||||||
li.push(<li className={"nav-item"} key={"files"}>
|
|
||||||
<a href={filePath} className={"nav-link"} target={"_blank"} rel={"noopener"}>
|
|
||||||
<Icon icon={"folder"} className={"nav-icon"} />
|
|
||||||
<p>Files</p>
|
|
||||||
</a>
|
|
||||||
</li>);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.push(<li className={"nav-item"} key={"logout"}>
|
li.push(<li className={"nav-item"} key={"logout"}>
|
||||||
<a href={"#"} onClick={() => onLogout()} className={"nav-link"}>
|
<a href={"#"} onClick={() => onLogout()} className={"nav-link"}>
|
||||||
<Icon icon={"arrow-left"} className={"nav-icon"} />
|
<Icon icon={"arrow-left"} className={"nav-icon"} />
|
||||||
|
@ -32,8 +32,7 @@ class AdminDashboard extends React.Component {
|
|||||||
loaded: false,
|
loaded: false,
|
||||||
dialog: { onClose: () => this.hideDialog() },
|
dialog: { onClose: () => this.hideDialog() },
|
||||||
notifications: [ ],
|
notifications: [ ],
|
||||||
contactRequests: [ ],
|
contactRequests: [ ]
|
||||||
filesPath: null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,35 +70,12 @@ class AdminDashboard extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchFilesPath() {
|
|
||||||
this.api.getRoutes().then((res) => {
|
|
||||||
if (!res.success) {
|
|
||||||
this.showDialog("Error fetching routes: " + res.msg, "Error fetching routes");
|
|
||||||
} else {
|
|
||||||
for (const route of res.routes) {
|
|
||||||
if (route.target === "\\Documents\\Files") {
|
|
||||||
// prepare the path patterns, e.g. '/files(/.*)?' => '/files'
|
|
||||||
let path = route.request;
|
|
||||||
path = path.replace(/\(.*\)([?*])/g, ''); // remove optional and 0-n groups
|
|
||||||
path = path.replace(/.\*/g, ''); // remove .*
|
|
||||||
path = path.replace(/\[.*]\*/g, ''); // remove []*
|
|
||||||
path = path.replace(/(.*)\+/g, "$1"); // replace 1-n groups with one match
|
|
||||||
// todo: add some more rules, but we should have most of the cases now
|
|
||||||
this.setState({...this.state, filesPath: path });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.api.fetchUser().then(Success => {
|
this.api.fetchUser().then(Success => {
|
||||||
if (!Success) {
|
if (!Success) {
|
||||||
document.location = "/admin";
|
document.location = "/admin";
|
||||||
} else {
|
} else {
|
||||||
this.fetchNotifications();
|
this.fetchNotifications();
|
||||||
this.fetchFilesPath();
|
|
||||||
this.fetchContactRequests();
|
this.fetchContactRequests();
|
||||||
setInterval(this.onUpdate.bind(this), 60*1000);
|
setInterval(this.onUpdate.bind(this), 60*1000);
|
||||||
this.setState({...this.state, loaded: true});
|
this.setState({...this.state, loaded: true});
|
||||||
@ -121,7 +97,7 @@ class AdminDashboard extends React.Component {
|
|||||||
|
|
||||||
return <Router>
|
return <Router>
|
||||||
<Header {...this.controlObj} notifications={this.state.notifications} />
|
<Header {...this.controlObj} notifications={this.state.notifications} />
|
||||||
<Sidebar {...this.controlObj} notifications={this.state.notifications} contactRequests={this.state.contactRequests} filesPath={this.state.filesPath} />
|
<Sidebar {...this.controlObj} notifications={this.state.notifications} contactRequests={this.state.contactRequests}/>
|
||||||
<div className={"content-wrapper p-2"}>
|
<div className={"content-wrapper p-2"}>
|
||||||
<section className={"content"}>
|
<section className={"content"}>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -183,7 +183,7 @@ abstract class Request {
|
|||||||
$authHeader = $_SERVER["HTTP_AUTHORIZATION"];
|
$authHeader = $_SERVER["HTTP_AUTHORIZATION"];
|
||||||
if (startsWith($authHeader, "Bearer ")) {
|
if (startsWith($authHeader, "Bearer ")) {
|
||||||
$apiKey = substr($authHeader, strlen("Bearer "));
|
$apiKey = substr($authHeader, strlen("Bearer "));
|
||||||
$apiKeyAuthorized = $this->user->authorize($apiKey);
|
$apiKeyAuthorized = $this->user->loadApiKey($apiKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ class Settings {
|
|||||||
private string $recaptchaPrivateKey;
|
private string $recaptchaPrivateKey;
|
||||||
private string $mailSender;
|
private string $mailSender;
|
||||||
private string $mailFooter;
|
private string $mailFooter;
|
||||||
|
private array $allowedExtensions;
|
||||||
|
|
||||||
public function getJwtSecret(): string {
|
public function getJwtSecret(): string {
|
||||||
return $this->jwtSecret;
|
return $this->jwtSecret;
|
||||||
@ -51,6 +52,7 @@ class Settings {
|
|||||||
$settings->mailEnabled = false;
|
$settings->mailEnabled = false;
|
||||||
$settings->mailSender = "webmaster@localhost";
|
$settings->mailSender = "webmaster@localhost";
|
||||||
$settings->mailFooter = "";
|
$settings->mailFooter = "";
|
||||||
|
$settings->allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'htm', 'html'];
|
||||||
|
|
||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
@ -72,6 +74,7 @@ class Settings {
|
|||||||
$this->mailEnabled = $result["mail_enabled"] ?? $this->mailEnabled;
|
$this->mailEnabled = $result["mail_enabled"] ?? $this->mailEnabled;
|
||||||
$this->mailSender = $result["mail_from"] ?? $this->mailSender;
|
$this->mailSender = $result["mail_from"] ?? $this->mailSender;
|
||||||
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
|
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
|
||||||
|
$this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions)));
|
||||||
|
|
||||||
if (!isset($result["jwt_secret"])) {
|
if (!isset($result["jwt_secret"])) {
|
||||||
$req = new \Api\Settings\Set($user);
|
$req = new \Api\Settings\Set($user);
|
||||||
@ -92,7 +95,8 @@ class Settings {
|
|||||||
->addRow("jwt_secret", $this->jwtSecret, true, true)
|
->addRow("jwt_secret", $this->jwtSecret, true, true)
|
||||||
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
||||||
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
||||||
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false);
|
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
||||||
|
->addRow("allowed_extensions", implode(",", $this->allowedExtensions), true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSiteName(): string {
|
public function getSiteName(): string {
|
||||||
@ -126,4 +130,8 @@ class Settings {
|
|||||||
public function getMailSender(): string {
|
public function getMailSender(): string {
|
||||||
return $this->mailSender;
|
return $this->mailSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isExtensionAllowed(string $ext): bool {
|
||||||
|
return empty($this->allowedExtensions) || in_array(strtolower(trim($ext)), $this->allowedExtensions);
|
||||||
|
}
|
||||||
}
|
}
|
@ -46,7 +46,7 @@ class MySQL extends SQL {
|
|||||||
// Connection Management
|
// Connection Management
|
||||||
public function connect() {
|
public function connect() {
|
||||||
|
|
||||||
if(!is_null($this->connection)) {
|
if (!is_null($this->connection)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class MySQL extends SQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function disconnect() {
|
public function disconnect() {
|
||||||
if(is_null($this->connection)) {
|
if (is_null($this->connection)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +88,9 @@ class MySQL extends SQL {
|
|||||||
|
|
||||||
private function getPreparedParams($values): array {
|
private function getPreparedParams($values): array {
|
||||||
$sqlParams = array('');
|
$sqlParams = array('');
|
||||||
foreach($values as $value) {
|
foreach ($values as $value) {
|
||||||
$paramType = Parameter::parseType($value);
|
$paramType = Parameter::parseType($value);
|
||||||
switch($paramType) {
|
switch ($paramType) {
|
||||||
case Parameter::TYPE_BOOLEAN:
|
case Parameter::TYPE_BOOLEAN:
|
||||||
$value = $value ? 1 : 0;
|
$value = $value ? 1 : 0;
|
||||||
$sqlParams[0] .= 'i';
|
$sqlParams[0] .= 'i';
|
||||||
@ -134,6 +134,9 @@ class MySQL extends SQL {
|
|||||||
return $sqlParams;
|
return $sqlParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
||||||
|
|
||||||
$result = null;
|
$result = null;
|
||||||
@ -218,7 +221,7 @@ class MySQL extends SQL {
|
|||||||
return "";
|
return "";
|
||||||
} else if ($strategy instanceof UpdateStrategy) {
|
} else if ($strategy instanceof UpdateStrategy) {
|
||||||
$updateValues = array();
|
$updateValues = array();
|
||||||
foreach($strategy->getValues() as $key => $value) {
|
foreach ($strategy->getValues() as $key => $value) {
|
||||||
$leftColumn = $this->columnName($key);
|
$leftColumn = $this->columnName($key);
|
||||||
if ($value instanceof Column) {
|
if ($value instanceof Column) {
|
||||||
$columnName = $this->columnName($value->getName());
|
$columnName = $this->columnName($value->getName());
|
||||||
@ -253,16 +256,16 @@ class MySQL extends SQL {
|
|||||||
} else {
|
} else {
|
||||||
return "TEXT";
|
return "TEXT";
|
||||||
}
|
}
|
||||||
} else if($column instanceof SerialColumn) {
|
} else if ($column instanceof SerialColumn) {
|
||||||
return "INTEGER AUTO_INCREMENT";
|
return "INTEGER AUTO_INCREMENT";
|
||||||
} else if($column instanceof IntColumn) {
|
} else if ($column instanceof IntColumn) {
|
||||||
$unsigned = $column->isUnsigned() ? " UNSIGNED" : "";
|
$unsigned = $column->isUnsigned() ? " UNSIGNED" : "";
|
||||||
return $column->getType() . $unsigned;
|
return $column->getType() . $unsigned;
|
||||||
} else if($column instanceof DateTimeColumn) {
|
} else if ($column instanceof DateTimeColumn) {
|
||||||
return "DATETIME";
|
return "DATETIME";
|
||||||
} else if($column instanceof BoolColumn) {
|
} else if ($column instanceof BoolColumn) {
|
||||||
return "BOOLEAN";
|
return "BOOLEAN";
|
||||||
} else if($column instanceof JsonColumn) {
|
} else if ($column instanceof JsonColumn) {
|
||||||
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
||||||
} else {
|
} else {
|
||||||
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
||||||
@ -275,7 +278,7 @@ class MySQL extends SQL {
|
|||||||
$defaultValue = $column->getDefaultValue();
|
$defaultValue = $column->getDefaultValue();
|
||||||
if ($column instanceof EnumColumn) { // check this, shouldn't it be in getColumnType?
|
if ($column instanceof EnumColumn) { // check this, shouldn't it be in getColumnType?
|
||||||
$values = array();
|
$values = array();
|
||||||
foreach($column->getValues() as $value) {
|
foreach ($column->getValues() as $value) {
|
||||||
$values[] = $this->getValueDefinition($value);
|
$values[] = $this->getValueDefinition($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,11 +308,11 @@ class MySQL extends SQL {
|
|||||||
public function getValueDefinition($value) {
|
public function getValueDefinition($value) {
|
||||||
if (is_numeric($value)) {
|
if (is_numeric($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
} else if(is_bool($value)) {
|
} else if (is_bool($value)) {
|
||||||
return $value ? "TRUE" : "FALSE";
|
return $value ? "TRUE" : "FALSE";
|
||||||
} else if(is_null($value)) {
|
} else if (is_null($value)) {
|
||||||
return "NULL";
|
return "NULL";
|
||||||
} else if($value instanceof Keyword) {
|
} else if ($value instanceof Keyword) {
|
||||||
return $value->getValue();
|
return $value->getValue();
|
||||||
} else if ($value instanceof CurrentTimeStamp) {
|
} else if ($value instanceof CurrentTimeStamp) {
|
||||||
return "CURRENT_TIMESTAMP";
|
return "CURRENT_TIMESTAMP";
|
||||||
@ -341,7 +344,7 @@ class MySQL extends SQL {
|
|||||||
public function tableName($table): string {
|
public function tableName($table): string {
|
||||||
if (is_array($table)) {
|
if (is_array($table)) {
|
||||||
$tables = array();
|
$tables = array();
|
||||||
foreach($table as $t) $tables[] = $this->tableName($t);
|
foreach ($table as $t) $tables[] = $this->tableName($t);
|
||||||
return implode(",", $tables);
|
return implode(",", $tables);
|
||||||
} else {
|
} else {
|
||||||
$parts = explode(" ", $table);
|
$parts = explode(" ", $table);
|
||||||
@ -357,16 +360,16 @@ class MySQL extends SQL {
|
|||||||
public function columnName($col): string {
|
public function columnName($col): string {
|
||||||
if ($col instanceof Keyword) {
|
if ($col instanceof Keyword) {
|
||||||
return $col->getValue();
|
return $col->getValue();
|
||||||
} elseif(is_array($col)) {
|
} elseif (is_array($col)) {
|
||||||
$columns = array();
|
$columns = array();
|
||||||
foreach($col as $c) $columns[] = $this->columnName($c);
|
foreach ($col as $c) $columns[] = $this->columnName($c);
|
||||||
return implode(",", $columns);
|
return implode(",", $columns);
|
||||||
} else {
|
} else {
|
||||||
if (($index = strrpos($col, ".")) !== FALSE) {
|
if (($index = strrpos($col, ".")) !== FALSE) {
|
||||||
$tableName = $this->tableName(substr($col, 0, $index));
|
$tableName = $this->tableName(substr($col, 0, $index));
|
||||||
$columnName = $this->columnName(substr($col, $index + 1));
|
$columnName = $this->columnName(substr($col, $index + 1));
|
||||||
return "$tableName.$columnName";
|
return "$tableName.$columnName";
|
||||||
} else if(($index = stripos($col, " as ")) !== FALSE) {
|
} else if (($index = stripos($col, " as ")) !== FALSE) {
|
||||||
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
||||||
$alias = trim(substr($col, $index + 4));
|
$alias = trim(substr($col, $index + 4));
|
||||||
return "$columnName as $alias";
|
return "$columnName as $alias";
|
||||||
|
@ -92,6 +92,9 @@ class PostgreSQL extends SQL {
|
|||||||
return $lastError;
|
return $lastError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
||||||
|
|
||||||
$this->lastError = "";
|
$this->lastError = "";
|
||||||
|
@ -109,6 +109,9 @@ class Select extends Query {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function execute() {
|
public function execute() {
|
||||||
return $this->sql->executeQuery($this, $this->fetchType);
|
return $this->sql->executeQuery($this, $this->fetchType);
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,11 @@ abstract class SQL {
|
|||||||
public abstract function connect();
|
public abstract function connect();
|
||||||
public abstract function disconnect();
|
public abstract function disconnect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query $query
|
||||||
|
* @param int $fetchType
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
|
public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
|
||||||
|
|
||||||
$parameters = [];
|
$parameters = [];
|
||||||
@ -242,6 +247,9 @@ abstract class SQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Statements
|
// Statements
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
protected abstract function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE);
|
protected abstract function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE);
|
||||||
|
|
||||||
public function buildCondition($condition, &$params) {
|
public function buildCondition($condition, &$params) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Elements;
|
namespace Elements;
|
||||||
|
|
||||||
use Configuration\Settings;
|
use Configuration\Settings;
|
||||||
|
use Driver\Logger\Logger;
|
||||||
use Driver\SQL\SQL;
|
use Driver\SQL\SQL;
|
||||||
use Objects\Router\Router;
|
use Objects\Router\Router;
|
||||||
use Objects\User;
|
use Objects\User;
|
||||||
@ -10,6 +11,7 @@ use Objects\User;
|
|||||||
abstract class Document {
|
abstract class Document {
|
||||||
|
|
||||||
protected Router $router;
|
protected Router $router;
|
||||||
|
private Logger $logger;
|
||||||
protected bool $databaseRequired;
|
protected bool $databaseRequired;
|
||||||
private bool $cspEnabled;
|
private bool $cspEnabled;
|
||||||
private ?string $cspNonce;
|
private ?string $cspNonce;
|
||||||
@ -23,6 +25,11 @@ abstract class Document {
|
|||||||
$this->databaseRequired = true;
|
$this->databaseRequired = true;
|
||||||
$this->cspWhitelist = [];
|
$this->cspWhitelist = [];
|
||||||
$this->domain = $this->getSettings()->getBaseUrl();
|
$this->domain = $this->getSettings()->getBaseUrl();
|
||||||
|
$this->logger = new Logger("Document", $this->getSQL());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLogger(): Logger {
|
||||||
|
return $this->logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUser(): User {
|
public function getUser(): User {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Elements\Body;
|
|
||||||
|
|
||||||
class EmptyBody extends Body {
|
|
||||||
|
|
||||||
}
|
|
@ -69,40 +69,42 @@ abstract class Head extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getCode(): string {
|
public function getCode(): string {
|
||||||
$header = "<head>";
|
$content = [];
|
||||||
|
|
||||||
foreach($this->metas as $aMeta) {
|
// meta tags
|
||||||
$header .= '<meta';
|
foreach($this->metas as $meta) {
|
||||||
foreach($aMeta as $key => $val) {
|
$content[] = html_tag_short("meta", $meta);
|
||||||
$header .= " $key=\"$val\"";
|
|
||||||
}
|
|
||||||
$header .= ' />';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// description
|
||||||
if(!empty($this->description)) {
|
if(!empty($this->description)) {
|
||||||
$header .= "<meta name=\"description\" content=\"$this->description\" />";
|
$content[] = html_tag_short("meta", ["name" => "description", "content" => $this->description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keywords
|
||||||
if(!empty($this->keywords)) {
|
if(!empty($this->keywords)) {
|
||||||
$keywords = implode(", ", $this->keywords);
|
$keywords = implode(", ", $this->keywords);
|
||||||
$header .= "<meta name=\"keywords\" content=\"$keywords\" />";
|
$content[] = html_tag_short("meta", ["name" => "keywords", "content" => $keywords]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// base tag
|
||||||
if(!empty($this->baseUrl)) {
|
if(!empty($this->baseUrl)) {
|
||||||
$header .= "<base href=\"$this->baseUrl\">";
|
$content[] = html_tag_short("base", ["href" => $this->baseUrl]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$header .= "<title>$this->title</title>";
|
// title
|
||||||
|
$content[] = html_tag("title", [], $this->title);
|
||||||
|
|
||||||
|
// src tags
|
||||||
foreach($this->sources as $src) {
|
foreach($this->sources as $src) {
|
||||||
$header .= $src->getCode();
|
$content[] = $src->getCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($this->rawFields as $raw) {
|
//
|
||||||
$header .= $raw;
|
foreach ($this->rawFields as $raw) {
|
||||||
|
$content[] = $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
$header .= "</head>";
|
return html_tag("head", [], $content, false);
|
||||||
return $header;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class HtmlDocument extends Document {
|
|||||||
|
|
||||||
$view = parseClass($this->activeView);
|
$view = parseClass($this->activeView);
|
||||||
$file = getClassPath($view);
|
$file = getClassPath($view);
|
||||||
if(!file_exists($file) || !is_subclass_of($view, View::class)) {
|
if (!file_exists($file) || !is_subclass_of($view, View::class)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,12 +67,10 @@ class HtmlDocument extends Document {
|
|||||||
$head = $this->head->getCode();
|
$head = $this->head->getCode();
|
||||||
$lang = $this->getUser()->getLanguage()->getShortCode();
|
$lang = $this->getUser()->getLanguage()->getShortCode();
|
||||||
|
|
||||||
$html = "<!DOCTYPE html>";
|
$code = "<!DOCTYPE html>";
|
||||||
$html .= "<html lang=\"$lang\">";
|
$code .= html_tag("html", ["lang" => $lang], $head . $body, false);
|
||||||
$html .= $head;
|
|
||||||
$html .= $body;
|
return $code;
|
||||||
$html .= "</html>";
|
|
||||||
return $html;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,8 +34,7 @@ class Link extends StaticView {
|
|||||||
$attributes["nonce"] = $this->nonce;
|
$attributes["nonce"] = $this->nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes = html_attributes($attributes);
|
return html_tag_short("link", $attributes);
|
||||||
return "<link $attributes/>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setNonce(string $nonce) {
|
public function setNonce(string $nonce) {
|
||||||
|
@ -35,8 +35,8 @@ class Script extends StaticView {
|
|||||||
$attributes["nonce"] = $this->nonce;
|
$attributes["nonce"] = $this->nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes = html_attributes($attributes);
|
// TODO: do we need to escape the content here?
|
||||||
return "<script $attributes>$this->content</script>";
|
return html_tag("script", $attributes, $this->content, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setNonce(string $nonce) {
|
public function setNonce(string $nonce) {
|
||||||
|
@ -10,7 +10,7 @@ abstract class SimpleBody extends Body {
|
|||||||
|
|
||||||
public function getCode(): string {
|
public function getCode(): string {
|
||||||
$content = $this->getContent();
|
$content = $this->getContent();
|
||||||
return parent::getCode() . "<body>$content</body>";
|
return html_tag("body", [], $content, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract function getContent(): string;
|
protected abstract function getContent(): string;
|
||||||
|
@ -11,6 +11,7 @@ class Style extends StaticView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCode(): string {
|
function getCode(): string {
|
||||||
return "<style>$this->style</style>";
|
// TODO: do we need to escape the content here?
|
||||||
|
return html_tag("style", [], $this->style, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ abstract class View extends StaticView {
|
|||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
} catch(\ReflectionException $e) {
|
} catch(\ReflectionException $e) {
|
||||||
error_log($e->getMessage());
|
$this->document->getLogger()->error("Error loading view: '$viewClass': " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
@ -44,7 +44,7 @@ abstract class View extends StaticView {
|
|||||||
|
|
||||||
private function loadLanguageModules() {
|
private function loadLanguageModules() {
|
||||||
$lang = $this->document->getUser()->getLanguage();
|
$lang = $this->document->getUser()->getLanguage();
|
||||||
foreach($this->langModules as $langModule) {
|
foreach ($this->langModules as $langModule) {
|
||||||
$lang->loadModule($langModule);
|
$lang->loadModule($langModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ abstract class View extends StaticView {
|
|||||||
// Load translations
|
// Load translations
|
||||||
$this->loadLanguageModules();
|
$this->loadLanguageModules();
|
||||||
|
|
||||||
// Load Meta Data + Head (title, scripts, includes, ...)
|
// Load metadata + head (title, scripts, includes, ...)
|
||||||
if($this->loadView) {
|
if($this->loadView) {
|
||||||
$this->loadView();
|
$this->loadView();
|
||||||
}
|
}
|
||||||
@ -66,46 +66,46 @@ abstract class View extends StaticView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UI Functions
|
// UI Functions
|
||||||
private function createList($items, $tag, $classes = ""): string {
|
private function createList(array $items, string $tag, array $classes = []): string {
|
||||||
|
|
||||||
$class = ($classes ? " class=\"$classes\"" : "");
|
$attributes = [];
|
||||||
|
if (!empty($classes)) {
|
||||||
if(count($items) === 0) {
|
$attributes["class"] = implode(" ", $classes);
|
||||||
return "<$tag$class></$tag>";
|
|
||||||
} else {
|
|
||||||
return "<$tag$class><li>" . implode("</li><li>", $items) . "</li></$tag>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createOrderedList($items=array(), $classes = ""): string {
|
$content = array_map(function ($item) { html_tag("li", [], $item, false); }, $items);
|
||||||
|
return html_tag_ex($tag, $attributes, $content, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createOrderedList(array $items=[], array $classes=[]): string {
|
||||||
return $this->createList($items, "ol", $classes);
|
return $this->createList($items, "ol", $classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createUnorderedList($items=array(), $classes = ""): string {
|
public function createUnorderedList(array $items=[], array $classes=[]): string {
|
||||||
return $this->createList($items, "ul", $classes);
|
return $this->createList($items, "ul", $classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createLink($link, $title=null, $classes=""): string {
|
protected function createLink(string $link, $title=null, array $classes=[], bool $escapeTitle=true): string {
|
||||||
if(is_null($title)) $title=$link;
|
$attrs = ["href" => $link];
|
||||||
if(!empty($classes)) $classes = " class=\"$classes\"";
|
if (!empty($classes)) {
|
||||||
return "<a href=\"$link\"$classes>$title</a>";
|
$attrs["class"] = implode(" ", $classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createExternalLink($link, $title=null): string {
|
return html_tag("a", $attrs, $title ?? $link, $escapeTitle);
|
||||||
if(is_null($title)) $title=$link;
|
|
||||||
return "<a href=\"$link\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external\">$title</a>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createIcon($icon, $type = "fas", $classes = ""): string {
|
protected function createExternalLink(string $link, $title=null, bool $escapeTitle=true): string {
|
||||||
$iconClass = "$type fa-$icon";
|
$attrs = ["href" => $link, "target" => "_blank", "rel" => "noopener noreferrer", "class" => "external"];
|
||||||
|
return html_tag("a", $attrs, $title ?? $link, $escapeTitle);
|
||||||
|
}
|
||||||
|
|
||||||
if($icon === "spinner" || $icon === "circle-notch")
|
protected function createIcon($icon, $type="fas", $classes = []): string {
|
||||||
$iconClass .= " fa-spin";
|
$classes = array_merge($classes, [$type, "fa-$icon"]);
|
||||||
|
if ($icon === "spinner" || $icon === "circle-notch") {
|
||||||
|
$classes[] = "fa-spin";
|
||||||
|
}
|
||||||
|
|
||||||
if($classes)
|
return html_tag("i", ["class" => implode(" ", $classes)]);
|
||||||
$iconClass .= " $classes";
|
|
||||||
|
|
||||||
return "<i class=\"$iconClass\" ></i>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createErrorText($text, $id="", $hidden=false): string {
|
protected function createErrorText($text, $id="", $hidden=false): string {
|
||||||
@ -128,87 +128,23 @@ abstract class View extends StaticView {
|
|||||||
return $this->createStatusText("info", $text, $id, $hidden);
|
return $this->createStatusText("info", $text, $id, $hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createStatusText($type, $text, $id="", $hidden=false, $classes=""): string {
|
protected function createStatusText(string $type, $text, string $id="", bool $hidden=false, array $classes=[]): string {
|
||||||
if(strlen($id) > 0) $id = " id=\"$id\"";
|
$classes[] = "alert";
|
||||||
if($hidden) $classes .= " hidden";
|
$classes[] = "alert-$type";
|
||||||
if(strlen($classes) > 0) $classes = " $classes";
|
|
||||||
return "<div class=\"alert alert-$type$hidden$classes\" role=\"alert\"$id>$text</div>";
|
if ($hidden) {
|
||||||
|
$classes[] = "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createBadge($type, $text): string {
|
$attributes = [
|
||||||
$text = htmlspecialchars($text);
|
"class" => implode(" ", $classes),
|
||||||
return "<span class=\"badge badge-$type\">$text</span>";
|
"role" => "alert"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($id)) {
|
||||||
|
$attributes["id"] = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createJumbotron(string $content, bool $fluid=false, $class=""): string {
|
return html_tag("div", $attributes, $text, false);
|
||||||
$jumbotronClass = "jumbotron" . ($fluid ? " jumbotron-fluid" : "");
|
|
||||||
if (!empty($class)) $jumbotronClass .= " $class";
|
|
||||||
|
|
||||||
return
|
|
||||||
"<div class=\"$jumbotronClass\">
|
|
||||||
$content
|
|
||||||
</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createSimpleParagraph(string $content, string $class=""): string {
|
|
||||||
if($class) $class = " class=\"$class\"";
|
|
||||||
return "<p$class>$content</p>";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createParagraph($title, $id, $content): string {
|
|
||||||
$id = replaceCssSelector($id);
|
|
||||||
$iconId = urlencode("$id-icon");
|
|
||||||
return "
|
|
||||||
<div class=\"row mt-4\">
|
|
||||||
<div class=\"col-12\">
|
|
||||||
<h2 id=\"$id\" data-target=\"$iconId\" class=\"inlineLink\">$title</h2>
|
|
||||||
<hr/>
|
|
||||||
$content
|
|
||||||
</div>
|
|
||||||
</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createBootstrapTable($data, string $classes=""): string {
|
|
||||||
$classes = empty($classes) ? "" : " $classes";
|
|
||||||
$code = "<div class=\"container$classes\">";
|
|
||||||
foreach($data as $row) {
|
|
||||||
$code .= "<div class=\"row mt-2 mb-2\">";
|
|
||||||
$columnCount = count($row);
|
|
||||||
if($columnCount > 0) {
|
|
||||||
$remainingSize = 12;
|
|
||||||
$columnSize = 12 / $columnCount;
|
|
||||||
foreach($row as $col) {
|
|
||||||
$size = ($columnSize <= $remainingSize ? $columnSize : $remainingSize);
|
|
||||||
$content = $col;
|
|
||||||
$class = "";
|
|
||||||
$code .= "<div";
|
|
||||||
|
|
||||||
if(is_array($col)) {
|
|
||||||
$content = "";
|
|
||||||
foreach($col as $key => $val) {
|
|
||||||
if(strcmp($key, "content") === 0) {
|
|
||||||
$content = $val;
|
|
||||||
} else if(strcmp($key, "class") === 0) {
|
|
||||||
$class = " " . $col["class"];
|
|
||||||
} else if(strcmp($key, "cols") === 0 && is_numeric($val)) {
|
|
||||||
$size = intval($val);
|
|
||||||
} else {
|
|
||||||
$code .= " $key=\"$val\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isset($col["class"])) $class = " " . $col["class"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($size <= 6) $class .= " col-md-" . intval($size * 2);
|
|
||||||
$code .= " class=\"col-lg-$size$class\">$content</div>";
|
|
||||||
$remainingSize -= $size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$code .= "</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
$code .= "</div>";
|
|
||||||
return $code;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,22 @@ use Objects\User;
|
|||||||
|
|
||||||
class Router {
|
class Router {
|
||||||
|
|
||||||
private User $user;
|
private ?User $user;
|
||||||
private Logger $logger;
|
private Logger $logger;
|
||||||
protected array $routes;
|
protected array $routes;
|
||||||
protected array $statusCodeRoutes;
|
protected array $statusCodeRoutes;
|
||||||
|
|
||||||
public function __construct(User $user) {
|
public function __construct(?User $user = null) {
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
$this->logger = new Logger("Router", $user->getSQL());
|
|
||||||
$this->routes = [];
|
$this->routes = [];
|
||||||
$this->statusCodeRoutes = [];
|
$this->statusCodeRoutes = [];
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
$this->addRoute(new ApiRoute());
|
$this->addRoute(new ApiRoute());
|
||||||
|
$this->logger = new Logger("Router", $user->getSQL());
|
||||||
|
} else {
|
||||||
|
$this->logger = new Logger("Router");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(string $url): string {
|
public function run(string $url): string {
|
||||||
|
@ -15,10 +15,61 @@ class StaticFileRoute extends AbstractRoute {
|
|||||||
|
|
||||||
public function call(Router $router, array $params): string {
|
public function call(Router $router, array $params): string {
|
||||||
http_response_code($this->code);
|
http_response_code($this->code);
|
||||||
return serveStatic(WEBROOT, $this->path);
|
$this->serveStatic($this->path, $router);
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getArgs(): array {
|
protected function getArgs(): array {
|
||||||
return array_merge(parent::getArgs(), [$this->path, $this->code]);
|
return array_merge(parent::getArgs(), [$this->path, $this->code]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function serveStatic(string $path, ?Router $router = null) {
|
||||||
|
|
||||||
|
$path = realpath(WEBROOT . DIRECTORY_SEPARATOR . $path);
|
||||||
|
if (!startsWith($path, WEBROOT . DIRECTORY_SEPARATOR)) {
|
||||||
|
http_response_code(406);
|
||||||
|
echo "<b>Access restricted, requested file outside web root:</b> " . htmlspecialchars($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($path) || !is_file($path) || !is_readable($path)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "<b>Unable to read file:</b> " . htmlspecialchars($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathInfo = pathinfo($path);
|
||||||
|
if ($router !== null && ($user = $router->getUser()) !== null) {
|
||||||
|
$ext = $pathInfo["extension"] ?? "";
|
||||||
|
if (!$user->getConfiguration()->getSettings()->isExtensionAllowed($ext)) {
|
||||||
|
http_response_code(406);
|
||||||
|
echo "<b>Access restricted:</b> Extension '" . htmlspecialchars($ext) . "' not allowed to serve.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = filesize($path);
|
||||||
|
$mimeType = mime_content_type($path);
|
||||||
|
header("Content-Type: $mimeType");
|
||||||
|
header("Content-Length: $size");
|
||||||
|
header('Accept-Ranges: bytes');
|
||||||
|
|
||||||
|
if (strcasecmp($_SERVER["REQUEST_METHOD"], "HEAD") !== 0) {
|
||||||
|
$handle = fopen($path, "rb");
|
||||||
|
if ($handle === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "<b>Unable to read file:</b> " . htmlspecialchars($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$length = $size;
|
||||||
|
|
||||||
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
|
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
|
||||||
|
$offset = intval($matches[1]);
|
||||||
|
$length = intval($matches[2]) - $offset;
|
||||||
|
http_response_code(206);
|
||||||
|
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadFile($handle, $offset, $length);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -101,19 +101,20 @@ class Session extends ApiObject {
|
|||||||
$sql = $this->user->getSQL();
|
$sql = $this->user->getSQL();
|
||||||
|
|
||||||
$minutes = Session::DURATION;
|
$minutes = Session::DURATION;
|
||||||
$columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in", "csrf_token");
|
$data = [
|
||||||
|
"expires" => (new DateTime())->modify("+$minutes minute"),
|
||||||
|
"user_id" => $this->user->getId(),
|
||||||
|
"ipAddress" => $this->ipAddress,
|
||||||
|
"os" => $this->os,
|
||||||
|
"browser" => $this->browser,
|
||||||
|
"data" => json_encode($_SESSION ?? []),
|
||||||
|
"stay_logged_in" => $stayLoggedIn,
|
||||||
|
"csrf_token" => $this->csrfToken
|
||||||
|
];
|
||||||
|
|
||||||
$success = $sql
|
$success = $sql
|
||||||
->insert("Session", $columns)
|
->insert("Session", array_keys($data))
|
||||||
->addRow(
|
->addRow(...array_values($data))
|
||||||
(new DateTime())->modify("+$minutes minute"),
|
|
||||||
$this->user->getId(),
|
|
||||||
$this->ipAddress,
|
|
||||||
$this->os,
|
|
||||||
$this->browser,
|
|
||||||
json_encode($_SESSION ?? []),
|
|
||||||
$stayLoggedIn,
|
|
||||||
$this->csrfToken)
|
|
||||||
->returning("uid")
|
->returning("uid")
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ class Session extends ApiObject {
|
|||||||
->set("Session.ipAddress", $this->ipAddress)
|
->set("Session.ipAddress", $this->ipAddress)
|
||||||
->set("Session.os", $this->os)
|
->set("Session.os", $this->os)
|
||||||
->set("Session.browser", $this->browser)
|
->set("Session.browser", $this->browser)
|
||||||
->set("Session.data", json_encode($_SESSION))
|
->set("Session.data", json_encode($_SESSION ?? []))
|
||||||
->set("Session.csrf_token", $this->csrfToken)
|
->set("Session.csrf_token", $this->csrfToken)
|
||||||
->where(new Compare("Session.uid", $this->sessionId))
|
->where(new Compare("Session.uid", $this->sessionId))
|
||||||
->where(new Compare("Session.user_id", $this->user->getId()))
|
->where(new Compare("Session.user_id", $this->user->getId()))
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
namespace Objects;
|
namespace Objects;
|
||||||
|
|
||||||
use Configuration\Configuration;
|
use Configuration\Configuration;
|
||||||
|
use Driver\SQL\Condition\CondAnd;
|
||||||
use Exception;
|
use Exception;
|
||||||
use External\JWT;
|
use External\JWT;
|
||||||
use Driver\SQL\SQL;
|
use Driver\SQL\SQL;
|
||||||
use Driver\SQL\Condition\Compare;
|
use Driver\SQL\Condition\Compare;
|
||||||
use Driver\SQL\Condition\CondBool;
|
|
||||||
use Objects\TwoFactor\TwoFactorToken;
|
use Objects\TwoFactor\TwoFactorToken;
|
||||||
|
|
||||||
// TODO: User::authorize and User::readData have similar function body
|
|
||||||
class User extends ApiObject {
|
class User extends ApiObject {
|
||||||
|
|
||||||
private ?SQL $sql;
|
private ?SQL $sql;
|
||||||
@ -40,7 +39,7 @@ class User extends ApiObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
if($this->sql && $this->sql->isConnected()) {
|
if ($this->sql && $this->sql->isConnected()) {
|
||||||
$this->sql->close();
|
$this->sql->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,21 +60,66 @@ class User extends ApiObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): int { return $this->uid; }
|
public function getId(): int {
|
||||||
public function isLoggedIn(): bool { return $this->loggedIn; }
|
return $this->uid;
|
||||||
public function getUsername(): string { return $this->username; }
|
}
|
||||||
public function getFullName(): string { return $this->fullName; }
|
|
||||||
public function getEmail(): ?string { return $this->email; }
|
public function isLoggedIn(): bool {
|
||||||
public function getSQL(): ?SQL { return $this->sql; }
|
return $this->loggedIn;
|
||||||
public function getLanguage(): Language { return $this->language; }
|
}
|
||||||
public function setLanguage(Language $language) { $this->language = $language; $language->load(); }
|
|
||||||
public function getSession(): ?Session { return $this->session; }
|
public function getUsername(): string {
|
||||||
public function getConfiguration(): Configuration { return $this->configuration; }
|
return $this->username;
|
||||||
public function getGroups(): array { return $this->groups; }
|
}
|
||||||
public function hasGroup(int $group): bool { return isset($this->groups[$group]); }
|
|
||||||
public function getGPG(): ?GpgKey { return $this->gpgKey; }
|
public function getFullName(): string {
|
||||||
public function getTwoFactorToken(): ?TwoFactorToken { return $this->twoFactorToken; }
|
return $this->fullName;
|
||||||
public function getProfilePicture() : ?string { return $this->profilePicture; }
|
}
|
||||||
|
|
||||||
|
public function getEmail(): ?string {
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSQL(): ?SQL {
|
||||||
|
return $this->sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLanguage(): Language {
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLanguage(Language $language) {
|
||||||
|
$this->language = $language;
|
||||||
|
$language->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSession(): ?Session {
|
||||||
|
return $this->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfiguration(): Configuration {
|
||||||
|
return $this->configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroups(): array {
|
||||||
|
return $this->groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasGroup(int $group): bool {
|
||||||
|
return isset($this->groups[$group]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGPG(): ?GpgKey {
|
||||||
|
return $this->gpgKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTwoFactorToken(): ?TwoFactorToken {
|
||||||
|
return $this->twoFactorToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfilePicture(): ?string {
|
||||||
|
return $this->profilePicture;
|
||||||
|
}
|
||||||
|
|
||||||
public function __debugInfo(): array {
|
public function __debugInfo(): array {
|
||||||
$debugInfo = array(
|
$debugInfo = array(
|
||||||
@ -83,7 +127,7 @@ class User extends ApiObject {
|
|||||||
'language' => $this->language->getName(),
|
'language' => $this->language->getName(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if($this->loggedIn) {
|
if ($this->loggedIn) {
|
||||||
$debugInfo['uid'] = $this->uid;
|
$debugInfo['uid'] = $this->uid;
|
||||||
$debugInfo['username'] = $this->username;
|
$debugInfo['username'] = $this->username;
|
||||||
}
|
}
|
||||||
@ -135,7 +179,7 @@ class User extends ApiObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function updateLanguage($lang): bool {
|
public function updateLanguage($lang): bool {
|
||||||
if($this->sql) {
|
if ($this->sql) {
|
||||||
$request = new \Api\Language\Set($this);
|
$request = new \Api\Language\Set($this);
|
||||||
return $request->execute(array("langCode" => $lang));
|
return $request->execute(array("langCode" => $lang));
|
||||||
} else {
|
} else {
|
||||||
@ -162,68 +206,25 @@ class User extends ApiObject {
|
|||||||
* @param bool $sessionUpdate update session information, including session's lifetime and browser information
|
* @param bool $sessionUpdate update session information, including session's lifetime and browser information
|
||||||
* @return bool true, if the data could be loaded
|
* @return bool true, if the data could be loaded
|
||||||
*/
|
*/
|
||||||
public function readData($userId, $sessionId, bool $sessionUpdate = true): bool {
|
public function loadSession($userId, $sessionId, bool $sessionUpdate = true): bool {
|
||||||
|
|
||||||
$res = $this->sql->select("User.name", "User.email", "User.fullName",
|
$userRow = $this->loadUser("Session", ["Session.data", "Session.stay_logged_in", "Session.csrf_token"], [
|
||||||
"User.profilePicture",
|
new Compare("User.uid", $userId),
|
||||||
"User.gpg_id", "GpgKey.confirmed as gpg_confirmed", "GpgKey.fingerprint as gpg_fingerprint",
|
new Compare("Session.uid", $sessionId),
|
||||||
"GpgKey.expires as gpg_expires", "GpgKey.algorithm as gpg_algorithm",
|
new Compare("Session.active", true),
|
||||||
"User.2fa_id", "2FA.confirmed as 2fa_confirmed", "2FA.data as 2fa_data", "2FA.type as 2fa_type",
|
]);
|
||||||
"Language.uid as langId", "Language.code as langCode", "Language.name as langName",
|
|
||||||
"Session.data", "Session.stay_logged_in", "Session.csrf_token", "Group.uid as groupId", "Group.name as groupName")
|
|
||||||
->from("User")
|
|
||||||
->innerJoin("Session", "Session.user_id", "User.uid")
|
|
||||||
->leftJoin("Language", "User.language_id", "Language.uid")
|
|
||||||
->leftJoin("UserGroup", "UserGroup.user_id", "User.uid")
|
|
||||||
->leftJoin("Group", "UserGroup.group_id", "Group.uid")
|
|
||||||
->leftJoin("GpgKey", "User.gpg_id", "GpgKey.uid")
|
|
||||||
->leftJoin("2FA", "User.2fa_id", "2FA.uid")
|
|
||||||
->where(new Compare("User.uid", $userId))
|
|
||||||
->where(new Compare("Session.uid", $sessionId))
|
|
||||||
->where(new Compare("Session.active", true))
|
|
||||||
->where(new CondBool("Session.stay_logged_in"), new Compare("Session.expires", $this->sql->currentTimestamp(), '>'))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$success = ($res !== FALSE);
|
if ($userRow !== false) {
|
||||||
if($success) {
|
$this->session = new Session($this, $sessionId, $userRow["csrf_token"]);
|
||||||
if(empty($res)) {
|
$this->session->setData(json_decode($userRow["data"] ?? '{}', true));
|
||||||
$success = false;
|
$this->session->stayLoggedIn($this->sql->parseBool($userRow["stay_logged_in"]));
|
||||||
} else {
|
if ($sessionUpdate) {
|
||||||
$row = $res[0];
|
$this->session->update();
|
||||||
$csrfToken = $row["csrf_token"];
|
}
|
||||||
$this->username = $row['name'];
|
|
||||||
$this->email = $row["email"];
|
|
||||||
$this->fullName = $row["fullName"];
|
|
||||||
$this->uid = $userId;
|
|
||||||
$this->profilePicture = $row["profilePicture"];
|
|
||||||
|
|
||||||
$this->session = new Session($this, $sessionId, $csrfToken);
|
|
||||||
$this->session->setData(json_decode($row["data"] ?? '{}', true));
|
|
||||||
$this->session->stayLoggedIn($this->sql->parseBool($row["stay_logged_in"]));
|
|
||||||
if ($sessionUpdate) $this->session->update();
|
|
||||||
$this->loggedIn = true;
|
$this->loggedIn = true;
|
||||||
|
|
||||||
if (!empty($row["gpg_id"])) {
|
|
||||||
$this->gpgKey = new GpgKey($row["gpg_id"], $this->sql->parseBool($row["gpg_confirmed"]),
|
|
||||||
$row["gpg_fingerprint"], $row["gpg_algorithm"], $row["gpg_expires"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($row["2fa_id"])) {
|
return $userRow !== false;
|
||||||
$this->twoFactorToken = TwoFactorToken::newInstance($row["2fa_type"], $row["2fa_data"],
|
|
||||||
$row["2fa_id"], $this->sql->parseBool($row["2fa_confirmed"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!is_null($row['langId'])) {
|
|
||||||
$this->setLanguage(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($res as $row) {
|
|
||||||
$this->groups[$row["groupId"]] = $row["groupName"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseCookies() {
|
private function parseCookies() {
|
||||||
@ -232,21 +233,21 @@ class User extends ApiObject {
|
|||||||
$token = $_COOKIE['session'];
|
$token = $_COOKIE['session'];
|
||||||
$settings = $this->configuration->getSettings();
|
$settings = $this->configuration->getSettings();
|
||||||
$decoded = (array)JWT::decode($token, $settings->getJwtSecret());
|
$decoded = (array)JWT::decode($token, $settings->getJwtSecret());
|
||||||
if(!is_null($decoded)) {
|
if (!is_null($decoded)) {
|
||||||
$userId = ($decoded['userId'] ?? NULL);
|
$userId = ($decoded['userId'] ?? NULL);
|
||||||
$sessionId = ($decoded['sessionId'] ?? NULL);
|
$sessionId = ($decoded['sessionId'] ?? NULL);
|
||||||
if(!is_null($userId) && !is_null($sessionId)) {
|
if (!is_null($userId) && !is_null($sessionId)) {
|
||||||
$this->readData($userId, $sessionId);
|
$this->loadSession($userId, $sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(Exception $e) {
|
} catch (Exception $e) {
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_GET['lang']) && is_string($_GET["lang"]) && !empty($_GET["lang"])) {
|
if (isset($_GET['lang']) && is_string($_GET["lang"]) && !empty($_GET["lang"])) {
|
||||||
$this->updateLanguage($_GET['lang']);
|
$this->updateLanguage($_GET['lang']);
|
||||||
} else if(isset($_COOKIE['lang']) && is_string($_COOKIE["lang"]) && !empty($_COOKIE["lang"])) {
|
} else if (isset($_COOKIE['lang']) && is_string($_COOKIE["lang"]) && !empty($_COOKIE["lang"])) {
|
||||||
$this->updateLanguage($_COOKIE['lang']);
|
$this->updateLanguage($_COOKIE['lang']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,69 +263,95 @@ class User extends ApiObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize($apiKey): bool {
|
private function loadUser(string $table, array $columns, array $conditions) {
|
||||||
|
$userRow = $this->sql->select(
|
||||||
|
// User meta
|
||||||
|
"User.uid as userId", "User.name", "User.email", "User.fullName", "User.profilePicture", "User.confirmed",
|
||||||
|
|
||||||
|
// GPG
|
||||||
|
"User.gpg_id", "GpgKey.confirmed as gpg_confirmed", "GpgKey.fingerprint as gpg_fingerprint",
|
||||||
|
"GpgKey.expires as gpg_expires", "GpgKey.algorithm as gpg_algorithm",
|
||||||
|
|
||||||
|
// 2FA
|
||||||
|
"User.2fa_id", "2FA.confirmed as 2fa_confirmed", "2FA.data as 2fa_data", "2FA.type as 2fa_type",
|
||||||
|
|
||||||
|
// Language
|
||||||
|
"Language.uid as langId", "Language.code as langCode", "Language.name as langName",
|
||||||
|
|
||||||
|
// additional data
|
||||||
|
...$columns)
|
||||||
|
->from("User")
|
||||||
|
->innerJoin("$table", "$table.user_id", "User.uid")
|
||||||
|
->leftJoin("Language", "User.language_id", "Language.uid")
|
||||||
|
->leftJoin("GpgKey", "User.gpg_id", "GpgKey.uid")
|
||||||
|
->leftJoin("2FA", "User.2fa_id", "2FA.uid")
|
||||||
|
->where(new CondAnd(...$conditions))
|
||||||
|
->first()
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if ($userRow === null || $userRow === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta data
|
||||||
|
$userId = $userRow["userId"];
|
||||||
|
$this->uid = $userId;
|
||||||
|
$this->username = $userRow['name'];
|
||||||
|
$this->fullName = $userRow["fullName"];
|
||||||
|
$this->email = $userRow['email'];
|
||||||
|
$this->profilePicture = $userRow["profilePicture"];
|
||||||
|
|
||||||
|
// GPG
|
||||||
|
if (!empty($userRow["gpg_id"])) {
|
||||||
|
$this->gpgKey = new GpgKey($userRow["gpg_id"], $this->sql->parseBool($userRow["gpg_confirmed"]),
|
||||||
|
$userRow["gpg_fingerprint"], $userRow["gpg_algorithm"], $userRow["gpg_expires"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2FA
|
||||||
|
if (!empty($userRow["2fa_id"])) {
|
||||||
|
$this->twoFactorToken = TwoFactorToken::newInstance($userRow["2fa_type"], $userRow["2fa_data"],
|
||||||
|
$userRow["2fa_id"], $this->sql->parseBool($userRow["2fa_confirmed"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language
|
||||||
|
if (!is_null($userRow['langId'])) {
|
||||||
|
$this->setLanguage(Language::newInstance($userRow['langId'], $userRow['langCode'], $userRow['langName']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// select groups
|
||||||
|
$groupRows = $this->sql->select("Group.uid as groupId", "Group.name as groupName")
|
||||||
|
->from("UserGroup")
|
||||||
|
->where(new Compare("UserGroup.user_id", $userId))
|
||||||
|
->innerJoin("Group", "UserGroup.group_id", "Group.uid")
|
||||||
|
->execute();
|
||||||
|
if (is_array($groupRows)) {
|
||||||
|
foreach ($groupRows as $row) {
|
||||||
|
$this->groups[$row["groupId"]] = $row["groupName"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $userRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadApiKey($apiKey): bool {
|
||||||
|
|
||||||
if ($this->loggedIn) {
|
if ($this->loggedIn) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $this->sql->select("ApiKey.user_id as uid", "User.name", "User.fullName", "User.email",
|
$userRow = $this->loadUser("ApiKey", [], [
|
||||||
"User.confirmed", "User.profilePicture",
|
new Compare("ApiKey.api_key", $apiKey),
|
||||||
"User.gpg_id", "GpgKey.fingerprint as gpg_fingerprint", "GpgKey.expires as gpg_expires",
|
new Compare("valid_until", $this->sql->currentTimestamp(), ">"),
|
||||||
"GpgKey.confirmed as gpg_confirmed", "GpgKey.algorithm as gpg_algorithm",
|
new Compare("ApiKey.active", 1),
|
||||||
"User.2fa_id", "2FA.confirmed as 2fa_confirmed", "2FA.data as 2fa_data", "2FA.type as 2fa_type",
|
]);
|
||||||
"Language.uid as langId", "Language.code as langCode", "Language.name as langName",
|
|
||||||
"Group.uid as groupId", "Group.name as groupName")
|
|
||||||
->from("ApiKey")
|
|
||||||
->innerJoin("User", "ApiKey.user_id", "User.uid")
|
|
||||||
->leftJoin("UserGroup", "UserGroup.user_id", "User.uid")
|
|
||||||
->leftJoin("Group", "UserGroup.group_id", "Group.uid")
|
|
||||||
->leftJoin("Language", "User.language_id", "Language.uid")
|
|
||||||
->leftJoin("GpgKey", "User.gpg_id", "GpgKey.uid")
|
|
||||||
->leftJoin("2FA", "User.2fa_id", "2FA.uid")
|
|
||||||
->where(new Compare("ApiKey.api_key", $apiKey))
|
|
||||||
->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">"))
|
|
||||||
->where(new Compare("ApiKey.active", 1))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$success = ($res !== FALSE);
|
// User must be confirmed to use API-Keys
|
||||||
if ($success) {
|
if ($userRow === false || !$this->sql->parseBool($userRow["confirmed"])) {
|
||||||
if (empty($res) || !is_array($res)) {
|
|
||||||
$success = false;
|
|
||||||
} else {
|
|
||||||
$row = $res[0];
|
|
||||||
if (!$this->sql->parseBool($row["confirmed"])) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->uid = $row['uid'];
|
return true;
|
||||||
$this->username = $row['name'];
|
|
||||||
$this->fullName = $row["fullName"];
|
|
||||||
$this->email = $row['email'];
|
|
||||||
$this->profilePicture = $row["profilePicture"];
|
|
||||||
|
|
||||||
if (!empty($row["gpg_id"])) {
|
|
||||||
$this->gpgKey = new GpgKey($row["gpg_id"], $this->sql->parseBool($row["gpg_confirmed"]),
|
|
||||||
$row["gpg_fingerprint"], $row["gpg_algorithm"], $row["gpg_expires"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($row["2fa_id"])) {
|
|
||||||
$this->twoFactorToken = TwoFactorToken::newInstance($row["2fa_type"], $row["2fa_data"],
|
|
||||||
$row["2fa_id"], $this->sql->parseBool($row["2fa_confirmed"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!is_null($row['langId'])) {
|
|
||||||
$this->setLanguage(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($res as $row) {
|
|
||||||
$this->groups[$row["groupId"]] = $row["groupName"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processVisit() {
|
public function processVisit() {
|
||||||
@ -340,7 +367,7 @@ class User extends ApiObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function isBot(): bool {
|
private function isBot(): bool {
|
||||||
if (!isset($_SERVER["HTTP_USER_AGENT"]) || empty($_SERVER["HTTP_USER_AGENT"])) {
|
if (empty($_SERVER["HTTP_USER_AGENT"])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@ if (is_file($autoLoad)) {
|
|||||||
require_once $autoLoad;
|
require_once $autoLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
define("WEBBASE_VERSION", "1.5.1");
|
define("WEBBASE_VERSION", "1.5.2");
|
||||||
|
|
||||||
spl_autoload_extensions(".php");
|
spl_autoload_extensions(".php");
|
||||||
spl_autoload_register(function($class) {
|
spl_autoload_register(function ($class) {
|
||||||
if (!class_exists($class)) {
|
if (!class_exists($class)) {
|
||||||
$suffixes = ["", ".class", ".trait"];
|
$suffixes = ["", ".class", ".trait"];
|
||||||
foreach ($suffixes as $suffix) {
|
foreach ($suffixes as $suffix) {
|
||||||
@ -135,7 +135,6 @@ function endsWith($haystack, $needle, bool $ignoreCase = false): bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function contains($haystack, $needle, bool $ignoreCase = false): bool {
|
function contains($haystack, $needle, bool $ignoreCase = false): bool {
|
||||||
|
|
||||||
if (is_array($haystack)) {
|
if (is_array($haystack)) {
|
||||||
@ -191,10 +190,6 @@ function replaceCssSelector($sel) {
|
|||||||
return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel));
|
return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel));
|
||||||
}
|
}
|
||||||
|
|
||||||
function urlId($str) {
|
|
||||||
return urlencode(htmlspecialchars(preg_replace("[: ]","-", $str)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function html_attributes(array $attributes): string {
|
function html_attributes(array $attributes): string {
|
||||||
return implode(" ", array_map(function ($key) use ($attributes) {
|
return implode(" ", array_map(function ($key) use ($attributes) {
|
||||||
$value = htmlspecialchars($attributes[$key]);
|
$value = htmlspecialchars($attributes[$key]);
|
||||||
@ -202,6 +197,31 @@ function html_attributes(array $attributes): string {
|
|||||||
}, array_keys($attributes)));
|
}, array_keys($attributes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function html_tag_short(string $tag, array $attributes = []): string {
|
||||||
|
return html_tag_ex($tag, $attributes, "", true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function html_tag(string $tag, array $attributes = [], $content = "", bool $escapeContent = true): string {
|
||||||
|
return html_tag_ex($tag, $attributes, $content, $escapeContent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function html_tag_ex(string $tag, array $attributes, $content = "", bool $escapeContent = true, bool $short = false): string {
|
||||||
|
$attrs = html_attributes($attributes);
|
||||||
|
if (!empty($attrs)) {
|
||||||
|
$attrs = " " . $attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($content)) {
|
||||||
|
$content = implode("", $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($escapeContent) {
|
||||||
|
$content = htmlspecialchars($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($short && !empty($content)) ? "<$tag$attrs/>" : "<$tag$attrs>$content</$tag>";
|
||||||
|
}
|
||||||
|
|
||||||
function getClassPath($class, string $suffix = ".class"): string {
|
function getClassPath($class, string $suffix = ".class"): string {
|
||||||
$path = str_replace('\\', '/', $class);
|
$path = str_replace('\\', '/', $class);
|
||||||
$path = array_values(array_filter(explode("/", $path)));
|
$path = array_values(array_filter(explode("/", $path)));
|
||||||
@ -229,7 +249,7 @@ function createError($msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile($handle, $offset = 0, $length = null): bool {
|
function downloadFile($handle, $offset = 0, $length = null): bool {
|
||||||
if($handle === false) {
|
if ($handle === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +258,7 @@ function downloadFile($handle, $offset = 0, $length = null): bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$bytesRead = 0;
|
$bytesRead = 0;
|
||||||
$bufferSize = 1024*16;
|
$bufferSize = 1024 * 16;
|
||||||
while (!feof($handle) && ($length === null || $bytesRead < $length)) {
|
while (!feof($handle) && ($length === null || $bytesRead < $length)) {
|
||||||
$chunkSize = ($length === null ? $bufferSize : min($length - $bytesRead, $bufferSize));
|
$chunkSize = ($length === null ? $bufferSize : min($length - $bytesRead, $bufferSize));
|
||||||
echo fread($handle, $chunkSize);
|
echo fread($handle, $chunkSize);
|
||||||
@ -248,59 +268,6 @@ function downloadFile($handle, $offset = 0, $length = null): bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function serveStatic(string $webRoot, string $file): string {
|
|
||||||
|
|
||||||
$path = realpath($webRoot . "/" . $file);
|
|
||||||
if (!startsWith($path, $webRoot . "/")) {
|
|
||||||
http_response_code(406);
|
|
||||||
return "<b>Access restricted, requested file outside web root:</b> " . htmlspecialchars($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file_exists($path) || !is_file($path) || !is_readable($path)) {
|
|
||||||
http_response_code(500);
|
|
||||||
return "<b>Unable to read file:</b> " . htmlspecialchars($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pathInfo = pathinfo($path);
|
|
||||||
|
|
||||||
// TODO: add more file extensions here, probably add them to settings?
|
|
||||||
$allowedExtension = array("html", "htm", "pdf");
|
|
||||||
$ext = $pathInfo["extension"] ?? "";
|
|
||||||
if (!in_array($ext, $allowedExtension)) {
|
|
||||||
http_response_code(406);
|
|
||||||
return "<b>Access restricted:</b> Extension '" . htmlspecialchars($ext) . "' not allowed.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = filesize($path);
|
|
||||||
$mimeType = mime_content_type($path);
|
|
||||||
header("Content-Type: $mimeType"); // TODO: do we need to check mime type?
|
|
||||||
header("Content-Length: $size");
|
|
||||||
header('Accept-Ranges: bytes');
|
|
||||||
|
|
||||||
if (strcasecmp($_SERVER["REQUEST_METHOD"], "HEAD") !== 0) {
|
|
||||||
$handle = fopen($path, "rb");
|
|
||||||
if($handle === false) {
|
|
||||||
http_response_code(500);
|
|
||||||
return "<b>Unable to read file:</b> " . htmlspecialchars($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$offset = 0;
|
|
||||||
$length = $size;
|
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
|
||||||
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
|
|
||||||
$offset = intval($matches[1]);
|
|
||||||
$length = intval($matches[2]) - $offset;
|
|
||||||
http_response_code(206);
|
|
||||||
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFile($handle, $offset, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseClass($class): string {
|
function parseClass($class): string {
|
||||||
if (!startsWith($class, "\\")) {
|
if (!startsWith($class, "\\")) {
|
||||||
$class = "\\$class";
|
$class = "\\$class";
|
||||||
|
@ -8,7 +8,7 @@ define("WEBROOT", realpath("."));
|
|||||||
|
|
||||||
if (is_file("MAINTENANCE") && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
|
if (is_file("MAINTENANCE") && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
|
||||||
http_response_code(503);
|
http_response_code(503);
|
||||||
serveStatic(WEBROOT, "/static/maintenance.html");
|
\Objects\Router\StaticFileRoute::serveStatic("/static/maintenance.html");
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user