Merge branch 'dev'
This commit is contained in:
commit
0fc03b394d
@ -37,7 +37,7 @@ The compiled dist files will be automatically moved to `/js`.
|
|||||||
|
|
||||||
Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php).
|
Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php).
|
||||||
These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have
|
These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have
|
||||||
a category, e.g. [PatchSQL](/core/Api/PatchSQL.class.php). These functions can be called directly, for example with `/api/patchSQL`. Both methods have one thing in common:
|
a category, e.g. [VerifyCaptcha](/core/Api/VerifyCaptcha.class.php). These functions can be called directly, for example with `/api/verifyCaptcha`. Both methods have one thing in common:
|
||||||
Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this:
|
Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
@ -112,7 +112,7 @@ If any result is expected from the api call, the `$req->getResult()` method can
|
|||||||
This step is not really required, as and changes made to the database must not be presented inside the code.
|
This step is not really required, as and changes made to the database must not be presented inside the code.
|
||||||
On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application
|
On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application
|
||||||
to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or
|
to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or
|
||||||
an additional patch file, which can be executed using the API (`/api/PatchSQL`), can be created. The patch files are usually
|
an additional patch file, which can be executed using the [CLI](/cli.php), can be created. The patch files are usually
|
||||||
located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure:
|
located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
410
cli.php
Normal file
410
cli.php
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'core/core.php';
|
||||||
|
include_once 'core/constants.php';
|
||||||
|
|
||||||
|
use Configuration\Configuration;
|
||||||
|
use Configuration\DatabaseScript;
|
||||||
|
use Objects\ConnectionData;
|
||||||
|
use Objects\User;
|
||||||
|
|
||||||
|
function printLine(string $line = "") {
|
||||||
|
echo $line . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _exit(string $line = "") {
|
||||||
|
printLine($line);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (php_sapi_name() !== "cli") {
|
||||||
|
_exit("Can only be executed via CLI");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatabaseConfig(): ConnectionData {
|
||||||
|
$configClass = "\\Configuration\\Database";
|
||||||
|
$file = getClassPath($configClass);
|
||||||
|
if (!file_exists($file) || !is_readable($file)) {
|
||||||
|
_exit("Database configuration does not exist or is not readable");
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once $file;
|
||||||
|
return new $configClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUser(): User {
|
||||||
|
$config = new Configuration();
|
||||||
|
$user = new User($config);
|
||||||
|
if (!$user->getSQL() || !$user->getSQL()->isConnected()) {
|
||||||
|
_exit("Could not establish database connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHelp() {
|
||||||
|
// TODO: help
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDatabase(array $argv) {
|
||||||
|
$action = $argv[2] ?? "";
|
||||||
|
|
||||||
|
if ($action === "migrate") {
|
||||||
|
$class = $argv[3] ?? null;
|
||||||
|
if (!$class) {
|
||||||
|
_exit("Usage: cli.php db migrate <class name>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = str_replace('/', '\\', $class);
|
||||||
|
$className = "\\Configuration\\$class";
|
||||||
|
$classPath = getClassPath($className);
|
||||||
|
if (!file_exists($classPath) || !is_readable($classPath)) {
|
||||||
|
_exit("Database script file does not exist or is not readable");
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once $classPath;
|
||||||
|
$obj = new $className();
|
||||||
|
if (!($obj instanceof DatabaseScript)) {
|
||||||
|
_exit("Not a database script");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = getUser();
|
||||||
|
$sql = $user->getSQL();
|
||||||
|
$queries = $obj->createQueries($sql);
|
||||||
|
foreach ($queries as $query) {
|
||||||
|
if (!$query->execute($sql)) {
|
||||||
|
_exit($sql->getLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($action === "export" || $action === "import") {
|
||||||
|
|
||||||
|
// database config
|
||||||
|
$config = getDatabaseConfig();
|
||||||
|
$dbType = $config->getProperty("type") ?? null;
|
||||||
|
$user = $config->getLogin();
|
||||||
|
$password = $config->getPassword();
|
||||||
|
$database = $config->getProperty("database");
|
||||||
|
$host = $config->getHost();
|
||||||
|
$port = $config->getPort();
|
||||||
|
|
||||||
|
// subprocess config
|
||||||
|
$env = [];
|
||||||
|
$options = array_slice($argv, 3);
|
||||||
|
$dataOnly = in_array("--data-only", $options) || in_array("-d", $options);
|
||||||
|
$descriptorSpec = [STDIN, STDOUT, STDOUT];
|
||||||
|
$inputData = null;
|
||||||
|
|
||||||
|
// argument config
|
||||||
|
if ($action === "import") {
|
||||||
|
$file = $argv[3] ?? null;
|
||||||
|
if (!$file) {
|
||||||
|
_exit("Usage: cli.php db import <path>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($file) || !is_readable($file)) {
|
||||||
|
_exit("File not found or not readable");
|
||||||
|
}
|
||||||
|
|
||||||
|
$inputData = file_get_contents($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dbType === "mysql") {
|
||||||
|
$command_args = ["-u", $user, '-h', $host, '-P', $port, "--password=$password"];
|
||||||
|
if ($action === "export") {
|
||||||
|
$command_bin = "mysqldump";
|
||||||
|
if ($dataOnly) {
|
||||||
|
$command_args[] = "--skip-triggers";
|
||||||
|
$command_args[] = "--compact";
|
||||||
|
$command_args[] = "--no-create-info";
|
||||||
|
}
|
||||||
|
} else if ($action === "import") {
|
||||||
|
$command_bin = "mysql";
|
||||||
|
$descriptorSpec[0] = ["pipe", "r"];
|
||||||
|
}
|
||||||
|
} else if ($dbType === "postgres") {
|
||||||
|
|
||||||
|
$env["PGPASSWORD"] = $password;
|
||||||
|
$command_args = ["-U", $user, '-h', $host, '-p', $port];
|
||||||
|
|
||||||
|
if ($action === "export") {
|
||||||
|
$command_bin = "/usr/bin/pg_dump";
|
||||||
|
if ($dataOnly) {
|
||||||
|
$command_args[] = "--data-only";
|
||||||
|
}
|
||||||
|
} else if ($action === "import") {
|
||||||
|
$command_bin = "/usr/bin/psql";
|
||||||
|
$descriptorSpec[0] = ["pipe", "r"];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_exit("Unsupported database type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($database) {
|
||||||
|
$command_args[] = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = array_merge([$command_bin], $command_args);
|
||||||
|
$process = proc_open($command, $descriptorSpec, $pipes, null, $env);
|
||||||
|
|
||||||
|
if (is_resource($process)) {
|
||||||
|
if ($action === "import" && $inputData && count($pipes) > 0) {
|
||||||
|
fwrite($pipes[0], $inputData);
|
||||||
|
fclose($pipes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_close($process);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_exit("Usage: cli.php db <migrate|import|export> [options...]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPullBranch(array $output): ?string {
|
||||||
|
foreach ($output as $line) {
|
||||||
|
$parts = preg_split('/\s+/', $line);
|
||||||
|
if (count($parts) >= 3 && $parts[2] === '(fetch)') {
|
||||||
|
$remoteName = $parts[0];
|
||||||
|
$url = $parts[1];
|
||||||
|
if (endsWith($url, "@github.com:rhergenreder/web-base.git") ||
|
||||||
|
endsWith($url, "@romanh.de:Projekte/web-base.git") ||
|
||||||
|
$url === 'https://github.com/rhergenreder/web-base.git' ||
|
||||||
|
$url === 'https://git.romanh.de/Projekte/web-base.git') {
|
||||||
|
return "$remoteName/master";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMaintenance(array $argv) {
|
||||||
|
$action = $argv[2] ?? "status";
|
||||||
|
$maintenanceFile = "MAINTENANCE";
|
||||||
|
$isMaintenanceEnabled = file_exists($maintenanceFile);
|
||||||
|
|
||||||
|
if ($action === "status") {
|
||||||
|
_exit("Maintenance: " . ($isMaintenanceEnabled ? "on" : "off"));
|
||||||
|
} else if ($action === "on") {
|
||||||
|
$file = fopen($maintenanceFile, 'w') or _exit("Unable to create maintenance file");
|
||||||
|
fclose($file);
|
||||||
|
_exit("Maintenance enabled");
|
||||||
|
} else if ($action === "off") {
|
||||||
|
if (file_exists($maintenanceFile)) {
|
||||||
|
if (!unlink($maintenanceFile)) {
|
||||||
|
_exit("Unable to delete maintenance file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_exit("Maintenance disabled");
|
||||||
|
} else if ($action === "update") {
|
||||||
|
|
||||||
|
printLine("$ git remote -v");
|
||||||
|
exec("git remote -v", $gitRemote, $ret);
|
||||||
|
if ($ret !== 0) {
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pullBranch = findPullBranch($gitRemote);
|
||||||
|
if ($pullBranch === null) {
|
||||||
|
$pullBranch = 'origin/master';
|
||||||
|
printLine("Unable to find remote update branch. Make sure, you are still in a git repository, and one of the remote branches " .
|
||||||
|
"have the original fetch url");
|
||||||
|
printLine("Trying to continue with '$pullBranch'");
|
||||||
|
} else {
|
||||||
|
printLine("Using remote update branch: $pullBranch");
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine("$ git fetch " . str_replace("/", " ", $pullBranch));
|
||||||
|
exec("git fetch " . str_replace("/", " ", $pullBranch), $gitFetch, $ret);
|
||||||
|
if ($ret !== 0) {
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine("$ git log HEAD..$pullBranch --oneline");
|
||||||
|
exec("git log HEAD..$pullBranch --oneline", $gitLog, $ret);
|
||||||
|
if ($ret !== 0) {
|
||||||
|
die();
|
||||||
|
} else if (count($gitLog) === 0) {
|
||||||
|
_exit("Already up to date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine("Found updates, checking repository state");
|
||||||
|
printLine("$ git diff-index --quiet HEAD --"); // check for any uncommitted changes
|
||||||
|
exec("git diff-index --quiet HEAD --", $gitDiff, $ret);
|
||||||
|
if ($ret !== 0) {
|
||||||
|
_exit("You have uncommitted changes. Please commit them before updating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable maintenance mode if it wasn't turned on before
|
||||||
|
if (!$isMaintenanceEnabled) {
|
||||||
|
printLine("Turning on maintenance mode");
|
||||||
|
$file = fopen($maintenanceFile, 'w') or _exit("Unable to create maintenance file");
|
||||||
|
fclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine("Ready to update, pulling and merging");
|
||||||
|
printLine("$ git pull " . str_replace("/", " ", $pullBranch) . " --no-ff");
|
||||||
|
exec("git pull " . str_replace("/", " ", $pullBranch) . " --no-ff", $gitPull, $ret);
|
||||||
|
if ($ret !== 0) {
|
||||||
|
printLine();
|
||||||
|
printLine("Update could not be applied, check the git output.");
|
||||||
|
printLine("Follow the instructions and afterwards turn off the maintenance mode again using:");
|
||||||
|
printLine("cli.php maintenance off");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable maintenance mode again
|
||||||
|
if (!$isMaintenanceEnabled) {
|
||||||
|
printLine("Turning off maintenance mode");
|
||||||
|
if (file_exists($maintenanceFile)) {
|
||||||
|
if (!unlink($maintenanceFile)) {
|
||||||
|
_exit("Unable to delete maintenance file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_exit("Usage: cli.php maintenance <status|on|off|update>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTable(array $head, array $body) {
|
||||||
|
|
||||||
|
$columns = [];
|
||||||
|
foreach ($head as $key) {
|
||||||
|
$columns[$key] = strlen($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($body as $row) {
|
||||||
|
foreach ($head as $key) {
|
||||||
|
$value = $row[$key] ?? "";
|
||||||
|
$length = strlen($value);
|
||||||
|
$columns[$key] = max($columns[$key], $length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print table
|
||||||
|
foreach ($head as $key) {
|
||||||
|
echo str_pad($key, $columns[$key]) . ' ';
|
||||||
|
}
|
||||||
|
printLine();
|
||||||
|
|
||||||
|
foreach ($body as $row) {
|
||||||
|
foreach ($head as $key) {
|
||||||
|
echo str_pad($row[$key] ?? "", $columns[$key]) . ' ';
|
||||||
|
}
|
||||||
|
printLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add missing api functions (should be all internal only i guess)
|
||||||
|
function onRoutes(array $argv) {
|
||||||
|
|
||||||
|
$user = getUser();
|
||||||
|
$action = $argv[2] ?? "list";
|
||||||
|
|
||||||
|
if ($action === "list") {
|
||||||
|
$req = new Api\Routes\Fetch($user);
|
||||||
|
$success = $req->execute();
|
||||||
|
if (!$success) {
|
||||||
|
_exit("Error fetching routes: " . $req->getLastError());
|
||||||
|
} else {
|
||||||
|
$routes = $req->getResult()["routes"];
|
||||||
|
$head = ["uid", "request", "action", "target", "extra", "active"];
|
||||||
|
|
||||||
|
// strict boolean
|
||||||
|
foreach ($routes as &$route) {
|
||||||
|
$route["active"] = $route["active"] ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
printTable($head, $routes);
|
||||||
|
}
|
||||||
|
} else if ($action === "add") {
|
||||||
|
if (count($argv) < 6) {
|
||||||
|
_exit("Usage: cli.php routes add <request> <action> <target> [extra]");
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = array(
|
||||||
|
"request" => $argv[3],
|
||||||
|
"action" => $argv[4],
|
||||||
|
"target" => $argv[5],
|
||||||
|
"extra" => $argv[6] ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
$req = new Api\Routes\Add($user);
|
||||||
|
$success = $req->execute($params);
|
||||||
|
if (!$success) {
|
||||||
|
_exit($req->getLastError());
|
||||||
|
} else {
|
||||||
|
_exit("Route added successfully");
|
||||||
|
}
|
||||||
|
} else if (in_array($action, ["remove","modify","enable","disable"])) {
|
||||||
|
$uid = $argv[3] ?? null;
|
||||||
|
if ($uid === null || ($action === "modify" && count($argv) < 7)) {
|
||||||
|
if ($action === "modify") {
|
||||||
|
_exit("Usage: cli.php routes $action <uid> <request> <action> <target> [extra]");
|
||||||
|
} else {
|
||||||
|
_exit("Usage: cli.php routes $action <uid>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = ["uid" => $uid];
|
||||||
|
if ($action === "remove") {
|
||||||
|
$input = null;
|
||||||
|
do {
|
||||||
|
if ($input === "n") {
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
echo "Remove route #$uid? (y|n): ";
|
||||||
|
} while(($input = trim(fgets(STDIN))) !== "y");
|
||||||
|
|
||||||
|
$req = new Api\Routes\Remove($user);
|
||||||
|
} else if ($action === "enable") {
|
||||||
|
$req = new Api\Routes\Enable($user);
|
||||||
|
} else if ($action === "disable") {
|
||||||
|
$req = new Api\Routes\Disable($user);
|
||||||
|
} else if ($action === "modify") {
|
||||||
|
$req = new Api\Routes\Update($user);
|
||||||
|
$params["request"] = $argv[4];
|
||||||
|
$params["action"] = $argv[5];
|
||||||
|
$params["target"] = $argv[6];
|
||||||
|
$params["extra"] = $argv[7] ?? "";
|
||||||
|
} else {
|
||||||
|
_exit("Unsupported action");
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $req->execute($params);
|
||||||
|
if (!$success) {
|
||||||
|
_exit($req->getLastError());
|
||||||
|
} else {
|
||||||
|
_exit("Route updated successfully");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_exit("Usage: cli.php routes <list|enable|disable|add|remove|modify> [options...]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$argv = $_SERVER['argv'];
|
||||||
|
if (count($argv) < 2) {
|
||||||
|
_exit("Usage: cli.php <db|routes|settings|maintenance> [options...]");
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $argv[1];
|
||||||
|
switch ($command) {
|
||||||
|
case 'help':
|
||||||
|
printHelp();
|
||||||
|
exit;
|
||||||
|
case 'db':
|
||||||
|
handleDatabase($argv);
|
||||||
|
break;
|
||||||
|
case 'routes':
|
||||||
|
onRoutes($argv);
|
||||||
|
break;
|
||||||
|
case 'maintenance':
|
||||||
|
onMaintenance($argv);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printLine("Unknown command '$command'");
|
||||||
|
printLine();
|
||||||
|
printHelp();
|
||||||
|
exit;
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
<?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()): bool {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -111,13 +111,15 @@ class Request {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($_SERVER['REQUEST_METHOD'], $this->allowedMethods)) {
|
|
||||||
$this->lastError = 'This method is not allowed';
|
|
||||||
header('HTTP 1.1 405 Method Not Allowed');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->externalCall) {
|
if ($this->externalCall) {
|
||||||
|
|
||||||
|
// check the request method
|
||||||
|
if (!in_array($_SERVER['REQUEST_METHOD'], $this->allowedMethods)) {
|
||||||
|
$this->lastError = 'This method is not allowed';
|
||||||
|
header('HTTP 1.1 405 Method Not Allowed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$apiKeyAuthorized = false;
|
$apiKeyAuthorized = false;
|
||||||
|
|
||||||
// Logged in or api key authorized?
|
// Logged in or api key authorized?
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
|
use Driver\SQL\Condition\Compare;
|
||||||
|
|
||||||
abstract class RoutesAPI extends Request {
|
abstract class RoutesAPI extends Request {
|
||||||
|
|
||||||
|
const ACTIONS = array("redirect_temporary", "redirect_permanently", "static", "dynamic");
|
||||||
|
|
||||||
protected function formatRegex(string $input, bool $append) : string {
|
protected function formatRegex(string $input, bool $append) : string {
|
||||||
$start = startsWith($input, "^");
|
$start = startsWith($input, "^");
|
||||||
$end = endsWith($input, "$");
|
$end = endsWith($input, "$");
|
||||||
@ -16,6 +21,39 @@ namespace Api {
|
|||||||
|
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function routeExists($uid): bool {
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$res = $sql->select($sql->count())
|
||||||
|
->from("Route")
|
||||||
|
->where(new Compare("uid", $uid))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->success = ($res !== false);
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
if ($this->success) {
|
||||||
|
if ($res[0]["count"] === 0) {
|
||||||
|
return $this->createError("Route not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toggleRoute($uid, $active): bool {
|
||||||
|
if (!$this->routeExists($uid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$this->success = $sql->update("Route")
|
||||||
|
->set("active", $active)
|
||||||
|
->where(new Compare("uid", $uid))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,8 +63,10 @@ namespace Api\Routes {
|
|||||||
use Api\Parameter\StringType;
|
use Api\Parameter\StringType;
|
||||||
use Api\RoutesAPI;
|
use Api\RoutesAPI;
|
||||||
use Driver\SQL\Column\Column;
|
use Driver\SQL\Column\Column;
|
||||||
|
use Driver\SQL\Condition\Compare;
|
||||||
use Driver\SQL\Condition\CondBool;
|
use Driver\SQL\Condition\CondBool;
|
||||||
use Driver\SQL\Condition\CondRegex;
|
use Driver\SQL\Condition\CondRegex;
|
||||||
|
use Objects\User;
|
||||||
|
|
||||||
class Fetch extends RoutesAPI {
|
class Fetch extends RoutesAPI {
|
||||||
|
|
||||||
@ -162,7 +202,7 @@ namespace Api\Routes {
|
|||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function validateRoutes() {
|
private function validateRoutes(): bool {
|
||||||
|
|
||||||
$this->routes = array();
|
$this->routes = array();
|
||||||
$keys = array(
|
$keys = array(
|
||||||
@ -173,10 +213,6 @@ namespace Api\Routes {
|
|||||||
"active" => Parameter::TYPE_BOOLEAN
|
"active" => Parameter::TYPE_BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
$actions = array(
|
|
||||||
"redirect_temporary", "redirect_permanently", "static", "dynamic"
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach($this->getParam("routes") as $index => $route) {
|
foreach($this->getParam("routes") as $index => $route) {
|
||||||
foreach($keys as $key => $expectedType) {
|
foreach($keys as $key => $expectedType) {
|
||||||
if (!array_key_exists($key, $route)) {
|
if (!array_key_exists($key, $route)) {
|
||||||
@ -193,7 +229,7 @@ namespace Api\Routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$action = $route["action"];
|
$action = $route["action"];
|
||||||
if (!in_array($action, $actions)) {
|
if (!in_array($action, self::ACTIONS)) {
|
||||||
return $this->createError("Invalid action: $action");
|
return $this->createError("Invalid action: $action");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,5 +249,149 @@ namespace Api\Routes {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Add extends RoutesAPI {
|
||||||
|
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"request" => new StringType("request", 128),
|
||||||
|
"action" => new StringType("action"),
|
||||||
|
"target" => new StringType("target", 128),
|
||||||
|
"extra" => new StringType("extra", 64, true, ""),
|
||||||
|
));
|
||||||
|
$this->isPublic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()): bool {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $this->formatRegex($this->getParam("request"), true);
|
||||||
|
$action = $this->getParam("action");
|
||||||
|
$target = $this->getParam("target");
|
||||||
|
$extra = $this->getParam("extra");
|
||||||
|
|
||||||
|
if (!in_array($action, self::ACTIONS)) {
|
||||||
|
return $this->createError("Invalid action: $action");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$this->success = $sql->insert("Route", ["request", "action", "target", "extra"])
|
||||||
|
->addRow($request, $action, $target, $extra)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Update extends RoutesAPI {
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"uid" => new Parameter("uid", Parameter::TYPE_INT),
|
||||||
|
"request" => new StringType("request", 128),
|
||||||
|
"action" => new StringType("action"),
|
||||||
|
"target" => new StringType("target", 128),
|
||||||
|
"extra" => new StringType("extra", 64, true, ""),
|
||||||
|
));
|
||||||
|
$this->isPublic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()): bool {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $this->getParam("uid");
|
||||||
|
if (!$this->routeExists($uid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $this->formatRegex($this->getParam("request"), true);
|
||||||
|
$action = $this->getParam("action");
|
||||||
|
$target = $this->getParam("target");
|
||||||
|
$extra = $this->getParam("extra");
|
||||||
|
if (!in_array($action, self::ACTIONS)) {
|
||||||
|
return $this->createError("Invalid action: $action");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$this->success = $sql->update("Route")
|
||||||
|
->set("request", $request)
|
||||||
|
->set("action", $action)
|
||||||
|
->set("target", $target)
|
||||||
|
->set("extra", $extra)
|
||||||
|
->where(new Compare("uid", $uid))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Remove extends RoutesAPI {
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||||
|
));
|
||||||
|
$this->isPublic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()): bool {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $this->getParam("uid");
|
||||||
|
if (!$this->routeExists($uid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$this->success = $sql->delete("Route")
|
||||||
|
->where(new Compare("uid", $uid))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Enable extends RoutesAPI {
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||||
|
));
|
||||||
|
$this->isPublic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()): bool {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $this->getParam("uid");
|
||||||
|
return $this->toggleRoute($uid, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Disable extends RoutesAPI {
|
||||||
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||||
|
));
|
||||||
|
$this->isPublic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()): bool {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $this->getParam("uid");
|
||||||
|
return $this->toggleRoute($uid, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,8 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
->addString("target", 128)
|
->addString("target", 128)
|
||||||
->addString("extra", 64, true)
|
->addString("extra", 64, true)
|
||||||
->addBool("active", true)
|
->addBool("active", true)
|
||||||
->primaryKey("uid");
|
->primaryKey("uid")
|
||||||
|
->unique("request");
|
||||||
|
|
||||||
$queries[] = $sql->insert("Route", array("request", "action", "target", "extra"))
|
$queries[] = $sql->insert("Route", array("request", "action", "target", "extra"))
|
||||||
->addRow("^/admin(/.*)?$", "dynamic", "\\Documents\\Admin", NULL)
|
->addRow("^/admin(/.*)?$", "dynamic", "\\Documents\\Admin", NULL)
|
||||||
@ -192,8 +193,7 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
|
->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("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("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);
|
self::loadPatches($queries, $sql);
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function loadDefaults(): Settings {
|
public static function loadDefaults(): Settings {
|
||||||
$hostname = $_SERVER["SERVER_NAME"];
|
$hostname = $_SERVER["SERVER_NAME"] ?? "localhost";
|
||||||
$protocol = getProtocol();
|
$protocol = getProtocol();
|
||||||
$jwt = generateRandomString(32);
|
$jwt = generateRandomString(32);
|
||||||
|
|
||||||
|
@ -280,9 +280,7 @@ namespace Documents\Install {
|
|||||||
$success = false;
|
$success = false;
|
||||||
$msg = "Unable to write file";
|
$msg = "Unable to write file";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($sql) {
|
|
||||||
$sql->close();
|
$sql->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class PostgreSQL extends SQL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection = @pg_connect(implode(" ", $connectionString));
|
$this->connection = @pg_connect(implode(" ", $connectionString), PGSQL_CONNECT_FORCE_NEW);
|
||||||
if (!$this->connection) {
|
if (!$this->connection) {
|
||||||
$this->lastError = "Failed to connect to Database";
|
$this->lastError = "Failed to connect to Database";
|
||||||
$this->connection = NULL;
|
$this->connection = NULL;
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
define("WEBBASE_VERSION", "1.2.3");
|
define("WEBBASE_VERSION", "1.2.4");
|
||||||
|
|
||||||
|
spl_autoload_extensions(".php");
|
||||||
|
spl_autoload_register(function($class) {
|
||||||
|
$full_path = getClassPath($class);
|
||||||
|
if(file_exists($full_path)) {
|
||||||
|
include_once $full_path;
|
||||||
|
} else {
|
||||||
|
include_once getClassPath($class, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function getProtocol(): string {
|
function getProtocol(): string {
|
||||||
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https" : "http";
|
$isSecure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ||
|
||||||
|
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
|
||||||
|
(!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on');
|
||||||
|
|
||||||
|
return $isSecure ? 'https' : 'http';
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRandomString($length): string {
|
function generateRandomString($length): string {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
img/maintenance.png
Normal file
BIN
img/maintenance.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
25
index.php
25
index.php
@ -1,29 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
include_once 'core/core.php';
|
||||||
|
include_once 'core/datetime.php';
|
||||||
|
include_once 'core/constants.php';
|
||||||
|
|
||||||
|
if (is_file("MAINTENANCE")) {
|
||||||
|
http_response_code(503);
|
||||||
|
$currentDir = dirname(__FILE__);
|
||||||
|
serveStatic($currentDir, "/static/maintenance.html");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
use Api\Request;
|
use Api\Request;
|
||||||
use Configuration\Configuration;
|
use Configuration\Configuration;
|
||||||
use Documents\Document404;
|
use Documents\Document404;
|
||||||
use Elements\Document;
|
use Elements\Document;
|
||||||
|
|
||||||
include_once 'core/core.php';
|
|
||||||
include_once 'core/datetime.php';
|
|
||||||
include_once 'core/constants.php';
|
|
||||||
|
|
||||||
if (!is_readable(getClassPath(Configuration::class))) {
|
if (!is_readable(getClassPath(Configuration::class))) {
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
die(json_encode(array( "success" => false, "msg" => "Configuration directory is not readable, check permissions before proceeding." )));
|
die(json_encode(array( "success" => false, "msg" => "Configuration directory is not readable, check permissions before proceeding." )));
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_extensions(".php");
|
|
||||||
spl_autoload_register(function($class) {
|
|
||||||
$full_path = getClassPath($class, true);
|
|
||||||
if(file_exists($full_path)) {
|
|
||||||
include_once $full_path;
|
|
||||||
} else {
|
|
||||||
include_once getClassPath($class, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$config = new Configuration();
|
$config = new Configuration();
|
||||||
$user = new Objects\User($config);
|
$user = new Objects\User($config);
|
||||||
$sql = $user->getSQL();
|
$sql = $user->getSQL();
|
||||||
|
@ -138,22 +138,26 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// DATABASE PORT
|
// DATABASE PORT
|
||||||
let prevPort = $("#port").val();
|
let portField = $("#port");
|
||||||
let prevDbms = $("#type option:selected").val();
|
let typeField = $("#type");
|
||||||
|
|
||||||
|
let prevPort = parseInt(portField.val());
|
||||||
|
let prevDbms = typeField.find("option:selected").val();
|
||||||
function updateDefaultPort() {
|
function updateDefaultPort() {
|
||||||
let defaultPorts = {
|
let defaultPorts = {
|
||||||
"mysql": 3306,
|
"mysql": 3306,
|
||||||
"postgres": 5432
|
"postgres": 5432
|
||||||
};
|
};
|
||||||
|
|
||||||
let curDbms = $("#type option:selected").val();
|
let curDbms = typeField.find("option:selected").val();
|
||||||
if(defaultPorts[prevDbms] === prevPort) {
|
if(defaultPorts[prevDbms] === prevPort) {
|
||||||
$("#port").val(defaultPorts[curDbms]);
|
prevDbms = curDbms;
|
||||||
|
portField.val(prevPort = defaultPorts[curDbms]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDefaultPort();
|
updateDefaultPort();
|
||||||
$("#type").change(function() {
|
typeField.change(function() {
|
||||||
updateDefaultPort();
|
updateDefaultPort();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
36
static/maintenance.html
Normal file
36
static/maintenance.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="format-detection" content="telephone=yes">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="expires" content="0">
|
||||||
|
<meta name="robots" content="noarchive">
|
||||||
|
<title>Maintenance</title>
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css" type="text/css">
|
||||||
|
<script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/fontawesome.min.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="error-template pt-5 pl-5">
|
||||||
|
<h1>Service unavailable</h1>
|
||||||
|
<h3>Temporarily down for maintenance</h3>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Sorry for the inconvenience but we're performing some maintenance at the moment.
|
||||||
|
we'll be back online shortly!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="javascript:document.location.reload()" class="btn btn-info btn-lg mt-3">
|
||||||
|
<i class="fa fa-redo mr-2"></i>Retry
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-center pt-5">
|
||||||
|
<img src="/img/maintenance.png" alt="[maintenance]">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
Loading…
Reference in New Issue
Block a user