v1.2.0 - Merge branch 'dev'

This commit is contained in:
2021-03-31 16:02:40 +02:00
93 changed files with 75651 additions and 15139 deletions

View File

@@ -162,8 +162,8 @@ namespace Api\ApiKey {
$this->loginRequired = true;
}
public function execute($aValues = array()) {
if(!parent::execute($aValues)) {
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}

View File

@@ -53,6 +53,7 @@ namespace Api\Contact {
}
$this->createNotification();
$this->sendMail();
if (!$this->success) {
return $this->createError("The contact request was saved, but the server was unable to create a notification.");
@@ -110,6 +111,17 @@ namespace Api\Contact {
return $this->success;
}
private function sendMail() {
/*$email = $this->getParam("fromEmail");
$settings = $this->user->getConfiguration()->getSettings();
$request = new \Api\Mail\Send($this->user);
$this->success = $request->execute(array(
"to" => $settings->get,
"subject" => "[$siteName] Account Invitation",
"body" => $messageBody
));*/
}
}
}

1096
core/Api/FileAPI.class.php Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
<?php
namespace Api\Parameter;
class ArrayType extends Parameter {
private Parameter $elementParameter;
public int $elementType;
public int $canBeOne;
public function __construct($name, $elementType = Parameter::TYPE_MIXED, $canBeOne=false, $optional = FALSE, $defaultValue = NULL) {
$this->elementType = $elementType;
$this->elementParameter = new Parameter('', $elementType);
$this->canBeOne = $canBeOne;
parent::__construct($name, Parameter::TYPE_ARRAY, $optional, $defaultValue);
}
public function parseParam($value) {
if(!is_array($value)) {
if (!$this->canBeOne) {
return false;
} else {
$value = array($value);
}
}
if ($this->elementType != Parameter::TYPE_MIXED) {
foreach ($value as &$element) {
if ($this->elementParameter->parseParam($element)) {
$element = $this->elementParameter->value;
} else {
return false;
}
}
}
$this->value = $value;
return true;
}
public function getTypeName() {
$elementType = $this->elementParameter->getTypeName();
return parent::getTypeName() . "($elementType)";
}
public function toString() {
$typeName = $this->getTypeName();
$str = "$typeName $this->name";
$defaultValue = (is_null($this->value) ? 'NULL' : (is_array($this->value) ? '[' . implode(",", $this->value) . ']' : $this->value));
if($this->optional) {
$str = "[$str = $defaultValue]";
}
return $str;
}
}

View File

@@ -18,9 +18,11 @@ class Parameter {
const TYPE_RAW = 8;
// only json will work here i guess
// nope. also name[]=value
const TYPE_ARRAY = 9;
const TYPE_MIXED = 10;
const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array');
const names = array('Integer', 'Float', 'Boolean', 'String', 'Date', 'Time', 'DateTime', 'E-Mail', 'Raw', 'Array', 'Mixed');
public string $name;
public $value;

View File

@@ -0,0 +1,65 @@
<?php
namespace Api;
use Api\Parameter\StringType;
use Configuration\DatabaseScript;
use Objects\User;
class PatchSQL extends Request {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"className" => new StringType("className", 64)
));
$this->loginRequired = true;
$this->csrfTokenRequired = false;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$className = $this->getParam("className");
$fullClassName = "\\Configuration\\Patch\\" . $className;
$path = getClassPath($fullClassName, true);
if (!file_exists($path)) {
return $this->createError("File not found");
}
if(!class_exists($fullClassName)) {
return $this->createError("Class not found.");
}
try {
$reflection = new \ReflectionClass($fullClassName);
if (!$reflection->isInstantiable()) {
return $this->createError("Class is not instantiable");
}
if (!$reflection->isSubclassOf(DatabaseScript::class)) {
return $this->createError("Not a database script.");
}
$sql = $this->user->getSQL();
$obj = $reflection->newInstance();
$queries = $obj->createQueries($sql);
if (!is_array($queries)) {
return $this->createError("Database script returned invalid values");
}
foreach($queries as $query) {
if (!$query->execute()) {
return $this->createError("Query error: " . $sql->getLastError());
}
}
$this->success = true;
} catch (\ReflectionException $e) {
return $this->createError("Error reflecting class: " . $e->getMessage());
}
return $this->success;
}
}

View File

@@ -50,7 +50,7 @@ class Request {
foreach($this->params as $name => $param) {
$value = $values[$name] ?? NULL;
$isEmpty = (is_string($value) || is_array($value)) && empty($value);
$isEmpty = (is_string($value) && strlen($value) === 0) || (is_array($value) && empty($value));
if(!$param->optional && (is_null($value) || $isEmpty)) {
return $this->createError("Missing parameter: $name");
}
@@ -187,6 +187,7 @@ class Request {
public function success() { return $this->success; }
public function loginRequired() { return $this->loginRequired; }
public function isExternalCall() { return $this->externalCall; }
public function clearError() { $this->success = true; $this->lastError = ""; }
private function getMethod() {
$class = str_replace("\\", "/", get_class($this));

View File

@@ -39,7 +39,7 @@ namespace Api\Settings {
$query = $sql->select("name", "value") ->from("Settings");
if (!is_null($key) && !empty($key)) {
if (!is_null($key)) {
$query->where(new CondRegex(new Column("name"), $key));
}

View File

@@ -14,9 +14,38 @@ namespace Api\Visitors {
use Api\VisitorsAPI;
use DateTime;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Expression\Add;
use Driver\SQL\Query\Select;
use Driver\SQL\Strategy\UpdateStrategy;
use Objects\User;
class ProcessVisit extends VisitorsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"cookie" => new StringType("cookie")
));
$this->isPublic = false;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$cookie = $this->getParam("cookie");
$day = (new DateTime())->format("Ymd");
$sql->insert("Visitor", array("cookie", "day"))
->addRow($cookie, $day)
->onDuplicateKeyStrategy(new UpdateStrategy(
array("day", "cookie"),
array("count" => new Add("Visitor.count", 1))))
->execute();
return $this->success;
}
}
class Stats extends VisitorsAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(

View File

@@ -6,7 +6,7 @@ use Driver\SQL\SQL;
use \Driver\SQL\Strategy\SetNullStrategy;
use \Driver\SQL\Strategy\CascadeStrategy;
class CreateDatabase {
class CreateDatabase extends DatabaseScript {
// NOTE:
// explicit serial ids removed due to postgres' serial implementation
@@ -192,7 +192,10 @@ class CreateDatabase {
->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");
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics")
->addRow("PatchSQL", array(USER_GROUP_ADMIN), "Allows users to import database patches");
self::loadPatches($queries, $sql);
return $queries;
}
@@ -225,4 +228,22 @@ class CreateDatabase {
"Best Regards<br>" .
"{{site_name}} Administration";
}
private static function loadPatches(&$queries, $sql) {
$patchDirectory = './core/Configuration/Patch/';
if (file_exists($patchDirectory) && is_dir($patchDirectory)) {
$scan_arr = scandir($patchDirectory);
$files_arr = array_diff($scan_arr, array('.','..'));
foreach ($files_arr as $file) {
$suffix = ".class.php";
if (endsWith($file, $suffix)) {
$className = substr($file, 0, strlen($file) - strlen($suffix));
$className = "\\Configuration\\Patch\\$className";
$method = "$className::createQueries";
$patchQueries = call_user_func($method, $sql);
foreach($patchQueries as $query) $queries[] = $query;
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Configuration;
use Driver\SQL\SQL;
abstract class DatabaseScript {
public static abstract function createQueries(SQL $sql);
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Configuration\Patch;
use Configuration\DatabaseScript;
use Driver\SQL\SQL;
use Driver\SQL\Column\Column;
use Driver\SQL\Strategy\CascadeStrategy;
use Driver\SQL\Strategy\UpdateStrategy;
class file_api extends DatabaseScript {
public static function createQueries(SQL $sql) {
$queries = array();
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array("method" => new Column("method"))))
->addRow("File/GetRestrictions", array(), "Allows users to view global upload restrictions")
->addRow("File/Download", array(), "Allows users to download files when logged in, or using a given token")
->addRow("File/Upload", array(), "Allows users to upload files when logged in, or using a given token")
->addRow("File/ValidateToken", array(), "Allows users to validate a given token")
->addRow("File/RevokeToken", array(USER_GROUP_ADMIN), "Allows users to revoke a token")
->addRow("File/ListFiles", array(), "Allows users to list all files assigned to an account")
->addRow("File/ListTokens", array(USER_GROUP_ADMIN), "Allows users to list all tokens assigned to the virtual filesystem of an account")
->addRow("File/CreateDirectory", array(), "Allows users to create a virtual directory")
->addRow("File/Rename", array(), "Allows users to rename files in the virtual filesystem")
->addRow("File/Move", array(), "Allows users to move files in the virtual filesystem")
->addRow("File/Delete", array(), "Allows users to delete files in the virtual filesystem")
->addRow("File/CreateUploadToken", array(USER_GROUP_ADMIN), "Allows users to create a token to upload files to the virtual filesystem assigned to the users account")
->addRow("File/CreateDownloadToken", array(USER_GROUP_ADMIN), "Allows users to create a token to download files from the virtual filesystem assigned to the users account");
$queries[] = $sql->insert("Route", array("request", "action", "target", "extra"))
->onDuplicateKeyStrategy(new UpdateStrategy(array("request"), array("request" => new Column("request"))))
->addRow("^/files(/.*)?$", "dynamic", "\\Documents\\Files", NULL);
$queries[] = $sql->createTable("UserFile")
->onlyIfNotExists()
->addSerial("uid")
->addBool("directory")
->addString("name", 64, false)
->addString("path", 512, true)
->addInt("parent_id", true)
->addInt("user_id")
->primaryKey("uid")
->unique("user_id", "parent_id", "name")
->foreignKey("parent_id", "UserFile", "uid", new CascadeStrategy())
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("UserFileToken")
->onlyIfNotExists()
->addSerial("uid")
->addString("token", 36, false)
->addDateTime("valid_until", true)
->addEnum("token_type", array("download", "upload"))
->addInt("user_id")
# upload only:
->addInt("maxFiles", true)
->addInt("maxSize", true)
->addInt("parent_id", true)
->addString("extensions", 64, true)
->primaryKey("uid")
->foreignKey("user_id", "User", "uid", new CascadeStrategy())
->foreignKey("parent_id", "UserFile", "uid", new CascadeStrategy());
$queries[] = $sql->createTable("UserFileTokenFile")
->addInt("file_id")
->addInt("token_id")
->unique("file_id", "token_id")
->foreignKey("file_id", "UserFile", "uid", new CascadeStrategy())
->foreignKey("token_id", "UserFileToken", "uid", new CascadeStrategy());
return $queries;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Documents {
use Documents\Files\FilesBody;
use Documents\Files\FilesHead;
use Elements\Document;
use Objects\User;
class Files extends Document {
public function __construct(User $user, string $view = NULL) {
parent::__construct($user, FilesHead::class, FilesBody::class, $view);
}
}
}
namespace Documents\Files {
use Elements\Head;
use Elements\Script;
use Elements\SimpleBody;
class FilesHead extends Head {
protected function initSources() {
$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'),
array('name' => 'referrer', 'content' => 'origin')
);
}
protected function initRawFields() {
return array();
}
protected function initTitle() {
return "File Control Panel";
}
}
class FilesBody extends SimpleBody {
public function __construct($document) {
parent::__construct($document);
}
protected function getContent() {
$html = "<noscript>" . $this->createErrorText("Javascript is required for this site to render.") . "</noscript>";
$html .= "<div id=\"root\"></div>";
$html .= new Script(Script::MIME_TEXT_JAVASCRIPT, Script::FILES);
return $html;
}
}
}

View File

@@ -234,7 +234,7 @@ namespace Documents\Install {
$missingInputs[] = "Type";
}
$supportedTypes = array("mysql", "postgres"); # , "oracle", "postgres");
$supportedTypes = array("mysql", "postgres");
if(!$success) {
$msg = "Please fill out the following inputs:<br>" .
$this->createUnorderedList($missingInputs);
@@ -590,7 +590,7 @@ namespace Documents\Install {
"title" => "Database configuration",
"form" => array(
array("title" => "Database Type", "name" => "type", "type" => "select", "required" => true, "items" => array(
"mysql" => "MySQL", "oracle" => "Oracle", "postgres" => "PostgreSQL"
"mysql" => "MySQL", "postgres" => "PostgreSQL"
)),
array("title" => "Username", "name" => "username", "type" => "text", "required" => true),
array("title" => "Password", "name" => "password", "type" => "password"),

View File

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

View File

@@ -8,17 +8,20 @@ class Join {
private string $table;
private string $columnA;
private string $columnB;
private $tableAlias;
public function __construct($type, $table, $columnA, $columnB) {
public function __construct($type, $table, $columnA, $columnB, $tableAlias=null) {
$this->type = $type;
$this->table = $table;
$this->columnA = $columnA;
$this->columnB = $columnB;
$this->tableAlias = $tableAlias;
}
public function getType() { return $this->type; }
public function getTable() { return $this->table; }
public function getColumnA() { return $this->columnA; }
public function getColumnB() { return $this->columnB; }
public function getTableAlias() { return $this->tableAlias; }
}

View File

@@ -4,6 +4,7 @@ namespace Driver\SQL;
use \Api\Parameter\Parameter;
use DateTime;
use \Driver\SQL\Column\Column;
use \Driver\SQL\Column\IntColumn;
use \Driver\SQL\Column\SerialColumn;
@@ -91,15 +92,21 @@ class MySQL extends SQL {
$sqlParams[0] .= 'd';
break;
case Parameter::TYPE_DATE:
$value = $value->format('Y-m-d');
if ($value instanceof DateTime) {
$value = $value->format('Y-m-d');
}
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_TIME:
$value = $value->format('H:i:s');
if ($value instanceof DateTime) {
$value = $value->format('H:i:s');
}
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_DATE_TIME:
$value = $value->format('Y-m-d H:i:s');
if ($value instanceof DateTime) {
$value = $value->format('Y-m-d H:i:s');
}
$sqlParams[0] .= 's';
break;
case Parameter::TYPE_ARRAY:

View File

@@ -0,0 +1,29 @@
<?php
namespace Driver\SQL\Query;
use Driver\SQL\SQL;
class Drop extends Query {
private string $table;
/**
* Drop constructor.
* @param SQL $sql
* @param string $table
*/
public function __construct(\Driver\SQL\SQL $sql, string $table) {
parent::__construct($sql);
$this->table = $table;
}
public function execute() {
$this->sql->executeDrop($this);
}
public function getTable() {
return $this->table;
}
}

View File

@@ -40,13 +40,13 @@ class Select extends Query {
return $this;
}
public function innerJoin($table, $columnA, $columnB) {
$this->joins[] = new Join("INNER", $table, $columnA, $columnB);
public function innerJoin($table, $columnA, $columnB, $tableAlias=null) {
$this->joins[] = new Join("INNER", $table, $columnA, $columnB, $tableAlias);
return $this;
}
public function leftJoin($table, $columnA, $columnB) {
$this->joins[] = new Join("LEFT", $table, $columnA, $columnB);
public function leftJoin($table, $columnA, $columnB, $tableAlias=null) {
$this->joins[] = new Join("LEFT", $table, $columnA, $columnB, $tableAlias);
return $this;
}

View File

@@ -9,6 +9,7 @@ use Driver\SQL\Condition\CondIn;
use Driver\SQL\Condition\Condition;
use Driver\SQL\Condition\CondKeyword;
use Driver\SQL\Condition\CondNot;
use Driver\Sql\Condition\CondNull;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Constraint\Constraint;
use \Driver\SQL\Constraint\Unique;
@@ -16,6 +17,7 @@ use \Driver\SQL\Constraint\PrimaryKey;
use \Driver\SQL\Constraint\ForeignKey;
use Driver\SQL\Query\CreateTable;
use Driver\SQL\Query\Delete;
use Driver\SQL\Query\Drop;
use Driver\SQL\Query\Insert;
use Driver\SQL\Query\Query;
use Driver\SQL\Query\Select;
@@ -73,6 +75,10 @@ abstract class SQL {
return new Update($this, $table);
}
public function drop(string $table) {
return new Drop($this, $table);
}
// ####################
// ### ABSTRACT METHODS
// ####################
@@ -107,7 +113,9 @@ abstract class SQL {
$joinTable = $this->tableName($join->getTable());
$columnA = $this->columnName($join->getColumnA());
$columnB = $this->columnName($join->getColumnB());
$joinStr .= " $type JOIN $joinTable ON $columnA=$columnB";
$tableAlias = ($join->getTableAlias() ? " " . $join->getTableAlias() : "");
$joinStr .= " $type JOIN $joinTable$tableAlias ON $columnA=$columnB";
}
}
@@ -248,6 +256,12 @@ abstract class SQL {
return $this->execute($query, $params);
}
public function executeDrop(Drop $drop) {
$query = "DROP TABLE " . $this->tableName($drop->getTable());
if ($drop->dump) { var_dump($query); }
return $this->execute($query);
}
protected function getWhereClause($conditions, &$params) {
if (!$conditions) {
return "";
@@ -338,6 +352,15 @@ abstract class SQL {
$column = $this->columnName($condition->getColumn());
$value = $condition->getValue();
$operator = $condition->getOperator();
if ($value === null) {
if ($operator === "=") {
return "$column IS NULL";
} else if ($operator === "!=") {
return "$column IS NOT NULL";
}
}
return $column . $operator . $this->addValue($value, $params);
} else if ($condition instanceof CondBool) {
return $this->columnName($condition->getValue());
@@ -385,6 +408,8 @@ abstract class SQL {
}
return "NOT $expression";
} else if($condition instanceof CondNull) {
return $this->columnName($condition->getColumn()) . " IS NULL";
} else {
$this->lastError = "Unsupported condition type: " . get_class($condition);
return false;
@@ -410,9 +435,6 @@ abstract class SQL {
$sql = new MySQL($connectionData);
} else if ($type === "postgres") {
$sql = new PostgreSQL($connectionData);
/*} else if ($type === "oracle") {
// $sql = new OracleSQL($connectionData);
*/
} else {
return "Unknown database type";
}

View File

@@ -0,0 +1,7 @@
<?php
use Elements\Body;
class EmptyBody extends Body {
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Elements;
class EmptyHead extends Head {
public function __construct($document) {
parent::__construct($document);
}
protected function initSources() {
}
protected function initMetas() {
return array(
);
}
protected function initRawFields() {
return array();
}
protected function initTitle() {
return "";
}
}

View File

@@ -11,6 +11,7 @@ class Script extends StaticView {
const INSTALL = "/js/install.js";
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
const ACCOUNT = "/js/account.js";
const FILES = "/js/files.min.js";
private string $type;
private string $content;

View File

@@ -2,8 +2,6 @@
namespace Elements;
use External\PHPMailer\Exception;
abstract class View extends StaticView {
private Document $document;
@@ -81,9 +79,10 @@ abstract class View extends StaticView {
return $this->createList($items, "ul");
}
protected function createLink($link, $title=null) {
protected function createLink($link, $title=null, $classes="") {
if(is_null($title)) $title=$link;
return "<a href=\"$link\">$title</a>";
if(!empty($classes)) $classes = " class=\"$classes\"";
return "<a href=\"$link\"$classes>$title</a>";
}
protected function createExternalLink($link, $title=null) {
@@ -123,14 +122,91 @@ abstract class View extends StaticView {
return $this->createStatusText("info", $text, $id, $hidden);
}
protected function createStatusText($type, $text, $id="", $hidden=false) {
protected function createStatusText($type, $text, $id="", $hidden=false, $classes="") {
if(strlen($id) > 0) $id = " id=\"$id\"";
$hidden = ($hidden?" hidden" : "");
return "<div class=\"alert alert-$type$hidden\" role=\"alert\"$id>$text</div>";
if($hidden) $classes .= " hidden";
if(strlen($classes) > 0) $classes = " $classes";
return "<div class=\"alert alert-$type$hidden$classes\" role=\"alert\"$id>$text</div>";
}
protected function createBadge($type, $text) {
$text = htmlspecialchars($text);
return "<span class=\"badge badge-$type\">$text</span>";
}
}
protected function createJumbotron(string $content, bool $fluid=false, $class="") {
$jumbotronClass = "jumbotron" . ($fluid ? "-fluid" : "");
if (!empty($class)) $jumbotronClass .= " $class";
return "
<div class=\"row\">
<div class=\"col-12\">
<div class=\"$jumbotronClass\">
$content
</div>
</div>
</div>";
}
public function createSimpleParagraph(string $content, string $class="") {
if($class) $class = " class=\"$class\"";
return "<p$class>$content</p>";
}
public function createParagraph($title, $id, $content) {
$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="") {
$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;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Copyright (c) Borago 2019
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
namespace External\ZipStream {
class BufferWriter implements Writer {
private $stream = '';
private $offset = 0;
private $callback = null;
public function __construct() {
}
public function registerCallback($callback) {
$this->callback = $callback;
}
public function write($data) {
$this->offset += strlen($data);
$this->stream .= $data;
if ($this->callback !== null) {
call_user_func($this->callback, $this);
}
return strlen($data);
}
public function read() {
$data = $this->stream;
$this->stream = '';
return $data;
}
public function offset() {
return $this->offset;
}
public function close() {
if ($this->callback !== null) {
call_user_func($this->callback, $this);
}
}
}
}

207
core/External/ZipStream/File.php vendored Normal file
View File

@@ -0,0 +1,207 @@
<?php
/**
* Copyright (c) Borago 2019
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
namespace External\ZipStream {
class File {
private $name;
private $content = '';
private $fileHandle = false;
private $lastModificationTimestamp;
private $crc32 = null;
private $fileSize = 0;
private $compressedSize = 0;
private $offset = 0;
private $bitField = 0;
private $useCompression = true;
private $deflateState = null;
//check for duplications //currently not used
private $sha256;
public const BIT_NO_SIZE_IN_HEADER = 0b0000000000001000;
public const BIT_UTF8_NAMES = 0b0000100000000000;
public function __construct($name) {
$this->name = $name;
$this->lastModificationTimestamp = time();
$this->crc32 = hash('crc32b', '', true);
$this->compressedSize = 0;
$this->bitField = 0;
$this->bitField |= self::BIT_NO_SIZE_IN_HEADER;
$this->bitField |= self::BIT_UTF8_NAMES;
$this->deflateState = deflate_init(ZLIB_ENCODING_RAW, ['level' => 9]);
}
public function setContent($content) {
$this->crc32 = hash('crc32b', $content, true);
$this->sha256 = hash('sha256', $content);
$this->content = $content;
$this->fileSize = strlen($content);
$this->fileHandle = false;
}
public function loadFromFile($filename) {
$this->crc32 = hash_file('crc32b', $filename, true);
$this->sha256 = hash_file('sha256', $filename);
$this->fileSize = filesize($filename);
$this->fileHandle = fopen($filename, 'rb');
}
public function name() {
return $this->name;
}
public function sha256() {
return $this->sha256;
}
private function unixTimeToDosTime($timestamp) {
$hour = intval(date('H', $timestamp));
$min = intval(date('i', $timestamp));
$sec = intval(date('s', $timestamp));
return ($hour << 11) |
($min << 5) |
($sec >> 1);
}
private function unixTimeToDosDate($timestamp) {
$year = intval(date('Y', $timestamp));
$month = intval(date('m', $timestamp));
$day = intval(date('d', $timestamp));
return (($year - 1980) << 9) |
($month << 5) |
($day);
}
public function readLocalFileHeader() {
if (!$this->useCompression) {
$this->compressedSize = $this->fileSize;
}
$header = "";
$header .= "\x50\x4b\x03\x04";
$header .= "\x14\x00"; //version 2.0 and MS-DOS compatible
$header .= pack("v", $this->bitField); //general purpose bit flag
if ($this->useCompression) {
$header .= "\x08\x00"; //compression Method - deflate
} else {
$header .= "\x00\x00"; //compression Method - no
}
$header .= pack("v", $this->unixTimeToDosTime($this->lastModificationTimestamp)); //dos time
$header .= pack("v", $this->unixTimeToDosDate($this->lastModificationTimestamp)); //dos date
if ($this->bitField & self::BIT_NO_SIZE_IN_HEADER) {
$header .= pack("V", 0); //crc32
$header .= pack("V", 0); //compressed Size
$header .= pack("V", 0); //uncompressed Size
} else {
$header .= strrev($this->crc32);
$header .= pack("V", $this->compressedSize); //compressed Size
$header .= pack("V", $this->fileSize); //uncompressed Size
}
$header .= pack("v", strlen($this->name)); //filename
$header .= "\x00\x00"; //extra field length
$header .= $this->name;
return $header;
}
public function readDataDescriptor() {
$data = "";
$data .= "\x50\x4b\x07\x08";
$data .= strrev($this->crc32);
$data .= pack("V", $this->compressedSize); //compressed Size
$data .= pack("V", $this->fileSize); //uncompressed Size
return $data;
}
public function readFileDataImp() {
$ret = null;
if ($this->fileHandle !== false) {
$block = fread($this->fileHandle, 65536);
if (!empty($block)) {
$ret = $block;
}
} else {
$ret = $this->content;
$this->content = null;
}
return $ret;
}
public function readFileData() {
$ret = null;
if ($this->useCompression) {
$block = $this->readFileDataImp();
if ($this->deflateState !== null) {
if ($block !== null) {
$ret = deflate_add($this->deflateState, $block, ZLIB_NO_FLUSH);
} else {
$ret = deflate_add($this->deflateState, '', ZLIB_FINISH);
$this->deflateState = null;
}
}
if ($ret !== null) {
$this->compressedSize += strlen($ret);
}
} else {
$ret = $this->readFileDataImp();
}
return $ret;
}
public function setOffset($offset) {
$this->offset = $offset;
}
public function readCentralDirectoryHeader() {
$header = "";
$header .= "\x50\x4b\x01\x02";
$header .= "\x14\x00"; //version 2.0 and MS-DOS compatible
$header .= "\x14\x00"; //version 2.0 and MS-DOS compatible
$header .= pack("v", $this->bitField); //general purpose bit flag
$header .= "\x00\x00"; //compression Method - no
$header .= pack("v", $this->unixTimeToDosTime($this->lastModificationTimestamp)); //dos time
$header .= pack("v", $this->unixTimeToDosDate($this->lastModificationTimestamp)); //dos date
$header .= strrev($this->crc32);
$header .= pack("V", $this->compressedSize); //compressed Size
$header .= pack("V", $this->fileSize); //uncompressed Size
$header .= pack("v", strlen($this->name)); //filename
$header .= "\x00\x00"; //extra field length
$header .= "\x00\x00"; //comment length
$header .= "\x00\x00"; //disk num start
$header .= "\x00\x00"; //int file attr
$header .= "\x00\x00\x00\x00"; //ext file attr
$header .= pack("V", $this->offset); //relative offset
$header .= $this->name;
return $header;
}
public function closeHandle() {
if ($this->fileHandle) {
fclose($this->fileHandle);
}
}
}
}

63
core/External/ZipStream/FileWriter.php vendored Normal file
View File

@@ -0,0 +1,63 @@
<?php
/**
* Copyright (c) Borago 2019
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
namespace External\ZipStream {
class FileWriter implements Writer {
private $offset = 0;
private $fileHandle = false;
public function __construct($filename) {
if (!empty($filename)) {
$this->open($filename);
}
}
public function __destruct() {
$this->close();
}
public function open($filename) {
$this->close();
$this->fileHandle = fopen($filename, 'wb');
}
public function write($data) {
$this->offset += strlen($data);
if ($this->fileHandle === false) {
throw new \Exception('No file opened.');
} else {
return fwrite($this->fileHandle, $data);
}
}
public function offset() {
return $this->offset;
}
public function close() {
if ($this->fileHandle !== false) {
fclose($this->fileHandle);
}
$this->fileHandle = false;
}
}
}

29
core/External/ZipStream/Writer.php vendored Normal file
View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright (c) Borago 2019
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
namespace External\ZipStream {
interface Writer {
public function write($data);
public function close();
public function offset();
}
}

72
core/External/ZipStream/ZipStream.php vendored Normal file
View File

@@ -0,0 +1,72 @@
<?php
/**
* Copyright (c) Borago 2019
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
namespace External\ZipStream {
class ZipStream {
private $writer = null;
private $files = [];
public function __construct($writer) {
$this->writer = $writer;
}
public function saveFile($file) {
$isSymlink = false; //currently not used
foreach ($this->files as $f) {
if ($f->name() == $file->name()) {
return false;
}
if ($f->sha256() == $file->sha256()) {
$isSymlink = true;
}
}
$file->setOffset($this->writer->offset());
$this->writer->write($file->readLocalFileHeader());
while (($buffer = $file->readFileData()) !== null) {
$this->writer->write($buffer);
}
$this->writer->write($file->readDataDescriptor());
$this->files[] = $file;
$file->closeHandle();
return true;
}
public function close() {
$size = 0;
$offset = $this->writer->offset();
foreach ($this->files as $file) {
$size += $this->writer->write($file->readCentralDirectoryHeader());
}
$data = "";
$data .= "\x50\x4b\x05\x06";
$data .= "\x00\x00"; //number of disks
$data .= "\x00\x00"; //number of the disk with the start of the central directory
$data .= pack("v", count($this->files)); //total number of entries in the central directory on this disk
$data .= pack("v", count($this->files)); //total number of entries in the central directory
$data .= pack("V", $size); //size of the central directory
$data .= pack("V", $offset); //offset of start of central directory with respect to the starting disk number
$data .= "\x0\x0"; //comment length
$this->writer->write($data);
}
}
}

View File

@@ -254,20 +254,13 @@ class User extends ApiObject {
public function processVisit() {
if ($this->sql && $this->sql->isConnected() && isset($_COOKIE["PHPSESSID"]) && !empty($_COOKIE["PHPSESSID"])) {
if ($this->isBot()) {
return;
}
$cookie = $_COOKIE["PHPSESSID"];
$day = (new DateTime())->format("Ymd");
$this->sql->insert("Visitor", array("cookie", "day"))
->addRow($cookie, $day)
->onDuplicateKeyStrategy(new UpdateStrategy(
array("month", "cookie"),
array("count" => new Add("Visitor.count", 1))))
->execute();
$req = new \Api\Visitors\ProcessVisit($this);
$req->execute(array("cookie" => $cookie));
}
}

View File

@@ -1,6 +1,6 @@
<?php
define("WEBBASE_VERSION", "1.0.4");
define("WEBBASE_VERSION", "1.2.0");
function getProtocol() {
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https" : "http";
@@ -75,6 +75,10 @@ function replaceCssSelector($sel) {
return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel));
}
function urlId($str) {
return urlencode(htmlspecialchars(preg_replace("[: ]","-", $str)));
}
function getClassPath($class, $suffix = true) {
$path = str_replace('\\', '/', $class);
$path = array_values(array_filter(explode("/", $path)));
@@ -134,7 +138,6 @@ function serveStatic(string $webRoot, string $file) {
$length = $size;
if (isset($_SERVER['HTTP_RANGE'])) {
$partialContent = true;
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = intval($matches[2]) - $offset;
@@ -166,4 +169,4 @@ function parseClass($class) {
$parts = explode("\\", $class);
$parts = array_map('ucfirst', $parts);
return implode("\\", $parts);
}
}