412 lines
12 KiB
PHP
412 lines
12 KiB
PHP
<?php #lib/20_routes.php
|
|
|
|
namespace Resources;
|
|
|
|
class Element
|
|
{
|
|
protected $parent;
|
|
protected $filled = false;
|
|
protected $callerList;
|
|
protected $id;
|
|
protected $chksum = "";
|
|
|
|
protected $fields = [];
|
|
protected $keys = [];
|
|
protected $links = [];
|
|
protected $files = [];
|
|
|
|
public function __construct($parent, $id, $callerList) {
|
|
$this->parent = $parent;
|
|
$this->callerList = $callerList;
|
|
$this->callerList[] = $this->parent->Table();
|
|
$this->id = $id;
|
|
|
|
$definitions = $parent->Definitions();
|
|
$this->fields = array_combine(array_keys($definitions["fields"]), array_column($definitions["fields"], "default"));
|
|
$this->keys = array_fill_keys(array_keys($definitions["keys"]), null);
|
|
$this->files = array_keys($definitions["files"]);
|
|
foreach ($definitions["links"] as $link => $def) {
|
|
$resClass = "\\Resources\\".$def["resourceClass"];
|
|
if (!in_array($resClass::Get()->Table(), $callerList)) {
|
|
$this->links[$link] = [];
|
|
} } }
|
|
|
|
public function ID() {return $this->id;}
|
|
public function Checksum() {return $this->filled ? $this->chksum : "";}
|
|
|
|
public function Patch($data) {
|
|
// Overwrite field values if data exists
|
|
foreach ($this->fields as $field => &$value) {
|
|
if (isset($data[$field])) {
|
|
$value = $data[$field];
|
|
} }
|
|
unset($value); // If removed, $value would still point to last element of fields
|
|
|
|
$definitions = $this->parent->Definitions();
|
|
foreach ($this->keys as $key => &$reference) {
|
|
if (isset($data[$key])) {
|
|
$class = "\\Resources\\".$definitions["keys"][$key]["resourceClass"];
|
|
$reference = is_null($data[$key]) ? null : $class::Get()->Ref($data[$key], $this->callerList);
|
|
} }
|
|
unset($reference); // If removed, $reference would still point to last element of keys
|
|
|
|
foreach ($this->links as $link => &$refList) {
|
|
$class = "\\Links\\".$definitions["links"][$link]["linkClass"];
|
|
$refList = $class::Get()->Refs($this->id, $this->parent->Ident(), $this->callerList);
|
|
}
|
|
unset($refList); // If removed, $reference would still point to last element of keys
|
|
|
|
$this->filled = true;
|
|
|
|
if (isset($data["CHKSUM"]) && !is_null($data["CHKSUM"])) {
|
|
$this->chksum = $data["CHKSUM"];
|
|
} else {
|
|
$this->updateChecksum();
|
|
} }
|
|
|
|
public function Load() {
|
|
$qry = "SELECT * FROM ".$this->parent->Table()." WHERE ID = ?";
|
|
if ($stmt = \DB::Get()->prepare($qry)) {
|
|
$stmt->bind_param("s", $this->id);
|
|
if ($stmt->execute()) {
|
|
$res = $stmt->get_result();
|
|
if ($row = $res->fetch_assoc()) {
|
|
$this->Patch($row);
|
|
return true;
|
|
}
|
|
// Not found
|
|
return false;
|
|
} }
|
|
\Response::Get()->DbError();
|
|
return false;
|
|
}
|
|
|
|
public function Store() {
|
|
if ($this->filled) {
|
|
$fields = [];
|
|
$values = [];
|
|
$types = "";
|
|
|
|
if ($this->parent->HasChksum()) {
|
|
$fields[] = "CHKSUM";
|
|
$values[] = $this->chksum;
|
|
$types.= "s";
|
|
}
|
|
|
|
$definitions = $this->parent->Definitions();
|
|
foreach ($definitions["fields"] as $field => $def) {
|
|
$fields[] = $field;
|
|
$values[] = $this->fields[$field];
|
|
$types.= $def["type"];
|
|
}
|
|
foreach ($definitions["keys"] as $key => $def) {
|
|
$fields[] = $key;
|
|
$values[] = is_null($this->keys[$key]) ? null : $this->keys[$key]->ID();
|
|
$types.= $def["type"];
|
|
}
|
|
|
|
$qry = "UPDATE ".$this->parent->Table()." SET ".implode(" = ?, ", $fields)." = ? WHERE ID = ?";
|
|
$values[] = $this->id;
|
|
$types.= "s";
|
|
|
|
if ($stmt = \DB::Get()->prepare($qry)) {
|
|
$stmt->bind_param($types, ...$values);
|
|
if ($stmt->execute()) {
|
|
$this->parent->UpdateChecksum();
|
|
return true;
|
|
} }
|
|
\Response::Get()->DbError();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected function updateChecksum() {
|
|
$this->chksum = $this->parent->CalcChecksum($this->Json(0));
|
|
}
|
|
|
|
public function Json($depth = null) {
|
|
if (is_null($depth)) {
|
|
$depth = \Request::DetailDepth();
|
|
}
|
|
if (!$this->filled && !$this->Load()) {
|
|
return $this->id;
|
|
} else {
|
|
$ret = [
|
|
"ID" => $this->id,
|
|
"CHKSUM" => $this->chksum
|
|
];
|
|
foreach ($this->fields as $field => $value) {
|
|
if (\Filter\Filter::Selected($field)) {
|
|
$ret[$field] = $value;
|
|
} }
|
|
foreach ($this->files as $field) {
|
|
if (\Filter\Filter::Selected($field)) {
|
|
$ret[$field] = $this->parent->FileGet($field, $this->id);
|
|
} }
|
|
foreach ($this->keys as $key => $reference) {
|
|
if (\Filter\Filter::Selected($key)) {
|
|
$ret[$key] = is_null($reference) ? null : ($depth>0 ? $reference->Json($depth-1) : $reference->ID());
|
|
} }
|
|
foreach ($this->links as $link => $referenceList) {
|
|
if (\Filter\Filter::Selected($link)) {
|
|
$ids = [];
|
|
foreach ($referenceList as $reference) {
|
|
if (!is_null($reference)) {
|
|
$ids[] = $depth>0 ? $reference->Json($depth-1) : $reference->ID();
|
|
} }
|
|
$ret[$link] = $ids;
|
|
} }
|
|
return $ret;
|
|
} }
|
|
|
|
public function FileUpload($field) {
|
|
if (isset($_FILES["file"])) {
|
|
$source = $_FILES ["file"]["tmp_name"];
|
|
|
|
// Delete old files
|
|
$this->FileErase($field);
|
|
|
|
// Ensure path exists
|
|
$dir = $this->parent->FileDirectory($field);
|
|
if (!file_exists($dir)) {
|
|
mkdir($dir, 0755, true);
|
|
}
|
|
|
|
// Get file type for proper extension
|
|
$mime = mime_content_type($source);
|
|
if (array_key_exists($mime, MIME_MAP)) {
|
|
if (move_uploaded_file($source, $dir.$this->id.".".MIME_MAP[$mime])) {
|
|
return true;
|
|
} } }
|
|
return false;
|
|
}
|
|
|
|
public function FileErase($field) {
|
|
$dir = $this->parent->FileDirectory($field);
|
|
|
|
$filelist = glob($dir.$this->id.".*");
|
|
foreach ($filelist as $existingfile) {
|
|
unlink($existingfile);
|
|
}
|
|
return sizeof($filelist)>0;
|
|
}
|
|
}
|
|
|
|
abstract class Handler {
|
|
protected static $instances = [];
|
|
protected $index = [];
|
|
public static function Get() {
|
|
$class = get_called_class();
|
|
if (!isset($instances[$class])) {
|
|
self::$instances[$class] = new $class();
|
|
}
|
|
return self::$instances[$class];
|
|
}
|
|
|
|
private function __construct() {
|
|
foreach (["fields", "keys", "files", "links"] as $type) {
|
|
if (!array_key_exists($type, $this->definitions)) {
|
|
$this->definitions[$type] = [];
|
|
} }
|
|
|
|
foreach ($this->definitions["links"] as &$link) {
|
|
$linkClass = "\\Links\\".$link["linkClass"];
|
|
$link["resourceClass"] = $linkClass::Get()->OtherResourceClass(get_called_class());
|
|
}
|
|
}
|
|
|
|
public function Table() {return $this->names["table"];}
|
|
public function Ident() {return $this->names["ident"];}
|
|
public function Short() {return $this->names["short"];}
|
|
public function TableWithShort() {return $this->names["table"]." ".$this->names["short"];}
|
|
public function HasUuid() {return $this->has["uuid"];}
|
|
public function HasChksum() {return $this->has["sha256"];}
|
|
public function Definitions() {return $this->definitions;}
|
|
|
|
public function Ref($id, $callerList = []) {
|
|
if (!isset($this->index[$id]) ) {
|
|
$this->index[$id] = new Element($this, $id, $callerList); // Might be null, but we will buffer the result anyway
|
|
}
|
|
return $this->index[$id];
|
|
}
|
|
|
|
public function RefAll($callerList = []) {
|
|
$this->loadAll($callerList);
|
|
return $this->index;
|
|
}
|
|
|
|
protected function loadChecksum($id = null) {
|
|
$field = "CHKSUM";
|
|
if (!$this->has["sha256"]) {
|
|
// There is no checksum column to use. Smasch all columns together
|
|
$fields = [];
|
|
$arr = array_merge(array_keys($this->definitions["fields"]), array_keys($this->definitions["keys"]));
|
|
$field = "CONCAT(IFNULL(".implode(", '__NULL__'), IFNULL(", $arr).", '__NULL__'))";
|
|
}
|
|
if (is_null($id)) {
|
|
// We need to concat all the rows
|
|
$field = "GROUP_CONCAT(".$field.")";
|
|
}
|
|
if (!$this->has["sha256"] || is_null($id)) {
|
|
// Call the hash function, because $field is not a pregenerated checksum
|
|
$field = "SHA2(".$field.", 256)";
|
|
}
|
|
$qry = "SELECT ".$field." cs FROM ".$this->Table();
|
|
if (!is_null($id)) {
|
|
$qry.= " WHERE ID = ?";
|
|
}
|
|
if ($stmt = \DB::Get()->prepare($qry)) {
|
|
if (!is_null($id)) {
|
|
$stmt->bind_param("s", $id);
|
|
}
|
|
if ($stmt->execute()) {
|
|
$res = $stmt->get_result();
|
|
if ($row = $res->fetch_assoc()) {
|
|
$chksum = $row["cs"];
|
|
return $chksum;
|
|
}
|
|
// Does not exist
|
|
return null;
|
|
} }
|
|
\Response::Get()->DbError();
|
|
return null;
|
|
}
|
|
|
|
public function CalcChecksum($data) {
|
|
$concat = "";
|
|
foreach ($this->definitions as $type => $definition) {
|
|
if ($type!="links") {
|
|
foreach ($definition as $field => $value) {
|
|
$concat.= is_array($data[$field]) ? implode("", $data[$field]) : ($data[$field] ?? "__NULL__");
|
|
} } }
|
|
return hash("sha256", $concat);
|
|
}
|
|
|
|
public function Checksum($id = null) {
|
|
if (is_null($id)) {
|
|
return \KV::Get()->get("Routes:".get_called_class().":Checksum");
|
|
} else if (isset($this->index[$id])) {
|
|
return $this->index[$id]->Checksum();
|
|
} else {
|
|
return $this->loadChecksum($id);
|
|
} }
|
|
|
|
public function UpdateChecksum() {
|
|
$checksum = $this->loadChecksum();
|
|
\KV::Get()->set("Routes:".get_called_class().":Checksum", $checksum);
|
|
}
|
|
|
|
public function Remove($id) {
|
|
if ($stmt = \DB::Get()->prepare("DELETE FROM ".$this->Table()." WHERE ID = ?")) {
|
|
$stmt->bind_param("s", $id);
|
|
if ($stmt->execute()) {
|
|
if (1==$stmt->affected_rows) {
|
|
$this->UpdateChecksum();
|
|
return true;
|
|
} else if (0==$stmt->affected_rows) {
|
|
\Response::Get()->Message("Fehler: Es wurde nichts entfernt!");
|
|
} else {
|
|
\Response::Get()->Message("Fehler: Es wurden mehrere Einträge entfernt!");
|
|
} } }
|
|
\Response::Get()->DbError();
|
|
return false;
|
|
}
|
|
|
|
public function Insert($data, &$newid) {
|
|
$fields = [];
|
|
$values = [];
|
|
$types = "";
|
|
$placeholders = [];
|
|
|
|
if ($this->has["uuid"]) {
|
|
$res = \DB::Get()->query("SELECT UUID_SHORT() uuid");
|
|
$fields[] = "ID";
|
|
$values[] = $res->fetch_assoc()["uuid"];
|
|
$types.= "s";
|
|
$placeholders[] = "?";
|
|
}
|
|
|
|
// Set `files` elements in data for initial checksum calculation
|
|
foreach ($this->definitions["files"] as $field => $fallback) {
|
|
$data[$field] = $this->FileGet($field, null);
|
|
}
|
|
|
|
if ($this->has["sha256"]) {
|
|
$fields[] = "CHKSUM";
|
|
$values[] = $this->CalcChecksum($data);
|
|
$types.= "s";
|
|
$placeholders[] = "?";
|
|
}
|
|
|
|
foreach ($this->definitions["fields"] as $field => $def) {
|
|
if (isset($data[$field])) {
|
|
$fields[] = $field;
|
|
$values[] = $data[$field];
|
|
$types.= $def["type"];
|
|
$placeholders[] = "?";
|
|
} }
|
|
|
|
foreach ($this->definitions["keys"] as $key => $def) {
|
|
if (isset($data[$key])) {
|
|
$fields[] = $key;
|
|
$values[] = $data[$key]=="__NULL__" ? null : $data[$key];
|
|
$types.= $def["type"];
|
|
$placeholders[] = "?";
|
|
} }
|
|
|
|
$qry = "INSERT INTO ".$this->Table()." (".implode(", ", $fields).") VALUES (".implode(", ", $placeholders).")";
|
|
|
|
if ($stmt = \DB::Get()->prepare($qry)) {
|
|
$stmt->bind_param($types, ...$values);
|
|
if ($stmt->execute()) {
|
|
$newid = $this->HasUuid() ? $values[0] : \DB::Get()->insert_id;
|
|
$this->UpdateChecksum();
|
|
return true;
|
|
} }
|
|
\Response::Get()->DbError();
|
|
return false;
|
|
}
|
|
|
|
protected function joins() {
|
|
$ret = "";
|
|
foreach ($this->definitions["links"] as $field => $definition) {
|
|
$ret.= " LEFT JOIN ".("\\Links\\".$definition["linkClass"])::Get()->TableWithShort()
|
|
." ON ".("\\Links\\".$definition["linkClass"])::Get()->Short().".`".$this->Ident()."`=".$this->Short().".ID";
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
protected function loadAll($callerList) {
|
|
$qry = "SELECT ID FROM ".$this->TableWithShort().$this->joins().\Filter\Filter::Where();
|
|
|
|
if (($stmt = \DB::Get()->prepare($qry)) && $stmt->execute()) {
|
|
$stmt->bind_result($id);
|
|
while ($stmt->fetch()) {
|
|
$this->Ref($id, $callerList);
|
|
}
|
|
} else {
|
|
\Response::Get()->DbError();
|
|
} }
|
|
|
|
public function HasFile($field) {
|
|
return array_key_exists($field, $this->definitions["files"]);
|
|
}
|
|
|
|
public function FileDirectory($field) {
|
|
return "upl/".$this->Table()."/".$field."/";
|
|
}
|
|
|
|
public function FileGet($field, $id = null) {
|
|
$dir = $this->FileDirectory($field);
|
|
|
|
if (!is_null($id)) {
|
|
$filelist = glob($dir.$id.".*");
|
|
if (sizeof($filelist)>0) {
|
|
return $filelist[0];
|
|
} }
|
|
|
|
$fallback = $this->definitions["files"][$field];
|
|
return is_null($fallback) ? null : $dir.$fallback;
|
|
}
|
|
}
|