App/lib/99_manager.php

333 lines
13 KiB
PHP
Executable File

<?php #lib/99_manager.php
/********************************************************************************
* Content: API Manager class *
* Author: Nils Otterpohl *
* Last modification: 13.12.2020 *
* Version: alpha (object, incomplete, untested, uncommented) *
********************************************************************************/
// Missing:
// - Error handling
// - Proper response codes
class Manager
{
public static $db; // Database, i.e. Mysqli
public static $kv; // Key-Value-Storage, i.e. Redis
public $user; // Object that handles login and logout
public $printer;
private $method = "GET"; // HTTP method used in connection
private $requestedContentType = "HTML"; // Desired content type of request
// Path used after index.php: index.php/MAIN/FILTER/SUB/SUBFILTER
private $route = [
"route" => "", // Route of page module. Has to be set, at least to "/" [GET(all), POST]
"mainID" => null, // Filter of main resource. [GET(one), PUT, PATCH, DELETE] with ID, [GET(all)] with condition
"sub" => null, // Route of sub module. Requests are specific to a main resource, so only valid if filter is an ID. [GET(all), POST]
"subID" => null // Filter of sub resource. [GET(one), PUT, PATCH, DELETE] with ID, [GET(all)] with condition
];
private $availablePages = []; // Array of scanned and accessible page modules
private $availableLinks = []; // Array of scanned and accessible link modules
private $availableObjects = []; // Array of scanned and accessible object modules
private $responseCode = 401; // HTTP code to be returned
public $objects = [];
public $input = []; // Submitted JSON data after sanitation. ONLY SET IF Security token was valid.
public $filter = [];
public $selected = [];
private $output = [
"messages" => [],
"admin" => false // TODO DEPRECATED
];
public function __construct($db, $kv, $key) {
self::$db = $db;
self::$kv = $kv;
$jwt = $this->readRequest();
if (isset($_SERVER["CONTENT_TYPE"]) && "application/json"==$_SERVER["CONTENT_TYPE"]) {
$input = $this->cleanInput(json_decode(file_get_contents("php://input"), true));
} else {
$input = [];
if (isset($_GET["filter"])) {
$input["filter"] = $this->cleanInput(json_decode($_GET["filter"], true));
}
if (isset($_GET["selected"])) {
$input["selected"] = $this->cleanInput(json_decode($_GET["selected"], true));
} else {
$input["selected"] = [];
}
if (isset($_GET["print"])) {
$input["print"] = $_GET["print"];
}
}
if ("HTML" == $this->requestedContentType && isset($input["print"])) {
$this->requestedContentType = "PRINT";
}
$this->user = new Login($db, $kv, $key, $this, $jwt, $input, "GET"!=$this->method);
if (in_array("Logout", $this->route)) {
$this->user->Logout();
$this->AddMessage("Logout erkannt!");
}
// Output status of user login to client
$this->output["status"] = $this->user->Status();
// Login was successful?
if ($this->user->LoggedIn()) {
// Überprüfe gesendetes Security-Token und veröffentliche ggfs. $input
if ("GET"==$this->method) {
// Maximal Filter oder Print als Input erlaubt
if (isset($input["print"])) {
$this->input["print"] = $input["print"];
}
if (isset($input["filter"])) {
$this->filter = $input["filter"];
}
if (isset($input["selected"])) {
$this->selected = $input["selected"];
}
} else if ("OPTIONS"==$this->method) {
// No filter or input at the moment
} else if (!empty($input)) {
if (isset($input["secToken"]) && $this->user->VerifySecToken($input["secToken"])) {
$this->input = $input;
} else {
$this->addMessage("Input wird ignoriert. Senden Sie den secToken mit!");
}
} else {
}
$this->detectObjects();
$this->detectLinks();
$this->detectPages();
if ("OPTIONS"==$this->method) {
$pages = $this->output["pages"];
$this->output["pages"] = [];
foreach ($pages as $page) {
$this->output["pages"][] = $page["route"];
}
$this->output["pagesChecksum"] = sha1(implode(",", $this->output["pages"]));
$this->responseCode = 200;
} else {
$this->loadPage();
$this->detectPrints();
$this->output["filter"] = $this->filter;
} } }
public function JWT() {return $this->jwt;}
public function Method() {return $this->method;}
public function RequestedContentType() {return $this->requestedContentType;}
public function Output() {return $this->output;}
public function Route() {return $this->route["route"];}
public function Main() {return $this->route["mainID"];}
public function Sub() {return $this->route["sub"];}
public function SubID() {return $this->route["subID"];}
public function ResponseCode() {return $this->responseCode;}
public function Filter() {return $this->filter;}
public function AddMessage($message, $ident = null) {$this->output["messages"][] = $message;}
public function ArrayToString($a) {
$ret = "";
foreach ($a as $k => $v) {
$ret.=$k." => ".(is_array($v) ? "[".$this->ArrayToString($v)."]" : $v).", ";
}
return $ret;
}
/******************** PRIVATE FUNCTIONS ********************/
private function readRequest() {
$jwt = null;
// Copied from https://stackoverflow.com/a/40582472
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
} else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
//print_r($requestHeaders);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
} }
// Get the access token from the header
if (!empty($headers)) {
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
$jwt = $matches[1];
} }
// End of copied
// Read accepted/requested content type
if (isset($_SERVER["HTTP_ACCEPT"]) && "application/json"==$_SERVER["HTTP_ACCEPT"]) {
$this->requestedContentType = "JSON";
} elseif (isset($_SERVER["HTTP_ACCEPT"]) && "application/pdf"==$_SERVER["HTTP_ACCEPT"]) {
$this->requestedContentType = "PDF";
}
// Read the requested route (resource) and method
$this->method = $_SERVER['REQUEST_METHOD'];
if (preg_match('$/([^/]+)/?([^/]+)?/?([^/]+)?/?([^/]+)?$', $_SERVER['PATH_INFO'] ?? "", $matches, PREG_UNMATCHED_AS_NULL)) {
$this->route["route"] = $matches[1];
$this->route["mainID"] = $matches[2];
$this->route["sub"] = $matches[3];
$this->route["subID"] = $matches[4];
}
return $jwt;
}
private function cleanInput($array) { // Cleans input
$ret = [];
if ($array!==null) {
foreach ($array as $key => $value) {
$ret[$key] = is_null($value) ? null : (is_array($value) ? $this->cleanInput($value) : (is_string($value) ? self::$db->escape_string($value) : intval($value)));
} }
return $ret;
}
private function detectObjects() {
$dir = scandir("obj");
foreach ($dir as $val) {
if ($val!="." && $val!="..") {
if (file_exists("obj/".$val."/module.json")) {
$json = json_decode(file_get_contents("obj/".$val."/module.json"), true);
$this->availableObjects[$json["name"]] = [
"name" => $json["name"],
"class" => $json["class"],
"path" => $val,
"useRight" => $json["useRight"] ?? null,
"adminRight" => $json["adminRight"] ?? null
];
} } } }
private function detectLinks() {
$dir = scandir("lnk");
foreach ($dir as $val) {
if ($val!="." && $val!="..") {
if (file_exists("lnk/".$val."/module.json")) {
$json = json_decode(file_get_contents("lnk/".$val."/module.json"), true);
if ($this->user->HasRight($json["useRight"])) {
$this->availableLinks[$json["route"]] = [
"route" => $json["route"],
"table" => $json["table"] ?? "",
"path" => $val,
"dependencies" => $json["dependencies"] ?? [],
"useRight" => $json["useRight"] ?? null,
"adminRight" => $json["adminRight"] ?? null,
];
} } } } }
private function detectPages() {
$dir = scandir("pgs");
foreach ($dir as $val) {
if ($val!="." && $val!="..") {
if (file_exists("pgs/".$val."/module.json")) {
$json = json_decode(file_get_contents("pgs/".$val."/module.json"), true);
if ($this->user->HasRight($json["useRight"])) {
$this->availablePages[$json["route"]] = [
"title" => $json["title"] ?? "",
"route" => $json["route"],
"table" => $json["table"] ?? "",
"path" => $val,
"links" => $json["links"] ?? [],
"dependencies" => $json["dependencies"] ?? [],
"useRight" => $json["useRight"] ?? null,
"adminRight" => $json["adminRight"] ?? null,
];
$page = [
"link" => "/index.php/".$json["route"],
"route" => $json["route"],
"isPage" => $json["isPage"] ?? false,
];
if ($page["isPage"]) {
$page["name"] = $json["title"];
$page["template"] = isset($json["template"]) ? "/pgs/".$val."/".$json["template"] : null;
$page["marker"] = $json["marker"];
$page["mainFields"] = $json["mainFields"];
}
$this->output["pages"][] = $page;
} } } } }
private function loadPage() {
if (isset($this->availablePages[$this->Route()])) {
$info = $this->availablePages[$this->Route()];
foreach ($info["dependencies"] as $dep) {
if (isset($this->availableObjects[$dep]) && !isset($this->objects[$dep])) {
include "obj/".$this->availableObjects[$dep]["path"]."/object.php";
$className = "\\".$this->availableObjects[$dep]["name"]."\\".$this->availableObjects[$dep]["class"];
$this->objects[$dep] = new $className(self::$db, $this, $this->Main());
} }
$file = "pgs/".$info["path"]."/page.php";
if (file_exists($file)) {
include $file;
$className = $info["route"]."Page";
$page = new $className(self::$db, self::$kv, $this, $info);
if (isset($this->route["sub"])) {
if ($this->Main()!==null && $this->SubID()!==null) {
$sub = $page->GetLink($this->route["sub"]);
if (isset($sub, $this->availableLinks[$sub["route"]])) {
$link = $this->availableLinks[$sub["route"]];
include "lnk/".$link["path"]."/link.php";
$subClassName = $link["route"]."Link";
$subClass = new $subClassName(self::$db, self::$kv, $this, $link, $page);
$ids[$sub["mainName"]] = $this->Main();
$ids[$sub["subName"]] = $this->SubID();
$this->responseCode = $subClass->DoRequest($this->method, $ids);
$page->DoRequest("GET");
} else {
$this->responseCode = 404;
}
} else {
$this->responseCode = 400;
}
} else {
$this->responseCode = $page->DoRequest($this->method);
}
if ($this->responseCode>=200 && $this->responseCode<300) {
$this->output["meta"] = $page->GetMeta();
$this->output["content"] = $page->GetResult();
$this->output["template"] = $page->GetTemplate();
if (isset($info["adminRight"]) && $this->user->HasRight($info["adminRight"])) {
$this->output["admin"] = true;
}
$this->output["options"] = $page->GetOptions($this->output["admin"]);
}
} else {
$this->responseCode = 500; // Missing file means misconfiguration
$this->AddMessage($this->Route()." seems to be misconfigured. A required file is missing!");
}
} else if ($this->Route()==null) {
// Dashboard with Stubs (hopefully)?
$this->responseCode = 200;
$this->output["content"] = [];
$this->output["template"] = "";
} else {
// Load nothing, because an unknown page was requested
$this->responseCode = 404;
$this->AddMessage($this->Route()." doesn't exist");
$this->output["content"] = [];
$this->output["template"] = "";
} }
private function detectPrints() {
$this->printer = new Printer(self::$db, $this);
$this->output["prints"] = $this->printer->GetPrints();
}
}