diff --git a/core/Api/ApiKeyAPI.class.php b/core/Api/ApiKeyAPI.class.php
index a75d5b8..b0ce080 100644
--- a/core/Api/ApiKeyAPI.class.php
+++ b/core/Api/ApiKeyAPI.class.php
@@ -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;
}
diff --git a/core/Api/ContactAPI.class.php b/core/Api/ContactAPI.class.php
index e0b04a0..cb4a4d7 100644
--- a/core/Api/ContactAPI.class.php
+++ b/core/Api/ContactAPI.class.php
@@ -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
+ ));*/
+ }
}
}
\ No newline at end of file
diff --git a/core/Api/FileAPI.class.php b/core/Api/FileAPI.class.php
new file mode 100644
index 0000000..90c2438
--- /dev/null
+++ b/core/Api/FileAPI.class.php
@@ -0,0 +1,737 @@
+user->getSQL();
+ $res = $sql->select("directory")
+ ->from("UserFile")
+ ->where(new Compare("uid", $parentId))
+ ->limit(1)
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ if (empty($res)) {
+ return $this->createError("Parent directory not found");
+ } else if(!$res[0]["directory"]) {
+ return $this->createError("Parent file is not a directory");
+ }
+ }
+
+ return $this->success;
+ }
+
+ protected function downloadFile($name, $path) {
+ if (!file_exists($path)) {
+ http_response_code(404);
+ die("404 - File does not exist anymore");
+ } else if(!is_file($path) || !is_readable($path)) {
+ die("403 - Unable to download file.");
+ } else {
+ $mimeType = @mime_content_type($path);
+ if ($mimeType) {
+ header("Content-Type: $mimeType");
+ }
+
+ $name = trim(preg_replace('/\s\s+/', ' ', $name));
+ header("Content-Disposition: attachment; filename=$name");
+
+ //No cache
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+
+ //Define file size
+ header('Content-Length: ' . filesize($path));
+
+ ob_clean();
+ flush();
+ readfile($path);
+ exit;
+ }
+ }
+
+ protected function isInDirectory($fileId, $directoryId) {
+
+ $sql = $this->user->getSQL();
+
+
+
+ }
+ }
+}
+
+namespace Api\File {
+
+ use Api\FileAPI;
+ use Api\Parameter\Parameter;
+ use Api\Parameter\StringType;
+ use Driver\SQL\Condition\Compare;
+ use Driver\SQL\Condition\CondIn;
+ use Objects\User;
+
+ class ValidateToken extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ 'token' => new StringType('token', 36, false)
+ ));
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $sql = $this->user->getSQL();
+ $token = $this->getParam("token");
+ $res = $sql->select("UserFile.uid", "valid_until", "token_type", "maxFiles", "maxSize", "extensions", "name", "path", "directory")
+ ->from("UserFileToken")
+ ->leftJoin("UserFileTokenFile", "UserFileToken.uid", "token_id")
+ ->innerJoin("UserFile", "UserFile.uid", "file_id")
+ ->where(new Compare("token", $token))
+ ->where(new Compare("valid_until", $sql->now(), ">"))
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ if (empty($res)) {
+ return $this->createError("Invalid Token");
+ } else {
+ $row = $res[0];
+ $this->result["token"] = array(
+ "type" => $row["token_type"],
+ "valid_until" => $row["valid_until"]
+ );
+
+ $this->result["files"] = array();
+ foreach ($res as $row) {
+ $file = array(
+ "isDirectory" => $row["directory"],
+ "name" => $row["name"],
+ "uid" => $row["uid"]
+ );
+
+ if ($file["isDirectory"]) {
+ $file["items"] = array();
+ } else {
+ $file["size"] = @filesize($row["path"]);
+ $file["mimeType"] = @mime_content_type($row["path"]);
+ }
+
+ $this->result["files"][] = $file;
+ }
+
+ if ($row["token_type"] === "upload") {
+ $this->result["restrictions"] = array(
+ "maxFiles" => $row["maxFiles"] ?? 0,
+ "maxSize" => $row["maxSize"] ?? 0,
+ "extensions" => $row["extensions"] ?? ""
+ );
+ }
+ }
+ }
+
+ return $this->success;
+ }
+ }
+
+ class RevokeToken extends FileAPI {
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "token" => new StringType("token", 36)
+ ));
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = true;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $sql = $this->user->getSQL();
+ $token = $this->getParam("token");
+ $res = $sql->select($sql->count())
+ ->from("UserToken")
+ ->where(new Compare("user_id", $this->user->getId()))
+ ->where(new Compare("token", $token))
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ if(empty($res)) {
+ return $this->getParam("Invalid token");
+ } else {
+ $res = $sql->update("UserToken")
+ ->set("valid_until", new \DateTime())
+ ->where(new Compare("user_id", $this->user->getId()))
+ ->where(new Compare("token", $token))
+ ->execute();
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ }
+ }
+
+ return $this->success;
+ }
+ }
+
+ class ListFiles extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "directory" => new Parameter("directory", Parameter::TYPE_INT, true, null)
+ ));
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = false;
+ }
+
+ private function &findDirectory(&$files, $id) {
+
+ if ($id !== null) {
+ $id = (string)$id;
+ if (isset($files[$id])) {
+ return $files[$id]["items"];
+ } else {
+ foreach ($files as &$dir) {
+ if ($dir["isDirectory"]) {
+ $target =& $this->findDirectory($dir["items"], $id);
+ if ($target !== $dir) {
+ return $target;
+ }
+ }
+ }
+ return $files;
+ }
+ } else {
+ return $files;
+ }
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $sql = $this->user->getSQL();
+ $res = $sql->select(
+ "UserFile.uid", "UserFile.directory", "UserFile.path", "UserFile.name",
+ "UserFile.user_id", "parentTable.uid as parentId", "parentTable.name as parentName")
+ ->from("UserFile")
+ ->leftJoin("UserFile", "UserFile.parent_id", "parentTable.uid", "parentTable")
+ ->where(new Compare("UserFile.user_id", $this->user->getId()))
+ ->orderBy("UserFile.parent_id")
+ ->ascending()
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ $files = array();
+
+ foreach ($res as $row) {
+ $fileId = (string)$row["uid"];
+ $parentId = $row["parentId"];
+ $fileName = $row["name"];
+ $isDirectory = $row["directory"];
+ $fileElement = array("uid" => $fileId, "name" => $fileName, "isDirectory" => $isDirectory);
+ if ($isDirectory) {
+ $fileElement["items"] = array();
+ } else {
+ $fileElement["size"] = @filesize($row["path"]);
+ $fileElement["mimeType"] = @mime_content_type($row["path"]);
+ }
+
+ $dir =& $this->findDirectory($files, $parentId);
+ $dir[$fileId] = $fileElement;
+ unset($dir);
+ }
+
+ $directoryId = $this->getParam("directory");
+ if (!is_null($directoryId)) {
+ $wantedDir =& $this->findDirectory($files, $directoryId);
+ if ($files === $wantedDir) {
+ $files = array();
+ } else {
+ $files = $wantedDir;
+ }
+ }
+
+ $this->result["files"] = $files;
+ }
+
+ return $this->success;
+ }
+ }
+
+ class ListTokens extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array());
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $sql = $this->user->getSQL();
+ $res = $sql->select("uid","token","valid_until","token_type", "maxFiles", "maxSize", "extensions")
+ ->from("UserFileToken")
+ ->orderBy("valid_until")
+ ->descending()
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ $this->result["tokens"] = array();
+ foreach ($res as $row) {
+ $tokenType = $row["token_type"];
+ if ($tokenType !== "upload") {
+ unset($row["maxFiles"]);
+ unset($row["maxSize"]);
+ unset($row["extensions"]);
+ }
+ $this->result["tokens"][] = $row;
+ }
+ }
+
+ return $this->success;
+ }
+ }
+
+ class CreateDirectory extends FileAPI {
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ 'name' => new StringType('name', 32),
+ 'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null)
+ ));
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $sql = $this->user->getSQL();
+ $name = $this->getParam('name');
+ $parentId = $this->getParam("parentId");
+ if (!$this->checkDirectory($parentId)) {
+ return $this->success;
+ }
+
+ $res = $sql->insert("UserFile", array("directory", "name", "user_id", "parent_id"))
+ ->addRow(true, $name, $this->user->getId(), $parentId)
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ return $this->success;
+ }
+ }
+
+ class Rename extends FileAPI { }
+
+ class Move extends FileAPI { }
+
+ class Upload extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ 'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null),
+ 'token' => new StringType('token', 36, true, null)
+ ));
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $token = $this->getParam("token");
+ if (!$this->user->isLoggedIn() && is_null($token)) {
+ return $this->createError("Permission denied (expected token)");
+ }
+
+ $sql = $this->user->getSQL();
+ $parentId = $this->getParam("parentId");
+ if (!is_null($token) && !is_null($parentId)) {
+ return $this->createError("Cannot upload to parent directory using token");
+ }
+
+ if (!is_null($parentId) && !$this->checkDirectory($parentId)) {
+ return $this->success;
+ }
+
+ $fileKeys = array_keys($_FILES);
+ $numFilesUploaded = count($fileKeys);
+
+ if (!is_null($token)) {
+
+ $res = $sql->select("uid", "token_type", "maxFiles", "maxSize", "extensions", "user_id")
+ ->from("UserFileToken")
+ ->where(new Compare("token", $token))
+ ->where(new Compare("valid_until", $sql->now(), ">="))
+ ->limit(1)
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ if (!$this->success) {
+ return false;
+ }
+
+ if (empty($res) || $res[0]["token_type"] !== "upload") {
+ return $this->createError("Permission denied (token)");
+ }
+
+ $tokenId = $res[0]["uid"];
+ $maxFiles = $res[0]["maxFiles"] ?? 0;
+ $maxSize = $res[0]["maxSize"] ?? 0;
+ $userId = $res[0]["user_id"];
+ $extensions = explode(",", strtolower($res[0]["extensions"] ?? ""));
+
+ $res = $sql->select($sql->count())
+ ->from("UserFileTokenFile")
+ ->innerJoin("UserFileToken", "token_id", "UserFileToken.uid")
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ if (!$this->success) {
+ return false;
+ }
+
+ $count = $res[0]["count"];
+ if ($maxFiles > 0 && $numFilesUploaded > 0 && $numFilesUploaded + $count > $maxFiles) {
+ return $this->createError("File limit exceeded. Currently uploaded $count / $maxFiles count");
+ }
+
+ if ($maxSize > 0 || !empty($extensions)) {
+ foreach ($_FILES as $file) {
+ $name = $file["name"];
+ if ($maxSize > 0 && $file["size"] > $maxSize) {
+ return $this->createError("File Size limit of $maxSize bytes exceeded for file $name");
+ }
+
+ $dotPos = strrpos($name, ".");
+ $ext = ($dotPos !== false ? strtolower(substr($name, $dotPos + 1)) : false);
+ if (!empty($extensions) && $ext !== false && !in_array($ext, $extensions)) {
+ return $this->createError("File '$name' has prohibited extension. Allowed extensions: " . implode(",", $extensions));
+ }
+ }
+ }
+ } else {
+ $userId = $this->user->getId();
+ }
+
+ if ($numFilesUploaded === 0) {
+ return $this->createError("No file uploaded");
+ }
+
+ $uploadDir = realpath($_SERVER["DOCUMENT_ROOT"] . "/files/uploaded/");
+ if (!is_writable($uploadDir)) {
+ return $this->createError("Upload directory is not writable");
+ }
+
+ $fileIds = array();
+ foreach ($_FILES as $key => $file) {
+ $fileName = $file["name"];
+ $tmpPath = $file["tmp_name"];
+ $md5Hash = hash_file('md5', $tmpPath);
+ $sha1Hash = hash_file('sha1', $tmpPath);
+ $filePath = $uploadDir . "/" . $md5Hash . $sha1Hash;
+ if (move_uploaded_file($tmpPath, $filePath)) {
+ $res = $sql->insert("UserFile", array("name", "directory", "path", "user_id", "parent_id"))
+ ->addRow($fileName, false, $filePath, $userId, $parentId)
+ ->returning("uid")
+ ->execute();
+
+ if ($res === false) {
+ $this->lastError = $sql->getLastError();
+ $this->success = false;
+ return false;
+ } else {
+ $fileIds[] = $sql->getLastInsertId();
+ }
+ } else {
+ return $this->createError("Could not create file: " . $fileName);
+ }
+ }
+
+ if (!is_null($token)) {
+ $query = $sql->insert("UserFileTokenFile", array("file_id", "token_id"));
+ foreach ($fileIds as $fileId) {
+ $query->addRow($fileId, $tokenId);
+ }
+
+ $res = $query->execute();
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ }
+
+ return $this->success;
+ }
+ }
+
+ class Download extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "id" => new Parameter("id", Parameter::TYPE_INT),
+ "token" => new StringType("token", 36, true, null)
+ ));
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $token = $this->getParam("token");
+ if (!$this->user->isLoggedIn() && is_null($token)) {
+ return $this->createError("Permission denied (expected token)");
+ }
+
+ $sql = $this->user->getSQL();
+ $fileId = $this->getParam("id");
+ $query = $sql->select("path", "name")
+ ->from("UserFile")
+ ->where(new Compare("UserFile.uid", $fileId));
+
+ if (is_null($token)) {
+ $query->where(new Compare("user_id", $this->user->getId()));
+ } else {
+ $query->innerJoin("UserFileTokenFile", "UserFile.uid", "file_id")
+ ->innerJoin("UserFileToken", "UserFileToken.uid", "token_id")
+ ->where(new Compare("token", $token));
+ }
+
+ $res = $query->execute();
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ if (empty($res)) {
+ if (is_null($token)) {
+ return $this->createError("File not found");
+ } else {
+ return $this->createError("Permission denied (token)");
+ }
+ } else {
+ if ($res[0]["directory"]) {
+ return $this->createError("Cannot download directory (yet)");
+ } else {
+ $path = $res[0]["path"];
+ $name = $res[0]["name"];
+ $this->downloadFile($name, $path);
+ }
+ }
+ }
+
+ return $this->success;
+ }
+ }
+
+ class Delete extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "id" => new Parameter("id", Parameter::TYPE_INT),
+ "token" => new StringType("token", 36, true, null)
+ ));
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+
+ return $this->success;
+ }
+ }
+
+ class CreateUploadToken extends FileAPI {
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "maxFiles" => new Parameter("maxFiles", Parameter::TYPE_INT, true, 1),
+ "maxSize" => new Parameter("maxSize", Parameter::TYPE_INT, true, null),
+ "extensions" => new StringType("extensions", 64, true, null),
+ "durability" => new Parameter("durability", Parameter::TYPE_INT, true, 60*24*2)
+ ));
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $maxFiles = $this->getParam("maxFiles");
+ $maxSize = $this->getParam("maxSize");
+ $extensions = $this->getParam("extensions");
+ $durability = $this->getParam("durability");
+
+ if (!is_null($maxFiles) && $maxFiles < 0) {
+ return $this->createError("Invalid number of maximum files.");
+ }
+
+ if (!is_null($maxSize) && $maxSize < 0) {
+ return $this->createError("Invalid maximum size for uploaded files.");
+ }
+
+ if (!is_null($durability) && $durability < 0) {
+ return $this->createError("Invalid durability.");
+ }
+
+ if (!is_null($extensions)) {
+ $extensions = explode(",",$extensions);
+ foreach ($extensions as $i => $ext) {
+ if (strlen($ext) === 0 || (strlen($ext) === 1 && $ext[0] === ".")) {
+ unset ($extensions[$i]);
+ } else if ($ext[0] === ".") {
+ $extensions[$i] = substr($ext, 1);
+ }
+ }
+ $extensions = implode(",", $extensions);
+ }
+
+ $sql = $this->user->getSQL();
+ $token = generateRandomString(36);
+ $validUntil = (new \DateTime())->modify("+$durability HOURS");
+
+ $res = $sql->insert("UserFileToken",
+ array("token", "token_type", "maxSize", "maxFiles", "extensions", "valid_until", "user_id"))
+ ->addRow($token, "upload", $maxSize, $maxFiles, $extensions, $validUntil, $this->user->getId())
+ ->returning("uid")
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ $this->result["token"] = $token;
+ $this->result["tokenId"] = $sql->getLastInsertId();
+ }
+
+ return $this->success;
+ }
+ }
+
+ class CreateDownloadToken extends FileAPI {
+
+ public function __construct(User $user, bool $externalCall = false) {
+ parent::__construct($user, $externalCall, array(
+ "durability" => new Parameter("durability", Parameter::TYPE_INT, true, 60*24*2),
+ "files" => new Parameter("files", Parameter::TYPE_ARRAY, false)
+ ));
+ $this->loginRequired = true;
+ $this->csrfTokenRequired = false;
+ }
+
+ public function execute($values = array()) {
+ if (!parent::execute($values)) {
+ return false;
+ }
+
+ $durability = $this->getParam("durability");
+ $fileIds = $this->getParam("files");
+
+ if (!is_null($durability) && $durability < 0) {
+ return $this->createError("Invalid durability.");
+ }
+
+ foreach ($fileIds as $fileId) {
+ if (!is_int($fileId) && is_numeric($fileId)) {
+ $fileId = intval($fileId);
+ }
+ if (!is_int($fileId) || $fileId < 1) {
+ return $this->createError("Invalid file id: $fileId");
+ }
+ }
+
+ $fileIds = array_unique($fileIds);
+
+ // Check for files:
+ $sql = $this->user->getSQL();
+ $res = $sql->select("uid")
+ ->from("UserFile")
+ ->where(new CondIn("uid", $fileIds))
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ if (!$this->success) {
+ return false;
+ }
+
+ if (count($res) !== count($fileIds)) {
+ $foundFiles = array_map(function ($row) { return $row["uid"]; }, $res);
+ foreach ($fileIds as $fileId) {
+ if (!in_array($fileId, $foundFiles)) {
+ return $this->createError("File not found: $fileId");
+ }
+ }
+ }
+
+ // Insert
+ $token = generateRandomString(36);
+ $validUntil = (new \DateTime())->modify("+$durability HOURS");
+ $res = $sql->insert("UserFileToken", array("token_type", "valid_until", "user_id", "token"))
+ ->addRow("download", $validUntil, $this->user->getId(), $token)
+ ->returning("uid")
+ ->execute();
+
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+ if (!$this->success) {
+ return false;
+ }
+
+ $tokenId = $sql->getLastInsertId();
+ $query = $sql->insert("UserFileTokenFile", array("token_id", "file_id"));
+ foreach ($fileIds as $fileId) {
+ $query->addRow($tokenId, $fileId);
+ }
+
+ $res = $query->execute();
+ $this->success = ($res !== false);
+ $this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ $this->result["token"] = $token;
+ }
+
+ return $this->success;
+ }
+ }
+}
diff --git a/core/Api/PatchSQL.class.php b/core/Api/PatchSQL.class.php
new file mode 100644
index 0000000..a985a32
--- /dev/null
+++ b/core/Api/PatchSQL.class.php
@@ -0,0 +1,65 @@
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/core/Api/Request.class.php b/core/Api/Request.class.php
index d6e58dd..9a6e18f 100644
--- a/core/Api/Request.class.php
+++ b/core/Api/Request.class.php
@@ -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));
diff --git a/core/Api/SettingsAPI.class.php b/core/Api/SettingsAPI.class.php
index 3f1b568..cecb991 100644
--- a/core/Api/SettingsAPI.class.php
+++ b/core/Api/SettingsAPI.class.php
@@ -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));
}
diff --git a/core/Configuration/CreateDatabase.class.php b/core/Configuration/CreateDatabase.class.php
index 35be6fb..6567751 100755
--- a/core/Configuration/CreateDatabase.class.php
+++ b/core/Configuration/CreateDatabase.class.php
@@ -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
" .
"{{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;
+ }
+ }
+ }
+ }
}
diff --git a/core/Configuration/DatabaseScript.class.php b/core/Configuration/DatabaseScript.class.php
new file mode 100644
index 0000000..90d3018
--- /dev/null
+++ b/core/Configuration/DatabaseScript.class.php
@@ -0,0 +1,9 @@
+insert("ApiPermission", array("method", "groups", "description"))
+ ->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array("method" => new Column("method"))))
+ ->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->, 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", true)
+ ->primaryKey("uid")
+ ->unique("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)
+ ->addString("extensions", 64, true)
+ ->primaryKey("uid")
+ ->foreignKey("user_id", "User", "uid");
+
+ $queries[] = $sql->createTable("UserFileTokenFile")
+ ->addInt("file_id")
+ ->addInt("token_id")
+ ->unique("file_id", "token_id")
+ ->foreignKey("file_id", "UserFile", "uid")
+ ->foreignKey("token_id", "UserFileToken", "uid");
+
+ return $queries;
+ }
+}
diff --git a/core/Documents/Files.class.php b/core/Documents/Files.class.php
new file mode 100644
index 0000000..9d9ec25
--- /dev/null
+++ b/core/Documents/Files.class.php
@@ -0,0 +1,61 @@
+loadBootstrap();
+ $this->loadFontawesome();
+ }
+
+ protected function initMetas() {
+ return array(
+ array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'),
+ array('name' => 'format-detection', 'content' => 'telephone=yes'),
+ array('charset' => 'utf-8'),
+ array("http-equiv" => 'expires', 'content' => '0'),
+ array("name" => 'robots', 'content' => 'noarchive')
+ );
+ }
+
+ protected function initRawFields() {
+ return array();
+ }
+
+ protected function initTitle() {
+ return "File Control Panel";
+ }
+ }
+
+ class FilesBody extends SimpleBody {
+
+ public function __construct($document) {
+ parent::__construct($document);
+ }
+
+ protected function getContent() {
+ $html = "";
+ $html .= "
$content
"; + } + + public function createParagraph($title, $id, $content) { + $id = replaceCssSelector($id); + $iconId = urlencode("$id-icon"); + return " +