Very big commit on the way to version 3 backend

This commit is contained in:
Nils Otterpohl 2023-07-04 22:26:03 +02:00
parent 809fa2d1a4
commit e9b3a8d313
310 changed files with 17887 additions and 8843 deletions

12
.gitmodules vendored Normal file
View File

@ -0,0 +1,12 @@
[submodule "ext/PHPMailer"]
path = ext/PHPMailer
url = https://github.com/PHPMailer/PHPMailer.git
[submodule "ext/php-jwt"]
path = ext/php-jwt
url = https://github.com/firebase/php-jwt.git
[submodule "ext/php-qrcode"]
path = ext/php-qrcode
url = https://github.com/chillerlan/php-qrcode
[submodule "ext/php-settings-container"]
path = ext/php-settings-container
url = https://github.com/chillerlan/php-settings-container.git

1
.htaccess Normal file
View File

@ -0,0 +1 @@
CGIPassAuth On

119
LICENSE
View File

@ -1,119 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
OR WORKS PROVIDED HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive
Copyright and Related Rights (defined below) upon the creator and subsequent
owner(s) (each and all, an "owner") of an original work of authorship and/or
a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later claims
of infringement build upon, modify, incorporate in other works, reuse and
redistribute as freely as possible in any form whatsoever and for any purposes,
including without limitation commercial purposes. These owners may contribute
to the Commons to promote the ideal of a free culture and the further production
of creative, cultural and scientific works, or to gain reputation or greater
distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with
a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or
her Copyright and Related Rights in the Work and the meaning and intended
legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected
by copyright and related or neighboring rights ("Copyright and Related Rights").
Copyright and Related Rights include, but are not limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work, subject
to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal protection
of databases, and under any national implementation thereof, including any
amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time extensions),
(iii) in any current or future medium and for any number of copies, and (iv)
for any purpose whatsoever, including without limitation commercial, advertising
or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
benefit of each member of the public at large and to the detriment of Affirmer's
heirs and successors, fully intending that such Waiver shall not be subject
to revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account Affirmer's
express Statement of Purpose. In addition, to the extent the Waiver is so
judged Affirmer hereby grants to each affected person a royalty-free, non
transferable, non sublicensable, non exclusive, irrevocable and unconditional
license to exercise Affirmer's Copyright and Related Rights in the Work (i)
in all territories worldwide, (ii) for the maximum duration provided by applicable
law or treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional purposes
(the "License"). The License shall be deemed effective as of the date CC0
was applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder of
the License, and in such case Affirmer hereby affirms that he or she will
not (i) exercise any of his or her remaining Copyright and Related Rights
in the Work or (ii) assert any associated claims and causes of action with
respect to the Work, in either case contrary to Affirmer's express Statement
of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or other
defects, accuracy, or the present or absence of errors, whether or not discoverable,
all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
responsibility for obtaining any necessary consents, permissions or other
rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a party
to this document and has no duty or obligation with respect to this CC0 or
use of the Work.

View File

@ -1,2 +0,0 @@
# App

0
bin/errors.php → bin/__errors.php Normal file → Executable file
View File

0
bin/navi.php → bin/__navi.php Normal file → Executable file
View File

63
bin/__print.php Executable file
View File

@ -0,0 +1,63 @@
<?php #bin/print.php
//array $page = (int main, string sub, array css = [strings], array js = [strings])
//Include CSS
$tplcss = tplExtrSection($output["stat"], "###CSS###");
$css = "";
$x = false;
foreach ($output["css"] as $val)
{
$css.= tplReplMarker($tplcss, "###CSSFILE###", $val."?id=".lgnGenSalt()).($x ? "\n" : "");
$x = true;
}
$output["stat"] = tplReplSection($output["stat"], "###CSS###", $css);
//Include Javascript
$tpljs = tplExtrSection($output["stat"], "###JS###");
$js = "";
$x= false;
foreach ($output["js"] as $val)
{
$js.= tplReplMarker($tpljs, "###JSFILE###", $val).($x ? "\n" : "");
$x = true;
}
$output["stat"] = tplReplSection($output["stat"], "###JS###", $js);
//Additional
$output["stat"] = tplReplMarker($output["stat"], "###NAV###", $output["navi"]);
$output["stat"] = tplReplMarker($output["stat"], "###MAIN###", $output["main"]);
$output["stat"] = tplReplMarker($output["stat"], "###PAGETYPE###", $page["main"]=="" ? "frontpage" : "content");
$output["stat"] = tplReplMarker($output["stat"], "###TITLE###", TITLE);
$output["stat"] = tplReplMarker($output["stat"], "###LINKSTART###", "");
$output["stat"] = tplReplMarker($output["stat"], "###PAGEMAIN###", $page["main"]);
$url = parse_url("https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
$output["stat"] = tplReplMarker($output["stat"], "###PAGEURL###", "https://".$url["host"].$url["path"]);
$output["stat"] = tplReplMarker($output["stat"], "###PAGEDOMAIN###", str_replace("www.", "", $url["host"]));
$output["stat"] = tplReplMarker($output["stat"], "###ERRORS###", $output["error"]);
//Print login/logout forms
if ($output["showlogin"]=="in") {
//$tpllgn = tplExtrSection($output["stat"], "###LOGIN###");
$output["stat"] = tplReplSection($output["stat"], "###LOGOFF###", "");
$gets = "";
foreach ($_GET as $key => $value) {
$gets.= ($gets=="" ? "?" : "&").$key."=".$value;
}
$output["stat"] = tplReplMarker($output["stat"], "###PAGEGET###", $gets);
} elseif ($output["showlogin"]=="off") {
//$tpllgn = tplExtrSection($output["stat"], "###LOGOFF###");
$output["stat"] = tplReplSection($output["stat"], "###LOGIN###", "");
}
foreach ($lang as $key => $value) {
$output["stat"] = tplReplMarker($output["stat"], $key, $value[$user["address.plural"]]);
}
$output["stat"] = tplReplMarker($output["stat"], "###SECTOKEN###", $_SESSION["secTokenUse"]);
//Print page
echo $output["stat"];
?>

5
bin/exit.php Normal file → Executable file
View File

@ -1,5 +1,4 @@
<?php #bin/exit.php
$mysqli->close();
?>
$mysqli->close();
$redis->close();

79
bin/init.php Normal file → Executable file
View File

@ -1,70 +1,25 @@
<?php #bin/init.php
//Include external libraries (excl. hidden [.file])
$files["ext"] = scandir("ext");
foreach ($files["ext"] as $val) {
if ("."!=substr($val,0,1) && ".php"==substr($val,strlen($val)-4,4)) {
include "ext/".$val;
} }
//Include libraries (excl. hidden [.file])
$files["lib"] = scandir("lib");
foreach ($files["lib"] as $val)
if (substr($val,0,1)!= ".") include "lib/".$val;
foreach ($files["lib"] as $val) {
if (substr($val,0,1)!= "." && $val!="99_manager_new.php") {
include "lib/".$val;
} }
//Import configuration files (excl. hidden [.file])
$files["var"] = scandir("var");
foreach ($files["var"] as $val)
if (substr($val,0,1)!= ".") include "var/".$val;
foreach ($files["var"] as $val) {
if (substr($val,0,1)!= ".") {
include "var/".$val;
} }
error_reporting(ERRORLVL);
ini_set("display_errors", 1);
lgnSecSessionStart(); // Unsere selbstgemachte sichere Funktion um eine PHP-Sitzung zu starten
lgnRegenerateToken(); // Erzeugt neuen Token
//Clean input
function cleanInput($array) {
$ret = array();
foreach ($array as $key => $value) {
$ret[$key] = is_array($value) ? cleanInput($value) : (is_string($value) ? SS($value) : SI($value));
}
return $ret;
}
$input = cleanInput($_REQUEST);
//Check login attempt
if (isset($input["login"],$input["pass"],$input["submit"]) && $input["secToken"]==$_SESSION["secTokenVerify"])
{
//Login attempt
if (!lgnLogin($mysqli, $input["login"], $input["pass"]))
{
addError("Login Failed: ", $mysqli->error);
}
}
//Init main variables
//array $page = (string main, string sub, array css = [strings], array js = [strings])
//array $output = (string stat, string navi, string main)
//array $temp = (EMPTY) - can be used for temporary variables
$page = array
(
"main" => "",
"sub" => ""
);
$output = array
(
"stat" => tplLoadFile(FRAMETPL), //Load Mainframe
"main" => "",
"navi" => "",
"error" => "",
"css" => array(),
"js" => array(),
"showlogin" => "in"
);
addStyle(FRAMECSS, FRAMECSS!="");
addJScript(FRAMEJS, FRAMEJS!="");
$temp = array();
$user = array(
"address.plural" => 1
);
if (isset($input["userMod"]) && $input["userMod"]=="Ausloggen")
{
lgnLogout();
}
?>
// Authenticate user and prepare input/output
$man = new Manager($mysqli, $redis, JWT_KEY);

22
bin/init_new.php Normal file
View File

@ -0,0 +1,22 @@
<?php #bin/init.php
//Include external libraries (excl. hidden [.file])
$files["ext"] = scandir("ext");
foreach ($files["ext"] as $val) {
if ("."!=substr($val,0,1) && ".php"==substr($val,strlen($val)-4,4)) {
include "ext/".$val;
} }
//Include libraries (excl. hidden [.file])
$files["lib"] = scandir("lib_new");
foreach ($files["lib"] as $val) {
if (substr($val,0,1)!= ".") {
include "lib_new/".$val;
} }
//Import configuration files (excl. hidden [.file])
$files["conf"] = scandir("conf");
foreach ($files["conf"] as $val) {
if (substr($val,0,1)!= ".") {
include "conf/".$val;
} }

View File

@ -1,63 +1,259 @@
<?php #bin/print.php
<?php #ajax/print.php
//array $page = (int main, string sub, array css = [strings], array js = [strings])
require_once "../bin/init_ajax.php";
require_once "../lib/content.php";
//Include CSS
$tplcss = tplExtrSection($output["stat"], "###CSS###");
$css = "";
$x = false;
foreach ($output["css"] as $val)
{
$css.= tplReplMarker($tplcss, "###CSSFILE###", $val."?id=".lgnGenSalt()).($x ? "\n" : "");
$x = true;
}
$output["stat"] = tplReplSection($output["stat"], "###CSS###", $css);
function getContent($key, $getText = false, $demoMode = false) {
global $content, $data, $links, $refs, $options;
//Include Javascript
$tpljs = tplExtrSection($output["stat"], "###JS###");
$js = "";
$x= false;
foreach ($output["js"] as $val)
{
$js.= tplReplMarker($tpljs, "###JSFILE###", $val).($x ? "\n" : "");
$x = true;
}
$output["stat"] = tplReplSection($output["stat"], "###JS###", $js);
//Additional
$output["stat"] = tplReplMarker($output["stat"], "###NAV###", $output["navi"]);
$output["stat"] = tplReplMarker($output["stat"], "###MAIN###", $output["main"]);
$output["stat"] = tplReplMarker($output["stat"], "###PAGETYPE###", $page["main"]=="" ? "frontpage" : "content");
$output["stat"] = tplReplMarker($output["stat"], "###TITLE###", TITLE);
$output["stat"] = tplReplMarker($output["stat"], "###LINKSTART###", "");
$output["stat"] = tplReplMarker($output["stat"], "###PAGEMAIN###", $page["main"]);
$url = parse_url("https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
$output["stat"] = tplReplMarker($output["stat"], "###PAGEURL###", "https://".$url["host"].$url["path"]);
$output["stat"] = tplReplMarker($output["stat"], "###PAGEDOMAIN###", str_replace("www.", "", $url["host"]));
$output["stat"] = tplReplMarker($output["stat"], "###ERRORS###", $output["error"]);
//Print login/logout forms
if ($output["showlogin"]=="in") {
//$tpllgn = tplExtrSection($output["stat"], "###LOGIN###");
$output["stat"] = tplReplSection($output["stat"], "###LOGOFF###", "");
$gets = "";
foreach ($_GET as $key => $value) {
$gets.= ($gets=="" ? "?" : "&").$key."=".$value;
$ret = "";
if (array_key_exists($key, $content)) {
if ($getText && isset($options[$key])) {
if (is_array($content[$key])) {
$ret = array();
foreach ($options[$key] as $option) {
if (in_array($option["value"], $content[$key])) {
$ret[] = $option["display"];
}
}
} else {
foreach ($options[$key] as $option) {
$ret = "&nbsp;";
if ($option["value"]==$content[$key]) {
$ret = $option["display"];
break;
}
}
}
} else {
$ret = $content[$key];
}
} else if (isset($data[$key])) {
if (isset($data[$key]["default"]) && $data[$key]["default"]!="") {
$ret = $data[$key]["default"];
} else if ($demoMode) {
$ret = $data[$key]["title"];
}
} else if (isset($links[$key])) {
$ret = array();
} else if (isset($refs[$key])) {
$ret = array();
} else {
$ret = $key;
}
$output["stat"] = tplReplMarker($output["stat"], "###PAGEGET###", $gets);
} elseif ($output["showlogin"]=="off") {
//$tpllgn = tplExtrSection($output["stat"], "###LOGOFF###");
$output["stat"] = tplReplSection($output["stat"], "###LOGIN###", "");
if (isset($data[$key]["prefix"]) && !is_array($ret)) {
$ret = $data[$key]["prefix"].$ret;
}
if (isset($data[$key]["postfix"]) && !is_array($ret)) {
$ret.= $data[$key]["postfix"];
}
return $ret;
}
foreach ($lang as $key => $value) {
$output["stat"] = tplReplMarker($output["stat"], $key, $value[$user["address.plural"]]);
function moveIfOverlap(&$floating, &$left, &$top, $width, $height, $dir = "none", $lastdir = "none") {
$moved = false;
foreach ($floating as $key => $float) {
if (
$left < $float["right"] &&
$left+$width > $float["left"] &&
$top < $float["bottom"] &&
$top+$height > $float["top"]
) {
if ($float["dir"]=="left" && $lastdir!="right") {
$left = $float["right"];
} else if ($float["dir"]=="right" && $lastdir!="left") {
$left = $float["left"] - $width;
} else if ($float["dir"]=="up" && $lastdir!="down") {
$top = $float["bottom"];
} else if ($float["dir"]=="down" && $lastdir!="up") {
$top = $float["top"] - $height;
}
$lastdir = $float["dir"];
$moved = true;
}
}
if ($moved) {
moveIfOverlap($floating, $left, $top, $width, $height, $dir, $lastdir);
} else if ($dir!="none") {
$floating[] = array(
"left" => $left,
"right" => $left + $width,
"top" => $top,
"bottom" => $top + $height,
"dir" => $dir
);
}
}
$output["stat"] = tplReplMarker($output["stat"], "###SECTOKEN###", $_SESSION["secTokenUse"]);
if (lgnCheckLogin($mysqli) && isset($_POST["page"], $_POST["layout"]) && $_POST["layout"]!="") {
$page = $_POST["page"];
$data = $_POST["data"];
$links = $_POST["links"] ?? array();
$refs = $_POST["refs"] ?? array();
$options = $_POST["options"];
//Print page
echo $output["stat"];
$content = array();
$id = null;
if (isset($_POST["ID"]) && $_POST["ID"]!="") {
$id = $_POST["ID"];
$cntRow = cntGetRow($mysqli, $page, $id, $links, $refs);
$content = $cntRow["erg"];
if ($cntRow["err"]!="") {
echo "<div>".$cntRow["err"]."</div>";
}
}
$res = $mysqli->query("select * from print_layouts where ID='".$_POST["layout"]."'");
$layout = $res->fetch_assoc();
$floating = array();
echo "<div style='float: none; position: relative; display: inline-block; vertical-align: top; width: ".($layout["width"]/10)."mm; height: ".($layout["height"]/10)."mm; border: 1px solid #000000; background-color: #ffffff;'>";
$res = $mysqli->query("select * from print_elements where key_layout_ID='".$layout["ID"]."' order by ord asc");
while ($row = $res->fetch_assoc()) {
$do_print = false;
$cond = explode("|", $row["cond"]);
if ($row["params"]=="") {
$row["params"] = "{}";
}
$params = json_decode($row["params"], true);
switch ($cond[0]) {
case null:
case "":
case "0": //Print always
$do_print = true;
break;
case "1": //Field has to be NOT null and not empty text
if (sizeof($cond)>1 && (getContent($cond[1])!=null && getContent($cond[1], true)!=""))
$do_print = true;
break;
case "2": //Field has to be null or empty text
if (sizeof($cond)>1 && (getContent($cond[1])==null || getContent($cond[1], true)==""))
$do_print = true;
break;
case "3": //Field has to be value (second parameter) or include value if is link
if (sizeof($cond)>2 && ((array_key_exists($cond[1], $links) && in_array($cond[2], getContent($cond[1]))) || (getContent($cond[1])==$cond[2])))
$do_print = true;
break;
case "4": //Field has to be NOT value (second parameter) or DONT include value if is link
if (sizeof($cond)>2 && ((array_key_exists($cond[1], $links) && !in_array($cond[2], getContent($cond[1]))) && (getContent($cond[1])!=$cond[2])))
$do_print = true;
break;
case "5": //Field has to be text (second parameter) or include text if it is link
if (sizeof($cond)>2 && ((array_key_exists($cond[1], $links) && in_array($cond[2], getContent($cond[1], true))) || (getContent($cond[1], true)==$cond[2])))
$do_print = true;
break;
case "6": //Field has to be NOT text (second parameter) or DONT include text if it is link
if (sizeof($cond)>2 && ((array_key_exists($cond[1], $links) && !in_array($cond[2], getContent($cond[1], true))) && (getContent($cond[1], true)!=$cond[2])))
$do_print = true;
break;
}
if ($id===null) {//Demo mode
$do_print = true;
}
$print_count = 0;
if ($do_print) {
$print_count = $params["repeat"] ?? 1;
}
while ($print_count>0) {
$print_count--;
$border = array();
$border["top"] = isset($params["border-top"]) ? $params["border-top"] : (isset($params["border"]) ? $params["border"] : "0");
$border["bottom"] = isset($params["border-bottom"]) ? $params["border-bottom"] : (isset($params["border"]) ? $params["border"] : "0");
$border["left"] = isset($params["border-left"]) ? $params["border-left"] : (isset($params["border"]) ? $params["border"] : "0");
$border["right"] = isset($params["border-right"]) ? $params["border-right"] : (isset($params["border"]) ? $params["border"] : "0");
$border["color"] = isset($params["border-color"]) ? $params["border-color"] : "#000000";
$border["string"] = "";
if ($border["left"]!="0") {
$border["string"].= "border-left: 1px solid ".$border["color"]."; ";
}
if ($border["right"]!="0") {
$border["string"].= "border-right: 1px solid ".$border["color"]."; ";
}
if ($border["top"]!="0") {
$border["string"].= "border-top: 1px solid ".$border["color"]."; ";
}
if ($border["bottom"]!="0") {
$border["string"].= "border-bottom: 1px solid ".$border["color"]."; ";
}
$cnt = $row["content"];
$fields = array();
if (preg_match_all("/#([^#]+)#/", $cnt, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$replace = "";
$fields[] = $match[1];
if (array_key_exists($match[1], $links)) {
$replace = implode(", ", getContent($match[1], true, $id===null));
} else if (array_key_exists($match[1], $data)) {
$replace = getContent($match[1], true, $id===null);
} else {
foreach ($data as $key => $val) {
if ($val["group"]==$match[1]) {
$replace.=getContent($key, true, $id===null);
}
}
}
$cnt = str_replace("#".$match[1]."#", $replace, $cnt);
}
}
$left = $row["posl"];
$top = $row["post"];
$width = $row["dimw"];
if ($width == 0) {
$width = 10;
}
$height = $row["dimh"];
if ($height == 0) {
$height = 10;
}
moveIfOverlap($floating, $left, $top, $width, $height, isset($params["float"]) ? $params["float"] : "none");
switch ($row["type"]) {
case "img":
if (isset($params["aspect"]) && $params["aspect"]=="keep") {
$imgsize = "max-width: ".$width."mm; max-height: ".$height."mm; width: auto; height: auto; ";
}
if (isset($params["aspect"]) && $params["aspect"]=="height") {
$imgsize = "height: ".$height."mm; width: auto; ";
} else {
$imgsize = "width: ".$width."mm; height: ".$height."mm; ";
}
$path = $cnt;
if (count($fields)) {
$path = "uploads/".$page."/".$fields[0]."/".$cnt;
}
if (file_exists("../".$path)) {
echo "<img src='".$path."' style='position: absolute; left: ".$left."mm; top: ".$top."mm; ".$imgsize."' />";
}
break;
case "text":
$rotate = "";
if (isset($params["rotate"]) && $params["rotate"]=="1") {
$rotate = "transform: rotate(-90deg); ";
$left = "calc(".($left + ($height - $width)/2)."mm - ".$border["left"]."px); ";
$top = "calc(".($top + ($width - $height)/2)."mm - ".$border["top"]."px); ";
} else {
$left.= "mm; ";
$top.= "mm; ";
}
$font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
$font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
if (isset($params["weight"])) {
$font.= "font-weight: ".$params["weight"]."; ";
}
if (isset($params["align"])) {
$font.= "text-align: ".$params["align"]."; ";
}
echo "<span style='position: absolute; left: ".$left."top: ".$top."width: ".$width."mm; height: ".$height."mm; ".$font.$border["string"].$rotate."'>".$cnt."</span>";
break;
}
}
}
echo "</div>";
}
?>

1
client/dropzone.min.js vendored Normal file

File diff suppressed because one or more lines are too long

16
client/filter.js Normal file
View File

@ -0,0 +1,16 @@
class APIFilter // This holds ONE filter element
{
constructor(data, type) {
this.Set(data, type);
}
Get() {
return {and: this.and, field: this.field, op: this.op, value: this.value};
}
Set(data, type) {
this.and = typeof(data.and)!="undefined" ? data.and : "and";
this.field = typeof(data.field)!="undefined" ? data.field : "none";
this.op = typeof(data.op)!="undefined" ? data.op : "";
this.value = typeof(data.value)!="undefined" ? data.value : ("Multiple"==type ? [] : "");
}
}

81
client/index.html Normal file
View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="expires" content="0">
<meta name="author" content="Nils Otterpohl" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>OF56-App (Client v3)</title>
<link rel="icon" href="/wappen48.png" type="image/png">
<link rel="stylesheet" type="text/css" href="/client/main.css" />
<link rel="stylesheet" type="text/css" href="/client/toastify.css" />
<script src="/client/toastify.js"></script>
<script src="/client/dropzone.min.js"></script>
<script src="/client/qr-scanner.umd.min.js"></script>
<script src="/client/template.js"></script>
<script src="/client/scanner.js"></script>
<script src="/client/filter.js"></script>
<script src="/client/page.js"></script>
<!--<script src="/client/pages/start.js"></script>-->
<!--<script src="/client/pages/termine.js"></script>-->
<!--<script src="/client/pages/dienstpläne.js"></script>-->
<script src="/client/pages/personal.js"></script>
<!--<script src="/client/pages/prozesse.js"></script>-->
<!--<script src="/client/pages/spinde.js"></script>-->
<!--<script src="/client/pages/einsätze.js"></script>-->
<!--<script src="/client/pages/anwesenheiten.js"></script>-->
<script src="/client/pages/fahrzeuge.js"></script>
<script src="/client/root.js"></script>
</head>
<body>
<header id="header" style="display: none; opacity: 0;">
<div id="loginbox">
<form>
<center>
<input id="loginUser" class="login" type="text" name="username" autocomplete="username" /><br />
<input id="loginPassword" class="login" type="password" autocomplete="current-password" /><br />
<input id="loginButton" type="submit" class="login" value="Einloggen" />
</center>
</form>
<p>
Willkommen! Bitte Anmeldedaten von IServ verwenden! (vorname.nachname)<br />
Sollte der Login nicht klappen, bitte versuchen ob die Zugangsdaten auf
<a href="https://feuerwehr-bs.net">feuerwehr-bs.net</a> funktionieren und danach bei Nils melden.
</p>
</div>
</header>
<nav>
<ul id="navigation">
<li id="nav_start" class="current"><img src="/wappen48.png" title="Start"></li>
<li id="nav_termine" style="display: none;">Termine</li>
<li id="nav_dienstpläne" style="display: none;">Dienstpläne</li>
<li id="nav_personal" style="display: none;">Personal</li>
<li id="nav_prozesse" style="display: none;">Prozesse</li>
<li id="nav_spinde" style="display: none;">Spinde</li>
<li id="nav_einsätze" style="display: none;">(Einsätze)</li>
<li id="nav_anwesenheiten" style="display: none;">(Anwesenheiten)</li>
<li id="nav_fahrzeuge" style="display: none;">(Fahrzeuge)</li>
<li id="nav_logout"><img src="/res/dark/logout.png" title="Ausloggen"></li>
</ul>
<select id="navigation_select">
<option id="nav_sel_start" value="">☰ Start</option>
<option id="nav_sel_termine" value="Termine" style="display: none;">☰ Termine</option>
<option id="nav_sel_dienstpläne" value="Dienstpläne" style="display: none;">☰ Dienstpläne</option>
<option id="nav_sel_personal" value="Personal" style="display: none;">☰ Personal</option>
<option id="nav_sel_prozesse" value="Prozesse" style="display: none;">☰ Prozesse</option>
<option id="nav_sel_spinde" value="Spinde" style="display: none;">☰ Spinde</option>
<option id="nav_sel_einsätze" value="Einsätze" style="display: none;">☰ (Einsätze)</option>
<option id="nav_sel_anwesenheiten" value="Anwesenheiten" style="display: none;">☰ (Anwesenheiten)</option>
<option id="nav_sel_fahrzeuge" value="Fahrzeuge" style="display: none;">☰ (Fahrzeuge)</option>
<option id="nav_sel_logout" value="_LOGOUT_">Logout</option>
</select>
</nav>
<main id="content"></main>
<div id="qrscanner">
<img id="qrscanner_close" src="/res/dark/cancel.png">
<img id="qrscanner_camera" src="/res/dark/refresh.png">
<div id="qrscanner_text"></div>
<video id="qrscanner_video"></video>
</div>
</body>
</html>

247
client/main.css Normal file
View File

@ -0,0 +1,247 @@
/* tpl/main.css */
body {
font-family: arial;
font-size: 20px;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #000;
color: #fff;
}
header {
background: #000 url(/res/frontpage.jpg) center no-repeat;
background-size: cover;
width: 100%;
height: 100vh;
}
#loginbox {
position: absolute;
width: 18em;
left: calc(50% - 9em);
bottom: 5em;
}
#loginbox p {
background-color: #000;
text-align: center;
padding: 0.2em;
border-radius: 0.2em;
}
a {color: #fff; text-decoration: underline;}
ul {list-style: none; padding: 0; margin: 0;}
li {
display: inline-block;
padding: 0.2em;
margin: 0.1em;
border-radius: 0.2em;
}
li img {height: 0.8em;}
li > img {margin: 0 0.4em 0 0.2em;}
.button > img, li > .button {margin: 0 0.2em 0 0.2em;}
li .button {
padding: 0 0.1em;
background-color: #000;
border-radius: 0.2em;
border: 0.05em solid #fff;
}
.button {border: 0.05em solid #fff; background-color: #000; cursor: pointer;}
.button:hover {background-color: #333;}
nav {
vertical-align: middle;
text-align: left;
background-color: #9b0000;
position: sticky;
top: 0;
z-index: 100;
}
nav ul {padding: 0 0.5em;}
nav li {
padding: 0.5em 0.75em;
font-weight: bold;
margin: 0;
background-color: #9b0000;
border: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
cursor: pointer;
}
nav li:hover {background-color: #f00;}
nav li.current {background-color: #000;}
#navigation_select {
display:none;
background-color: #9b0000;
font-weight: bold;
padding: 0.5em 0.75em;
width: 100%;
}
main {padding: 0 1.75em;}
article {margin: 0 0 2em 0; max-width: 60em;}
article:first-of-type {margin: 2em 0 2em 0;}
article img {max-width: 100%;}
h1 img {height: 0.8em;}
input, select {
background-color: #000;
color: #fff;
font-size: 100%;
padding: 0.1em;
border: 1px solid #fff;
box-sizing: border-box;
}
.list_entry select {height: 1.4em;}
input[type="submit"], button {background-color: #9b0000; color: #fff; font-size: 100%;}
.login {
width: 12em;
text-align: center;
border: 0.1em solid #fff;
padding: 0.2em;
margin: 0.1em;
box-sizing: content-box;
}
.disabled, input[type="submit"].disabled {
background-color: #333;
}
a.toggleVisibility {text-decoration: none;}
h1 {
background-color: #333;
display: inline;
padding: 0.3em 0.6em;
border-radius: 0.2em;
}
div.toggleVisibility {
padding: 1em;
border: 0.3em solid #333;
border-radius: 0.2em;
}
.group_title {
border-bottom: none;
border-left: 0.3em solid #333;
border-right: 0.3em solid #333;
padding: 0.5em;
border-top: 0.2em solid #333;
border-top-right-radius: 0.3em;
border-top-left-radius: 0.3em;
background-color: #333;
}
.group_title h1 {
padding-left: 0;
}
.list_entry_row {
border-bottom: 0.05em dashed #333;
border-left: 0.3em solid #333;
border-right: 0.3em solid #333;
border-top: 0;
padding: 0.2em;
min-height: 1.733em;
}
.list_entry_row ul {display: inline;}
.list_entry_row .block {display: block;}
.list_entry {border-bottom: 0.05em dotted #333;}
.list_entry:last-of-type {border-bottom: none;}
.list_entry_title {
border-bottom: none;
border-left: 0.3em solid #333;
border-right: 0.3em solid #333;
padding: 0.2em;
border-top: 0.1em solid #333;
background-color: #333;
}
.list_entry .list_entry_row:last-of-type {border-bottom: none;}
.list_entry:last-of-type .list_entry_row:last-of-type {border-bottom: 0.3em solid #333;}
/*.list_entry:last-of-type .list_entry_title:last-of-type {border-bottom: 0.05em solid #888;}*/
.list_entry_title li {background-color: #000;}
.list_entry:first-of-type .list_entry_title {border-top-right-radius: 0.2em;}
.list_entry:last-of-type div:last-of-type {border-bottom-right-radius: 0.2em; border-bottom-left-radius: 0.2em;}
.list_selected div {border-color: #9b0000;}
.list_selected .list_entry_title {background-color: #9b0000;}
.list_entry .bild {
position: relative;
float: right;
display: flex;
align-items: center;
justify-content: center;
height: 4em;
min-width: 4em;
padding: 0;
background-color: #fff;
overflow: hidden;
}
.list_entry .bild img {
position: absolute;
z-index: 1;
max-height: 4em;
max-width: 4em;
}
.list_entry .bild form {
position: absolute;
z-index: 10;
top: 0;
left: 0;
height: 4em;
width: 4em;
opacity: 0.6;
background-color: #fff;
}
.list_entry .bild form button {
height: 4em;
width: 4em;
padding: 0;
}
.list_entry .bild .deleter {
position: absolute;
z-index: 20;
bottom: -0.2em;
right: -0.2em;
background-color: #000;
border: 0.05em solid #fff;
border-radius: 0.2em;
}
.list_entry .bild .deleter img {position: relative;height: 0.8em;}
.list_entry .bild .dz-default {max-height: 100%;}
#qrscanner {
display: none;
position: fixed;
left: 2em;
right: 2em;
top: 2em;
bottom: 2em;
width: auto;
height: auto;
z-index: 100;
background-color: #555;
padding: 2em;
align-items: center;
flex-direction: row;
justify-content: space-around;
}
#qrscanner_text, #qrscanner_video {/*width:100%;*/}
#qrscanner_close {position: fixed; z-index: 999; right: 2.5em; top: 2.5em;}
#qrscanner_video {max-height: 80%; max-width: 80%;}
.chocolate {background-color: #840;}
.merlot {background-color: #804;}
.mughal {background-color: #480;}
.philippine {background-color: #084;}
.blue {background-color: #048;}
.indigo {background-color: #408;}
.purple {background-color: #404;}
.olive {background-color: #440;}
.teal {background-color: #044;}
.red {background-color: #400;}
.pacific {background-color: #004;}
.green {background-color: #040;}
@media only screen and (orientation: portrait) {
body {font-size: 4vw;}
nav {font-size: 6vw;}
#navigation_select {display:inline;font-size: 6vw;}
#navigation {display:none;}
main {padding: 0 1em;}
nav li {padding: 1em 0.35em;}
table {font-size: 4vw;}
input,button,select {font-size: 4vw;}
input[type=radio] {border: 0; height: 1em;}
#qrscanner {flex-direction: column; padding: 0;}
#qrscanner_video {max-height: 40%; max-width: 100%;}
}

399
client/page.js Normal file
View File

@ -0,0 +1,399 @@
class Page
{
constructor(groupName, json, route, array, groups, subs, selects, globalAdmin) {
this.group = groups[groupName];
this.groupName = groupName;
this.ID = json.ID;
this.route = route;
this.array = array;
this.groups = groups;
this.subs = subs;
this.selects = selects;
this.admin = globalAdmin || false;//json.admin; // Local admin rights
this.init(json);
this.initSubs(json);
}
/* Static functions */
static Open() {
document.getElementById("nav_" + Root.instance.page.toLowerCase()).classList.remove("current");
document.getElementById("nav_" + this.route.toLowerCase()).classList.add("current");
Root.instance.page = this.route;
document.getElementById("navigation_select").value = this.route;
//this.FetchOptions();
this.Load();
}
static SetVisibility(visible) {
this.visible = visible;
document.getElementById("nav_"+this.route.toLowerCase()).style.display = visible ? "inline" : "none";
document.getElementById("nav_sel_"+this.route.toLowerCase()).style.display = visible ? "inline" : "none";
}
static UpdateData(json) {
//this.meta = json.meta;
this.prints = json.prints;
this.admin = json.admin;
for (let sub in this.subs) {
this.subs[sub].entries = json.options[sub.toUpperCase()];
}
for (let sel in this.selects) {
this.selects[sel] = json.options[sel.toUpperCase()];
}
this.visible = true;
/*this.selected = [];
if ("undefined"!=typeof(json.selected)) {
this.selected = json.selected;
}*/
for (var k in this.filter) {
delete this.filter[k];
}
this.filter = [];
for (var l in json.filter) {
this.filter.push(new APIFilter(json.filter[l], "none"!=json.filter[l].field ? this.mainFields[json.filter[l].field].type : "none"));
}
return true;
}
static Render() {
let html = "";
if (this.admin) {
html+= "<article id='" + this.route + "/Add'>" + this.RenderAdd() + "</article>";
}
for (const group in this.groups) {
const groupHtml = this.RenderGroup(group);
html+= groupHtml.begin;
for (const e in this.groups[group]) {
const entry = this.groups[group][e];
html+= entry.renderContainer()/*Entry(false, true)*/;
}
html+= "</article>";
}
document.getElementById("content").innerHTML = html;
for (const ID in this.array) {
this.array[ID].renderEntry();
this.array[ID].addEntryEvents();
this.array[ID].renderSubs();
this.array[ID].addSubEvents();
}
const _this = this;
Root.AddEventListenerIfButtonExists(this.route + "/Add/Add", function (event) {_this.Add();});
}
static Value(id, name) {
const e = document.getElementById(this.route+"/"+id+"/"+name);
return (e!==null ? e.value : null);
}
static Add(contextId = null) {
let request = new XMLHttpRequest();
let json = this.Collect("Add", contextId);
json.secToken = Root.instance.secToken;
const _this = this;
request.open("POST", Root.instance.url + this.route);
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
//_this.UpdateData(json);
if (typeof(_this.groups[json.content.GROUP])=="undefined") {
_this.groups[json.content.GROUP] = [];
const groupHtml = _this.RenderGroup(json.content.GROUP);
document.getElementById("content").insertAdjacentHTML("beforeend", groupHtml.begin + groupHtml.end);
}
const group = _this.groups[json.content.GROUP];
_this.array[json.content.ID] = _this.New(json.content.GROUP, json.content);
group.push(_this.array[json.content.ID]);
// Add html
var elList = document.getElementById("Group/" + json.content.GROUP);
elList.insertAdjacentHTML("beforeend", _this.array[json.content.ID].renderEntry(false, true));
_this.array[json.content.ID].addEntryEvents();
} };
request.send(JSON.stringify(json));
}
static Load(id = null) {
let request = new XMLHttpRequest();
const _this = this;
let path = Root.instance.url + this.route + (id !== null ? "/"+id : "");
if (this.filter.length>0) {
path += "?filter="+encodeURI(JSON.stringify(this.getFilter()));
}
request.open("GET", path);
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
if (id!==null) {
_this.array[id].init(json.content);
_this.Reset();
} else {
_this.UpdateData(json);
for (let g in json.content) {
const groupID = json.content[g].ID;
_this.groups[groupID] = [];
let group = _this.groups[groupID];
for (let i in json.content[g].ENTRIES) {
_this.array[json.content[g].ENTRIES[i].ID] = _this.New(groupID, json.content[g].ENTRIES[i]);
group.push(_this.array[json.content[g].ENTRIES[i].ID]);
} } } }
_this.Render();
}
request.send();
}
/* Public functions */
Save() {
const _this = this;
let request = new XMLHttpRequest();
const json = this.collect();
json.secToken = Root.instance.secToken;
request.open("PATCH", Root.instance.url + this.marker());
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
//_this.UpdateData(json);
if (typeof(_this.groups[json.content.GROUP])=="undefined") {
_this.groups[json.content.GROUP] = [];
const groupHtml = _this.RenderGroup(json.content.GROUP);
document.getElementById("content").insertAdjacentHTML("beforeend", groupHtml.begin + groupHtml.end);
}
if (_this.groupName!=json.content.GROUP) {
_this.removeFromGroup();
let element = document.getElementById(_this.marker());
let groupel = document.getElementById("Group/" + json.content.GROUP);
_this.groups[json.content.GROUP].push(_this);
groupel.appendChild(element);
}
_this.init(json.content);
_this.Show(false);
} };
request.send(JSON.stringify(json));
}
Delete() {
const _this = this;
let request = new XMLHttpRequest();
request.open("DELETE", Root.instance.url + this.marker());
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
_this.removeFromGroup();
delete _this.array[_this.ID];
let element = document.getElementById(_this.marker());
element.parentNode.removeChild(element);
} };
request.send(JSON.stringify({secToken: Root.instance.secToken}));
}
Show(edit = false) {
this.renderEntry(edit, false);
this.addEntryEvents(edit);
}
SubShow(sub, edit) {
document.getElementById(this.marker(sub, "Add")).innerHTML = this.renderSubAdd(sub, edit);
this.addSubEvents(edit);
}
SubAdd(sub) {
const id = document.getElementById(this.marker(sub, "Add", sub)).value;
let request = new XMLHttpRequest();
const _this = this;
request.open("POST", Root.instance.url + this.marker(sub, id));
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
_this.updateSubs(sub, json.content.SUB[sub.toUpperCase()]);
} };
request.send(JSON.stringify({secToken: Root.instance.secToken}));
}
SubDelete(sub, id) {
let request = new XMLHttpRequest();
const _this = this;
request.open("DELETE", Root.instance.url + this.marker(sub, id));
request.setRequestHeader("Authorization", "Bearer " + Root.instance.jwt);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
const {ok, json} = Root.instance.FinishResponse(request);
if (ok) {
_this.updateSubs(sub, json.content.SUB[sub.toUpperCase()]);
} };
request.send(JSON.stringify({secToken: Root.instance.secToken}));
}
/* "Private" functions */
initSubs(json) {
for (const s in this.subs) {
this[s] = new Set();
for (let i in json.SUB[s.toUpperCase()]) {
this[s].add(json.SUB[s.toUpperCase()][i].ID);
} } }
updateSubs(sub, newSubList) {
this[sub].clear();
for (let i in newSubList) {
this[sub].add(newSubList[i].ID);
}
this.renderSubs(sub);
this.addSubEvents(sub);
}
// first = action or subroute, second = subID, third = subAction
marker(firstAppend = null, secondAppend = null, thirdAppend = null) {
let id = this.route + "/" + this.ID;
if (firstAppend!==null) {
id+= "/" + firstAppend;
if (secondAppend!==null) {
id+= "/" + secondAppend;
if (thirdAppend!==null) {
id+= "/" + thirdAppend;
} } }
return id;
}
renderContainer() {
let html = "<div id='" + this.marker() + "' class='list_entry'>"
+ "<div id='" + this.marker("Main") + "' class='list_entry_title'></div>";
if (Object.keys(this.subs).length>0) {
html+= "<div class='list_entry_row'>";
for (let s in this.subs) {
html+= "<ul id='" + this.marker(s) + "'></ul>";
}
html+= "</div>";
}
html+= "</div>";
return html;
}
renderField(icon, value) {
return "<li><img src='/res/dark/" + icon + "' />" + value + "</li>";
}
renderInput(name, value, width = 8, maxLength = null) {
return "<input id='" + this.marker(name) + "' value='" + value + "' "
+ (maxLength!==null ? " maxlength=" + maxLength : "")
+ " style='width:"+ width + "em;'/>";
}
renderButton(icon, name, title) {
return "<li id='" + this.marker(name) + "' class='button' title='" + title + "'><img src='/res/dark/" + icon + "' /></li>"
}
renderSelect(name, selected, width = 8) {
let html = "<select id='" + this.marker(name) + "' style='width: " + width + "em;'>";
for (const e in this.selects[name]) {
const entry = this.selects[name][e];
html+= "<option value='" + entry.ID + "'" + (entry.ID==selected ? " selected" : "") + ">" + (typeof(entry.KÜRZEL)!="undefined" ? entry.KÜRZEL : entry.NAME) + "</option>";
}
return html+"</select>";
}
renderSubs(onlyOneSub = null) {
// TODO: SUBS
let subs = {};
if (onlyOneSub===null) {
subs = this.subs;
} else {
subs[onlyOneSub] = this.subs[onlyOneSub];
}
for (let s in subs) {
let html = "";
if (typeof(this.subs[s])!="undefined") {
for (let e in this.subs[s].entries) {
const entry = this.subs[s].entries[e];
if (this[s].has(entry.ID)) {
html+= "<li class='" + this.subs[s].color + "' title='" + entry.NAME + "'><img src='/res/dark/" + this.subs[s].icon + "' />"
+ (typeof(entry.KÜRZEL)!="undefined" ? entry.KÜRZEL : entry.NAME)
+ (this.admin ? " <img id='" + this.marker(s, entry.ID, "Delete") + "' src='/res/dark/delete.png' class='button' />" : "")
+ "</li>";
} }
if (this.admin) {
html+= "<li class='" + this.subs[s].color + "' id='" + this.marker(s, "Add") + "'>" + this.renderSubAdd(s, false) + "</li>";
}
document.getElementById(this.marker(s)).innerHTML = html;
} } }
renderSubAdd(sub, add) {
let html = "<img src='/res/dark/" + this.subs[sub].icon + "' />";
if (add) {
html+= "<select id='" + this.marker(sub, "Add", sub) + "'>";
for (let i in this.subs[sub].entries) {
const entry = this.subs[sub].entries[i];
if (!this[sub].has(entry.ID)) {
html+= "<option value='" + entry.ID + "'>" + entry.NAME + "</option>";
} }
html+= "</select>";
html+= "<img class='button' id='" + this.marker(sub, "Add/Add") + "' src='/res/dark/save.png' />";
html+= "<img class='button' id='" + this.marker(sub, "Add/Reset") + "' src='/res/dark/cancel.png' />";
} else {
html+= "<img class='button' id='" + this.marker(sub, "Add/Show") + "' src='/res/dark/add.png' />";
}
return html;
}
addSubEvents() {
const _this = this;
for (let sub in this.subs) {
for (const id of this[sub]) {
Root.AddEventListenerIfButtonExists(this.marker(sub, id, "Delete"), function (event) {_this.SubDelete(sub, id);});
}
Root.AddEventListenerIfButtonExists(this.marker(sub, "Add/Show"), function (event) {_this.SubShow(sub, true);});
Root.AddEventListenerIfButtonExists(this.marker(sub, "Add/Add"), function (event) {_this.SubAdd(sub);});
Root.AddEventListenerIfButtonExists(this.marker(sub, "Add/Reset"), function (event) {_this.SubShow(sub, false);});
} }
removeFromGroup() {
for (let i in this.groups[this.group]) {
if (this.groups[this.group][i].ID==this.ID) {
delete _this.groups[this.group][i];
break;
} } }
addEntryEvents(edit) {
const _this = this;
if (edit) {
Root.AddEventListenerIfButtonExists(this.marker("Save"), function (event) {_this.Save();});
Root.AddEventListenerIfButtonExists(this.marker("Delete"), function (event) {_this.Delete();});
Root.AddEventListenerIfButtonExists(this.marker("Reset"), function (event) {_this.Show(false);});
} else {
Root.AddEventListenerIfButtonExists(this.marker("Edit"), function (event) {_this.Show(true);});
} }
}

119
client/pages/fahrzeuge.js Normal file
View File

@ -0,0 +1,119 @@
class Fahrzeuge extends Page
{
static route = "Fahrzeuge";
static array = {};
static groups = {};
static subs = {
"Eingewiesene": {color: "chocolate", icon: "person.png", entries: []}
};
static selects = {};
static admin = false; // global page admin rights
static prints = [];
static filter = [];
//static selected = [];
//static dropzones = {};
static visible = false;
constructor(groupName, json) {
super(
groupName,
json,
Fahrzeuge.route,
Fahrzeuge.array,
Fahrzeuge.groups,
Fahrzeuge.subs,
Fahrzeuge.selects,
Fahrzeuge.admin/*,
Fahrzeuge.prints,
Fahrzeuge.filter,
Fahrzeuge.selected,
Fahrzeuge.dropzones*/
);
}
init(json) {
this.Kürzel = json.MAIN.KÜRZEL;
this.Name = json.MAIN.NAME;
this.Bild = json.MAIN.BILD;
}
renderEntry(drawEdit) {
let html = "<div class='bild'>";
if (this.Bild.EXISTIERT) {
html+= "<img src='/pic.php/Fahrzeuge/Bild/" + this.Bild.ADRESSE + "?h=260' />";
if (drawEdit) {
html+= "<div id='" + this.marker("Bild/Delete") + "' class='deleter'><img src='/res/dark/delete.png' /></div>";
}
} else {
html+= "<img src='/upl/Fahrzeuge/Bild/Nopic.svg' />";
}
if (drawEdit) {
html+= "<form action='/" + this.marker("Bilder") + "' class='dropzone' id='dropzone_" + this.ID + "'></form>";
}
html += "</div>";
if (drawEdit) {
html+= "<ul>"
+ "<li><img src='/res/dark/truck.png' /><input id='" + this.marker("Name") + "' value='" + this.Name + "' style='width: 8cm;'></li>"
+ "<li><img src='/res/dark/truck.png' /><input id='" + this.marker("Kürzel") + "' value='" + this.Kürzel + "' style='width: 4cm;'></li>"
+ "<li id='" + this.marker("Save") + "' class='button'><img src='/res/dark/save.png' /></li>"
+ "<li id='" + this.marker("Delete") + "' class='button'><img src='/res/dark/delete.png' /></li>"
+ "<li id='" + this.marker("Reset") + "' class='button'><img src='/res/dark/cancel.png' /></li>"
+ "</ul>";
} else {
html+= "<ul><li><img src='/res/dark/truck.png' />" + this.Name + "</li>"
"<li><img src='/res/dark/truck.png' />" + this.Kürzel + "</li>";
if (this.admin) {
html += "<li id='" + this.marker("Edit") + "' class='button'><img src='/res/dark/edit.png' title='Bearbeiten' /></li>";
}
html+= "</ul>";
}
document.getElementById(this.marker("Main")).innerHTML = html;
}
static RenderAdd() {
let html = "<h1>Fahrzeug hinzufügen</h1>"
+ "<div class='toggleVisibility'><ul><li>Name: <input id='Fahrzeuge/Add/Name' style='width: 8em;' /></li>"
+ "<li>Kürzel: <input id='Fahrzeuge/Add/Kürzel' style='width: 4em;' /></li>"
+ "<li id='Fahrzeuge/Add/Add' class='button'>Hinzufügen <img src='/res/dark/add.png' /></li></ul></div>";
return html;
}
renderGroup() {
return Fahrzeuge.RenderGroup(this.groupName);
}
static RenderGroup(groupName) {
return {
begin: "<article id='Group/" + groupName + "'><div class='group_title'><h1>" + groupName + "</h1></div>",
end: "</article>"
};
}
static New(groupName, json) {
return new Fahrzeuge(groupName, json);
}
collect() {
return Fahrzeuge.Collect(this.ID);
}
static Collect(id, contextId = null) {
return {
ID: id,
KÜRZEL: this.Value(id, "Kürzel"),
NAME: this.Value(id, "Name")
};
}
static RenderSelect(selectId, fahrzeugId, nullable = true) {
let html = "<select id='" + selectId + "'>" + (nullable ? "<option value='null'></option>" : "");
for (let group in Fahrzeuge.groups) {
for (let e in Fahrzeuge.groups[group]) {
const entry = Fahrzeuge.groups[group][e];
html+= "<option value='" + entry.ID + "'" + (entry.ID==fahrzeugId ? " selected" : "") + ">" + entry.Kürzel + "</option>";
}
}
html+= "</select>";
return html;
}
}

160
client/pages/personal.js Normal file
View File

@ -0,0 +1,160 @@
class PersonalPage extends Page
{
static routes = {
"Personal": {needRead: true, needWrite: false}
}
static route = "Personal";
static array = {};
static groups = {};
static subs = {
"Abteilungen": {color: "purple", icon: "department.png", entries: []},
"Gruppen": {color: "olive", icon: "group.png", entries: []},
"Lehrgänge": {color: "teal", icon: "skill.png", entries: []},
"Einweisungen": {color: "chocolate", icon: "person.png", entries: []}
};
static selects = {
"Kategorie" : [],
"Funktion" : []
};
static admin = true; // global page admin rights
static prints = [];
static filter = [];
//static selected = [];
//static dropzones = {};
static visible = false;
constructor(groupName, json) {
super(
groupName,
json,
Personal.route,
Personal.array,
Personal.groups,
Personal.subs,
Personal.selects,
Personal.admin/*,
Personal.prints,
Personal.filter,
Personal.selected,
Personal.dropzones*/
);
}
init(json) {
this.OFNr = json.MAIN.OFNR;
this.PNr = json.MAIN.PNR;
this.Nr = json.MAIN.NR;
this.Login = json.MAIN.LOGIN;
this.Email = json.MAIN.EMAIL;
this.Vornamen = json.MAIN.VORNAMEN;
this.Nachnamen = json.MAIN.NACHNAMEN;
this.Name = json.MAIN.NAME;
this.Bild = json.MAIN.BILD;
this.Kategorie = json.MAIN.KATEGORIE;
this.Funktion = json.MAIN.FUNKTION;
}
renderEntry(drawEdit) {
let html = "<div class='bild'>";
if (this.Bild.EXISTIERT) {
html+= "<img src='/pic.php/Personal/Bildadresse/" + this.Bild.ADRESSE + "?h=260' />";
if (drawEdit) {
html+= "<div id='" + this.marker("Bild/Delete") + "' class='deleter'><img src='/res/dark/delete.png' /></div>";
}
} else {
html+= "<img src='/upl/Personal/Bildadresse/Nopic.svg' />";
}
if (drawEdit) {
html+= "<form action='/" + this.marker("Bilder") + "' class='dropzone' id='dropzone_" + this.ID + "'></form>";
}
html += "</div><ul>";
// TODO: Draw selectbox
if (drawEdit) {
html+= this.renderField("number.png", this.renderInput("OFNr", this.OFNr, 1.5, 2) + "-" + this.renderInput("PNr", this.PNr, 2, 3))
+ this.renderField("person.png", this.renderInput("Nachnamen", this.Nachnamen) + ", " + this.renderInput("Vornamen", this.Vornamen))
+ this.renderField("type.png", this.renderSelect("Kategorie", this.Kategorie.ID))
+ this.renderField("note.png", this.renderInput("Login", this.Login) + "@feuerwehr-bs.net")
+ this.renderField("star.png", this.renderSelect("Funktion", this.Funktion.ID, 2.5))
+ this.renderButton("save.png", "Save", "Speichern")
+ this.renderButton("delete.png", "Delete", "Löschen")
+ this.renderButton("cancel.png", "Reset", "Zurücksetzen");
} else {
html+= this.renderField("number.png", this.Nr)
+ this.renderField("person.png", "<b>" + this.Nachnamen + ", " + this.Vornamen + "</b>")
+ (this.Login=="" || this.Login===null ? this.renderField("note.png", "<span class='red'>Kein Login!</span>") : "")
+ this.renderField("star.png", this.Funktion!==null ? this.Funktion.KÜRZEL : "-");
if (this.admin) {
html += this.renderButton("edit.png", "Edit", "Bearbeiten");
}
}
html+= "</ul>";
document.getElementById(this.marker("Main")).innerHTML = html;
}
static RenderAdd() {
let html = "<h1>Fahrzeug hinzufügen</h1>"
+ "<div class='toggleVisibility'><ul><li>Name: <input id='Personal/Add/Name' style='width: 8em;' /></li>"
+ "<li>Kürzel: <input id='Personal/Add/Kürzel' style='width: 4em;' /></li>"
+ "<li id='Personal/Add/Add' class='button'>Hinzufügen <img src='/res/dark/add.png' /></li></ul></div>";
return html;
}
renderGroup() {
return Personal.RenderGroup(this.groupName);
}
static RenderGroup(groupName) {
return {
begin: "<article id='Group/" + groupName + "'><div class='group_title'><h1>" + groupName + "</h1></div>",
end: "</article>"
};
}
static New(groupName, json) {
return new Personal(groupName, json);
}
collect() {
return Personal.Collect(this.ID);
}
static Collect(id, contextId = null) {
return {
ID: id,
OFNR: this.Value(id, "OFNr"),
PNR: this.Value(id, "PNr"),
NACHNAMEN: this.Value(id, "Nachnamen"),
VORNAMEN: this.Value(id, "Vornamen"),
KATEGORIE: this.Value(id, "Kategorie"),
LOGIN: this.Value(id, "Login"),
FUNKTION: this.Value(id, "Funktion")
};
}
static RenderSelect(selectId, fahrzeugId, nullable = true) {
let html = "<select id='" + selectId + "'>" + (nullable ? "<option value='null'></option>" : "");
for (let group in Personal.groups) {
for (let e in Personal.groups[group]) {
const entry = Personal.groups[group][e];
html+= "<option value='" + entry.ID + "'" + (entry.ID==fahrzeugId ? " selected" : "") + ">" + entry.Kürzel + "</option>";
}
}
html+= "</select>";
return html;
}
}

98
client/qr-scanner-worker.min.js vendored Normal file
View File

@ -0,0 +1,98 @@
export const createWorker=()=>new Worker(URL.createObjectURL(new Blob([`class x{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new x(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let g=a;g<a+c;g++)this.set(g,f,!!e)}}
class A{constructor(a,b,c){this.width=a;a*=b;if(c&&c.length!==a)throw Error("Wrong buffer size");this.data=c||new Uint8ClampedArray(a)}get(a,b){return this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c}}
class ba{constructor(a){this.bitOffset=this.byteOffset=0;this.bytes=a}readBits(a){if(1>a||32<a||a>this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0<this.bitOffset){b=8-this.bitOffset;var c=a<b?a:b;b-=c;b=(this.bytes[this.byteOffset]&255>>8-c<<b)>>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0<a){for(;8<=a;)b=b<<8|this.bytes[this.byteOffset]&255,this.byteOffset++,a-=8;0<a&&(c=8-a,b=b<<a|(this.bytes[this.byteOffset]&255>>c<<c)>>c,
this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var B,C=B||(B={});C.Numeric="numeric";C.Alphanumeric="alphanumeric";C.Byte="byte";C.Kanji="kanji";C.ECI="eci";C.StructuredAppend="structuredappend";var D,E=D||(D={});E[E.Terminator=0]="Terminator";E[E.Numeric=1]="Numeric";E[E.Alphanumeric=2]="Alphanumeric";E[E.Byte=4]="Byte";E[E.Kanji=8]="Kanji";E[E.ECI=7]="ECI";E[E.StructuredAppend=3]="StructuredAppend";let F="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");
function ca(a,b){let c=[],d="";b=a.readBits([8,16,16][b]);for(let e=0;e<b;e++){let f=a.readBits(8);c.push(f)}try{d+=decodeURIComponent(c.map(e=>\`%\${("0"+e.toString(16)).substr(-2)}\`).join(""))}catch(e){}return{bytes:c,text:d}}
function da(a,b){a=new ba(a);let c=9>=b?0:26>=b?1:2;for(b={text:"",bytes:[],chunks:[],version:b};4<=a.available();){var d=a.readBits(4);if(d===D.Terminator)return b;if(d===D.ECI)0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(7)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(14)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(21)}):b.chunks.push({type:B.ECI,assignmentNumber:-1});else if(d===D.Numeric){var e=a,f=[];d="";for(var g=
e.readBits([10,12,14][c]);3<=g;){var h=e.readBits(10);if(1E3<=h)throw Error("Invalid numeric value above 999");var k=Math.floor(h/100),m=Math.floor(h/10)%10;h%=10;f.push(48+k,48+m,48+h);d+=k.toString()+m.toString()+h.toString();g-=3}if(2===g){g=e.readBits(7);if(100<=g)throw Error("Invalid numeric value above 99");e=Math.floor(g/10);g%=10;f.push(48+e,48+g);d+=e.toString()+g.toString()}else if(1===g){e=e.readBits(4);if(10<=e)throw Error("Invalid numeric value above 9");f.push(48+e);d+=e.toString()}b.text+=
d;b.bytes.push(...f);b.chunks.push({type:B.Numeric,text:d})}else if(d===D.Alphanumeric){e=a;f=[];d="";for(g=e.readBits([9,11,13][c]);2<=g;)m=e.readBits(11),k=Math.floor(m/45),m%=45,f.push(F[k].charCodeAt(0),F[m].charCodeAt(0)),d+=F[k]+F[m],g-=2;1===g&&(e=e.readBits(6),f.push(F[e].charCodeAt(0)),d+=F[e]);b.text+=d;b.bytes.push(...f);b.chunks.push({type:B.Alphanumeric,text:d})}else if(d===D.Byte)d=ca(a,c),b.text+=d.text,b.bytes.push(...d.bytes),b.chunks.push({type:B.Byte,bytes:d.bytes,text:d.text});
else if(d===D.Kanji){f=a;d=[];e=f.readBits([8,10,12][c]);for(g=0;g<e;g++)k=f.readBits(13),k=Math.floor(k/192)<<8|k%192,k=7936>k?k+33088:k+49472,d.push(k>>8,k&255);f=(new TextDecoder("shift-jis")).decode(Uint8Array.from(d));b.text+=f;b.bytes.push(...d);b.chunks.push({type:B.Kanji,bytes:d,text:f})}else d===D.StructuredAppend&&b.chunks.push({type:B.StructuredAppend,currentSequence:a.readBits(4),totalSequence:a.readBits(4),parity:a.readBits(8)})}if(0===a.available()||0===a.readBits(a.available()))return b}
class G{constructor(a,b){if(0===b.length)throw Error("No coefficients.");this.field=a;let c=b.length;if(1<c&&0===b[0]){let d=1;for(;d<c&&0===b[d];)d++;if(d===c)this.coefficients=a.zero.coefficients;else for(this.coefficients=new Uint8ClampedArray(c-d),a=0;a<this.coefficients.length;a++)this.coefficients[a]=b[d+a]}else this.coefficients=b}degree(){return this.coefficients.length-1}isZero(){return 0===this.coefficients[0]}getCoefficient(a){return this.coefficients[this.coefficients.length-1-a]}addOrSubtract(a){if(this.isZero())return a;
if(a.isZero())return this;let b=this.coefficients;a=a.coefficients;b.length>a.length&&([b,a]=[a,b]);let c=new Uint8ClampedArray(a.length),d=a.length-b.length;for(var e=0;e<d;e++)c[e]=a[e];for(e=d;e<a.length;e++)c[e]=b[e-d]^a[e];return new G(this.field,c)}multiply(a){if(0===a)return this.field.zero;if(1===a)return this;let b=this.coefficients.length,c=new Uint8ClampedArray(b);for(let d=0;d<b;d++)c[d]=this.field.multiply(this.coefficients[d],a);return new G(this.field,c)}multiplyPoly(a){if(this.isZero()||
a.isZero())return this.field.zero;let b=this.coefficients,c=b.length;a=a.coefficients;let d=a.length,e=new Uint8ClampedArray(c+d-1);for(let f=0;f<c;f++){let g=b[f];for(let h=0;h<d;h++)e[f+h]=H(e[f+h],this.field.multiply(g,a[h]))}return new G(this.field,e)}multiplyByMonomial(a,b){if(0>a)throw Error("Invalid degree less than 0");if(0===b)return this.field.zero;let c=this.coefficients.length;a=new Uint8ClampedArray(c+a);for(let d=0;d<c;d++)a[d]=this.field.multiply(this.coefficients[d],b);return new G(this.field,
a)}evaluateAt(a){let b=0;if(0===a)return this.getCoefficient(0);let c=this.coefficients.length;if(1===a)return this.coefficients.forEach(d=>{b^=d}),b;b=this.coefficients[0];for(let d=1;d<c;d++)b=H(this.field.multiply(a,b),this.coefficients[d]);return b}}function H(a,b){return a^b}
class ea{constructor(a,b,c){this.primitive=a;this.size=b;this.generatorBase=c;this.expTable=Array(this.size);this.logTable=Array(this.size);a=1;for(b=0;b<this.size;b++)this.expTable[b]=a,a*=2,a>=this.size&&(a=(a^this.primitive)&this.size-1);for(a=0;a<this.size-1;a++)this.logTable[this.expTable[a]]=a;this.zero=new G(this,Uint8ClampedArray.from([0]));this.one=new G(this,Uint8ClampedArray.from([1]))}multiply(a,b){return 0===a||0===b?0:this.expTable[(this.logTable[a]+this.logTable[b])%(this.size-1)]}inverse(a){if(0===
a)throw Error("Can't invert 0");return this.expTable[this.size-this.logTable[a]-1]}buildMonomial(a,b){if(0>a)throw Error("Invalid monomial degree less than 0");if(0===b)return this.zero;a=new Uint8ClampedArray(a+1);a[0]=b;return new G(this,a)}log(a){if(0===a)throw Error("Can't take log(0)");return this.logTable[a]}exp(a){return this.expTable[a]}}
function fa(a,b,c,d){b.degree()<c.degree()&&([b,c]=[c,b]);let e=a.zero;for(var f=a.one;c.degree()>=d/2;){var g=b;let h=e;b=c;e=f;if(b.isZero())return null;c=g;f=a.zero;g=b.getCoefficient(b.degree());for(g=a.inverse(g);c.degree()>=b.degree()&&!c.isZero();){let k=c.degree()-b.degree(),m=a.multiply(c.getCoefficient(c.degree()),g);f=f.addOrSubtract(a.buildMonomial(k,m));c=c.addOrSubtract(b.multiplyByMonomial(k,m))}f=f.multiplyPoly(e).addOrSubtract(h);if(c.degree()>=b.degree())return null}d=f.getCoefficient(0);
if(0===d)return null;a=a.inverse(d);return[f.multiply(a),c.multiply(a)]}
function ha(a,b){let c=new Uint8ClampedArray(a.length);c.set(a);a=new ea(285,256,0);var d=new G(a,c),e=new Uint8ClampedArray(b),f=!1;for(var g=0;g<b;g++){var h=d.evaluateAt(a.exp(g+a.generatorBase));e[e.length-1-g]=h;0!==h&&(f=!0)}if(!f)return c;d=new G(a,e);d=fa(a,a.buildMonomial(b,1),d,b);if(null===d)return null;b=d[0];g=b.degree();if(1===g)b=[b.getCoefficient(1)];else{e=Array(g);f=0;for(h=1;h<a.size&&f<g;h++)0===b.evaluateAt(h)&&(e[f]=a.inverse(h),f++);b=f!==g?null:e}if(null==b)return null;e=d[1];
f=b.length;d=Array(f);for(g=0;g<f;g++){h=a.inverse(b[g]);let k=1;for(let m=0;m<f;m++)g!==m&&(k=a.multiply(k,H(1,a.multiply(b[m],h))));d[g]=a.multiply(e.evaluateAt(h),a.inverse(k));0!==a.generatorBase&&(d[g]=a.multiply(d[g],h))}for(e=0;e<b.length;e++){f=c.length-1-a.log(b[e]);if(0>f)return null;c[f]^=d[e]}return c}
let I=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,
dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]},
{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6,
34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18,
ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38},
{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36},
{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,
dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,
dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]},
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]},
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115},
{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22,
ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50,
74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749,
versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17,
dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,
dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]},
{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},
{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},
{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},
{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},
{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13,
dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,
dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117},
{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6,
26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]},
{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},
{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,
dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44,
dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,
dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,
dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166],
errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017,
versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15},
{numBlocks:61,dataCodewordsPerBlock:16}]}]}];function J(a,b){a^=b;for(b=0;a;)b++,a&=a-1;return b}function K(a,b){return b<<1|a}
let ia=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0,
dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,
formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,
dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],ja=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y%
2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2];
function ka(a,b,c){c=ja[c.dataMask];let d=a.height;var e=17+4*b.versionNumber;let f=x.createEmpty(e,e);f.setRegion(0,0,9,9,!0);f.setRegion(e-8,0,8,9,!0);f.setRegion(0,e-8,9,8,!0);for(var g of b.alignmentPatternCenters)for(var h of b.alignmentPatternCenters)6===g&&6===h||6===g&&h===e-7||g===e-7&&6===h||f.setRegion(g-2,h-2,5,5,!0);f.setRegion(6,9,1,e-17,!0);f.setRegion(9,6,e-17,1,!0);6<b.versionNumber&&(f.setRegion(e-11,0,3,6,!0),f.setRegion(0,e-11,6,3,!0));b=[];h=g=0;e=!0;for(let k=d-1;0<k;k-=2){6===
k&&k--;for(let m=0;m<d;m++){let l=e?d-1-m:m;for(let n=0;2>n;n++){let q=k-n;if(!f.get(q,l)){h++;let r=a.get(q,l);c({y:l,x:q})&&(r=!r);g=g<<1|r;8===h&&(b.push(g),g=h=0)}}}e=!e}return b}
function la(a){var b=a.height,c=Math.floor((b-17)/4);if(6>=c)return I[c-1];c=0;for(var d=5;0<=d;d--)for(var e=b-9;e>=b-11;e--)c=K(a.get(e,d),c);d=0;for(e=5;0<=e;e--)for(let g=b-9;g>=b-11;g--)d=K(a.get(e,g),d);a=Infinity;let f;for(let g of I){if(g.infoBits===c||g.infoBits===d)return g;b=J(c,g.infoBits);b<a&&(f=g,a=b);b=J(d,g.infoBits);b<a&&(f=g,a=b)}if(3>=a)return f}
function ma(a){let b=0;for(var c=0;8>=c;c++)6!==c&&(b=K(a.get(c,8),b));for(c=7;0<=c;c--)6!==c&&(b=K(a.get(8,c),b));var d=a.height;c=0;for(var e=d-1;e>=d-7;e--)c=K(a.get(8,e),c);for(e=d-8;e<d;e++)c=K(a.get(e,8),c);a=Infinity;d=null;for(let {bits:f,formatInfo:g}of ia){if(f===b||f===c)return g;e=J(b,f);e<a&&(d=g,a=e);b!==c&&(e=J(c,f),e<a&&(d=g,a=e))}return 3>=a?d:null}
function na(a,b,c){let d=b.errorCorrectionLevels[c],e=[],f=0;d.ecBlocks.forEach(h=>{for(let k=0;k<h.numBlocks;k++)e.push({numDataCodewords:h.dataCodewordsPerBlock,codewords:[]}),f+=h.dataCodewordsPerBlock+d.ecCodewordsPerBlock});if(a.length<f)return null;a=a.slice(0,f);b=d.ecBlocks[0].dataCodewordsPerBlock;for(c=0;c<b;c++)for(var g of e)g.codewords.push(a.shift());if(1<d.ecBlocks.length)for(g=d.ecBlocks[0].numBlocks,b=d.ecBlocks[1].numBlocks,c=0;c<b;c++)e[g+c].codewords.push(a.shift());for(;0<a.length;)for(let h of e)h.codewords.push(a.shift());
return e}function L(a){let b=la(a);if(!b)return null;var c=ma(a);if(!c)return null;a=ka(a,b,c);var d=na(a,b,c.errorCorrectionLevel);if(!d)return null;c=d.reduce((e,f)=>e+f.numDataCodewords,0);c=new Uint8ClampedArray(c);a=0;for(let e of d){d=ha(e.codewords,e.codewords.length-e.numDataCodewords);if(!d)return null;for(let f=0;f<e.numDataCodewords;f++)c[a++]=d[f]}try{return da(c,b.versionNumber)}catch(e){return null}}
function M(a,b,c,d){var e=a.x-b.x+c.x-d.x;let f=a.y-b.y+c.y-d.y;if(0===e&&0===f)return{a11:b.x-a.x,a12:b.y-a.y,a13:0,a21:c.x-b.x,a22:c.y-b.y,a23:0,a31:a.x,a32:a.y,a33:1};let g=b.x-c.x;var h=d.x-c.x;let k=b.y-c.y,m=d.y-c.y;c=g*m-h*k;h=(e*m-h*f)/c;e=(g*f-e*k)/c;return{a11:b.x-a.x+h*b.x,a12:b.y-a.y+h*b.y,a13:h,a21:d.x-a.x+e*d.x,a22:d.y-a.y+e*d.y,a23:e,a31:a.x,a32:a.y,a33:1}}
function oa(a,b,c,d){a=M(a,b,c,d);return{a11:a.a22*a.a33-a.a23*a.a32,a12:a.a13*a.a32-a.a12*a.a33,a13:a.a12*a.a23-a.a13*a.a22,a21:a.a23*a.a31-a.a21*a.a33,a22:a.a11*a.a33-a.a13*a.a31,a23:a.a13*a.a21-a.a11*a.a23,a31:a.a21*a.a32-a.a22*a.a31,a32:a.a12*a.a31-a.a11*a.a32,a33:a.a11*a.a22-a.a12*a.a21}}
function pa(a,b){var c=oa({x:3.5,y:3.5},{x:b.dimension-3.5,y:3.5},{x:b.dimension-6.5,y:b.dimension-6.5},{x:3.5,y:b.dimension-3.5}),d=M(b.topLeft,b.topRight,b.alignmentPattern,b.bottomLeft),e=d.a11*c.a11+d.a21*c.a12+d.a31*c.a13,f=d.a12*c.a11+d.a22*c.a12+d.a32*c.a13,g=d.a13*c.a11+d.a23*c.a12+d.a33*c.a13,h=d.a11*c.a21+d.a21*c.a22+d.a31*c.a23,k=d.a12*c.a21+d.a22*c.a22+d.a32*c.a23,m=d.a13*c.a21+d.a23*c.a22+d.a33*c.a23,l=d.a11*c.a31+d.a21*c.a32+d.a31*c.a33,n=d.a12*c.a31+d.a22*c.a32+d.a32*c.a33,q=d.a13*
c.a31+d.a23*c.a32+d.a33*c.a33;c=x.createEmpty(b.dimension,b.dimension);d=(r,u)=>{const p=g*r+m*u+q;return{x:(e*r+h*u+l)/p,y:(f*r+k*u+n)/p}};for(let r=0;r<b.dimension;r++)for(let u=0;u<b.dimension;u++){let p=d(u+.5,r+.5);c.set(u,r,a.get(Math.floor(p.x),Math.floor(p.y)))}return{matrix:c,mappingFunction:d}}let N=(a,b)=>Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));function O(a){return a.reduce((b,c)=>b+c)}
function qa(a,b,c){let d=N(a,b),e=N(b,c),f=N(a,c),g,h,k;e>=d&&e>=f?[g,h,k]=[b,a,c]:f>=e&&f>=d?[g,h,k]=[a,b,c]:[g,h,k]=[a,c,b];0>(k.x-h.x)*(g.y-h.y)-(k.y-h.y)*(g.x-h.x)&&([g,k]=[k,g]);return{bottomLeft:g,topLeft:h,topRight:k}}
function ra(a,b,c,d){d=(O(P(a,c,d,5))/7+O(P(a,b,d,5))/7+O(P(c,a,d,5))/7+O(P(b,a,d,5))/7)/4;if(1>d)throw Error("Invalid module size");b=Math.round(N(a,b)/d);a=Math.round(N(a,c)/d);a=Math.floor((b+a)/2)+7;switch(a%4){case 0:a++;break;case 2:a--}return{dimension:a,moduleSize:d}}
function Q(a,b,c,d){let e=[{x:Math.floor(a.x),y:Math.floor(a.y)}];var f=Math.abs(b.y-a.y)>Math.abs(b.x-a.x);if(f){var g=Math.floor(a.y);var h=Math.floor(a.x);a=Math.floor(b.y);b=Math.floor(b.x)}else g=Math.floor(a.x),h=Math.floor(a.y),a=Math.floor(b.x),b=Math.floor(b.y);let k=Math.abs(a-g),m=Math.abs(b-h),l=Math.floor(-k/2),n=g<a?1:-1,q=h<b?1:-1,r=!0;for(let u=g,p=h;u!==a+n;u+=n){g=f?p:u;h=f?u:p;if(c.get(g,h)!==r&&(r=!r,e.push({x:g,y:h}),e.length===d+1))break;l+=m;if(0<l){if(p===b)break;p+=q;l-=k}}c=
[];for(f=0;f<d;f++)e[f]&&e[f+1]?c.push(N(e[f],e[f+1])):c.push(0);return c}function P(a,b,c,d){let e=b.y-a.y,f=b.x-a.x;b=Q(a,b,c,Math.ceil(d/2));a=Q(a,{x:a.x-f,y:a.y-e},c,Math.ceil(d/2));c=b.shift()+a.shift()-1;return a.concat(c).concat(...b)}function R(a,b){let c=O(a)/O(b),d=0;b.forEach((e,f)=>{d+=Math.pow(a[f]-e*c,2)});return{averageSize:c,error:d}}
function S(a,b,c){try{let d=P(a,{x:-1,y:a.y},c,b.length),e=P(a,{x:a.x,y:-1},c,b.length),f=P(a,{x:Math.max(0,a.x-a.y)-1,y:Math.max(0,a.y-a.x)-1},c,b.length),g=P(a,{x:Math.min(c.width,a.x+a.y)+1,y:Math.min(c.height,a.y+a.x)+1},c,b.length),h=R(d,b),k=R(e,b),m=R(f,b),l=R(g,b),n=(h.averageSize+k.averageSize+m.averageSize+l.averageSize)/4;return Math.sqrt(h.error*h.error+k.error*k.error+m.error*m.error+l.error*l.error)+(Math.pow(h.averageSize-n,2)+Math.pow(k.averageSize-n,2)+Math.pow(m.averageSize-n,2)+
Math.pow(l.averageSize-n,2))/n}catch(d){return Infinity}}function T(a,b){for(var c=Math.round(b.x);a.get(c,Math.round(b.y));)c--;for(var d=Math.round(b.x);a.get(d,Math.round(b.y));)d++;c=(c+d)/2;for(d=Math.round(b.y);a.get(Math.round(c),d);)d--;for(b=Math.round(b.y);a.get(Math.round(c),b);)b++;return{x:c,y:(d+b)/2}}
function sa(a){var b=[],c=[];let d=[];var e=[];for(let p=0;p<=a.height;p++){var f=0,g=!1;let t=[0,0,0,0,0];for(let v=-1;v<=a.width;v++){var h=a.get(v,p);if(h===g)f++;else{t=[t[1],t[2],t[3],t[4],f];f=1;g=h;var k=O(t)/7;k=Math.abs(t[0]-k)<k&&Math.abs(t[1]-k)<k&&Math.abs(t[2]-3*k)<3*k&&Math.abs(t[3]-k)<k&&Math.abs(t[4]-k)<k&&!h;var m=O(t.slice(-3))/3;h=Math.abs(t[2]-m)<m&&Math.abs(t[3]-m)<m&&Math.abs(t[4]-m)<m&&h;if(k){let z=v-t[3]-t[4],y=z-t[2];k={startX:y,endX:z,y:p};m=c.filter(w=>y>=w.bottom.startX&&
y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5<t[2]/(w.bottom.endX-w.bottom.startX));0<m.length?m[0].bottom=k:c.push({top:k,bottom:k})}if(h){let z=v-t[4],y=z-t[3];h={startX:y,y:p,endX:z};k=e.filter(w=>y>=w.bottom.startX&&y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5<t[2]/(w.bottom.endX-w.bottom.startX));0<k.length?
k[0].bottom=h:e.push({top:h,bottom:h})}}}b.push(...c.filter(v=>v.bottom.y!==p&&2<=v.bottom.y-v.top.y));c=c.filter(v=>v.bottom.y===p);d.push(...e.filter(v=>v.bottom.y!==p));e=e.filter(v=>v.bottom.y===p)}b.push(...c.filter(p=>2<=p.bottom.y-p.top.y));d.push(...e);c=[];for(var l of b)2>l.bottom.y-l.top.y||(b=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4,e=(l.top.y+l.bottom.y+1)/2,a.get(Math.round(b),Math.round(e))&&(f=[l.top.endX-l.top.startX,l.bottom.endX-l.bottom.startX,l.bottom.y-l.top.y+
1],f=O(f)/f.length,g=S({x:Math.round(b),y:Math.round(e)},[1,1,3,1,1],a),c.push({score:g,x:b,y:e,size:f})));if(3>c.length)return null;c.sort((p,t)=>p.score-t.score);l=[];for(b=0;b<Math.min(c.length,5);++b){e=c[b];f=[];for(var n of c)n!==e&&f.push(Object.assign(Object.assign({},n),{score:n.score+Math.pow(n.size-e.size,2)/e.size}));f.sort((p,t)=>p.score-t.score);l.push({points:[e,f[0],f[1]],score:e.score+f[0].score+f[1].score})}l.sort((p,t)=>p.score-t.score);let {topRight:q,topLeft:r,bottomLeft:u}=qa(...l[0].points);
l=U(a,d,q,r,u);n=[];l&&n.push({alignmentPattern:{x:l.alignmentPattern.x,y:l.alignmentPattern.y},bottomLeft:{x:u.x,y:u.y},dimension:l.dimension,topLeft:{x:r.x,y:r.y},topRight:{x:q.x,y:q.y}});l=T(a,q);b=T(a,r);c=T(a,u);(a=U(a,d,l,b,c))&&n.push({alignmentPattern:{x:a.alignmentPattern.x,y:a.alignmentPattern.y},bottomLeft:{x:c.x,y:c.y},topLeft:{x:b.x,y:b.y},topRight:{x:l.x,y:l.y},dimension:a.dimension});return 0===n.length?null:n}
function U(a,b,c,d,e){let f,g;try{({dimension:f,moduleSize:g}=ra(d,c,e,a))}catch(l){return null}var h=c.x-d.x+e.x,k=c.y-d.y+e.y;c=(N(d,e)+N(d,c))/2/g;e=1-3/c;let m={x:d.x+e*(h-d.x),y:d.y+e*(k-d.y)};b=b.map(l=>{const n=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4;l=(l.top.y+l.bottom.y+1)/2;if(a.get(Math.floor(n),Math.floor(l))){var q=S({x:Math.floor(n),y:Math.floor(l)},[1,1,1],a)+N({x:n,y:l},m);return{x:n,y:l,score:q}}}).filter(l=>!!l).sort((l,n)=>l.score-n.score);return{alignmentPattern:15<=
c&&b.length?b[0]:m,dimension:f}}
function V(a){var b=sa(a);if(!b)return null;for(let e of b){b=pa(a,e);var c=b.matrix;if(null==c)c=null;else{var d=L(c);if(d)c=d;else{for(d=0;d<c.width;d++)for(let f=d+1;f<c.height;f++)c.get(d,f)!==c.get(f,d)&&(c.set(d,f,!c.get(d,f)),c.set(f,d,!c.get(f,d)));c=L(c)}}if(c)return{binaryData:c.bytes,data:c.text,chunks:c.chunks,version:c.version,location:{topRightCorner:b.mappingFunction(e.dimension,0),topLeftCorner:b.mappingFunction(0,0),bottomRightCorner:b.mappingFunction(e.dimension,e.dimension),bottomLeftCorner:b.mappingFunction(0,
e.dimension),topRightFinderPattern:e.topRight,topLeftFinderPattern:e.topLeft,bottomLeftFinderPattern:e.bottomLeft,bottomRightAlignmentPattern:e.alignmentPattern},matrix:b.matrix}}return null}let ta={inversionAttempts:"attemptBoth",greyScaleWeights:{red:.2126,green:.7152,blue:.0722,useIntegerApproximation:!1},canOverwriteImage:!0};function W(a,b){Object.keys(b).forEach(c=>{a[c]=b[c]})}
function X(a,b,c,d={}){let e=Object.create(null);W(e,ta);W(e,d);d="onlyInvert"===e.inversionAttempts||"invertFirst"===e.inversionAttempts;var f="attemptBoth"===e.inversionAttempts||d;var g=e.greyScaleWeights,h=e.canOverwriteImage,k=b*c;if(a.length!==4*k)throw Error("Malformed data passed to binarizer.");var m=0;if(h){var l=new Uint8ClampedArray(a.buffer,m,k);m+=k}l=new A(b,c,l);if(g.useIntegerApproximation)for(var n=0;n<c;n++)for(var q=0;q<b;q++){var r=4*(n*b+q);l.set(q,n,g.red*a[r]+g.green*a[r+1]+
g.blue*a[r+2]+128>>8)}else for(n=0;n<c;n++)for(q=0;q<b;q++)r=4*(n*b+q),l.set(q,n,g.red*a[r]+g.green*a[r+1]+g.blue*a[r+2]);g=Math.ceil(b/8);n=Math.ceil(c/8);q=g*n;if(h){var u=new Uint8ClampedArray(a.buffer,m,q);m+=q}u=new A(g,n,u);for(q=0;q<n;q++)for(r=0;r<g;r++){var p=Infinity,t=0;for(var v=0;8>v;v++)for(let w=0;8>w;w++){let aa=l.get(8*r+w,8*q+v);p=Math.min(p,aa);t=Math.max(t,aa)}v=(p+t)/2;v=Math.min(255,1.11*v);24>=t-p&&(v=p/2,0<q&&0<r&&(t=(u.get(r,q-1)+2*u.get(r-1,q)+u.get(r-1,q-1))/4,p<t&&(v=t)));
u.set(r,q,v)}h?(q=new Uint8ClampedArray(a.buffer,m,k),m+=k,q=new x(q,b)):q=x.createEmpty(b,c);r=null;f&&(h?(a=new Uint8ClampedArray(a.buffer,m,k),r=new x(a,b)):r=x.createEmpty(b,c));for(b=0;b<n;b++)for(a=0;a<g;a++){c=g-3;c=2>a?2:a>c?c:a;h=n-3;h=2>b?2:b>h?h:b;k=0;for(m=-2;2>=m;m++)for(p=-2;2>=p;p++)k+=u.get(c+m,h+p);c=k/25;for(h=0;8>h;h++)for(k=0;8>k;k++)m=8*a+h,p=8*b+k,t=l.get(m,p),q.set(m,p,t<=c),f&&r.set(m,p,!(t<=c))}f=f?{binarized:q,inverted:r}:{binarized:q};let {binarized:z,inverted:y}=f;(f=V(d?
y:z))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||(f=V(d?z:y));return f}X.default=X;let Y="dontInvert",Z={red:77,green:150,blue:29,useIntegerApproximation:!0};
self.onmessage=a=>{let b=a.data.id,c=a.data.data;switch(a.data.type){case "decode":(a=X(c.data,c.width,c.height,{inversionAttempts:Y,greyScaleWeights:Z}))?self.postMessage({id:b,type:"qrResult",data:a.data,cornerPoints:[a.location.topLeftCorner,a.location.topRightCorner,a.location.bottomRightCorner,a.location.bottomLeftCorner]}):self.postMessage({id:b,type:"qrResult",data:null});break;case "grayscaleWeights":Z.red=c.red;Z.green=c.green;Z.blue=c.blue;Z.useIntegerApproximation=c.useIntegerApproximation;
break;case "inversionMode":switch(c){case "original":Y="dontInvert";break;case "invert":Y="onlyInvert";break;case "both":Y="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}
`]),{type:"application/javascript"}))//# sourceMappingURL=qr-scanner-worker.min.js.map

File diff suppressed because one or more lines are too long

31
client/qr-scanner.umd.min.js vendored Normal file
View File

@ -0,0 +1,31 @@
'use strict';(function(e,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(e="undefined"!==typeof globalThis?globalThis:e||self,e.QrScanner=a())})(this,function(){class e{constructor(a,b,c,d,f){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredCamera="environment";this._maxScansPerSecond=25;this._lastScanTimestamp=-1;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");
c&&"object"===typeof c?this._onDecode=b:(c||d||f?console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future"):console.warn("Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."),this._legacyOnDecode=b);b="object"===typeof c?c:{};this._onDecodeError=b.onDecodeError||("function"===typeof c?c:this._onDecodeError);this._calculateScanRegion=
b.calculateScanRegion||("function"===typeof d?d:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof c?c:"number"===typeof d?d:this._legacyCanvasSize;this._maxScansPerSecond=b.maxScansPerSecond||this._maxScansPerSecond;this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);this._updateOverlay=this._updateOverlay.bind(this);
a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let h=!1;a.hidden&&(a.hidden=!1,h=!0);document.body.contains(a)||(document.body.appendChild(a),h=!0);c=a.parentElement;if(b.highlightScanRegion||b.highlightCodeOutline){d=!!b.overlay;this.$overlay=b.overlay||document.createElement("div");f=this.$overlay.style;f.position="absolute";f.display="none";f.pointerEvents="none";this.$overlay.classList.add("scan-region-highlight");if(!d&&b.highlightScanRegion){this.$overlay.innerHTML='<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>';
try{this.$overlay.firstElementChild.animate({transform:["scale(.98)","scale(1.01)"]},{duration:400,iterations:Infinity,direction:"alternate",easing:"ease-in-out"})}catch(m){}c.insertBefore(this.$overlay,this.$video.nextSibling)}b.highlightCodeOutline&&(this.$overlay.insertAdjacentHTML("beforeend",'<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'),
this.$codeOutlineHighlight=this.$overlay.lastElementChild)}this._scanRegion=this._calculateScanRegion(a);requestAnimationFrame(()=>{let m=window.getComputedStyle(a);"none"===m.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==m.visibility&&(a.style.setProperty("visibility","visible","important"),h=!0);h&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity="0",a.style.width="0",a.style.height="0",this.$overlay&&
this.$overlay.parentElement&&this.$overlay.parentElement.removeChild(this.$overlay),delete this.$overlay,delete this.$codeOutlineHighlight);this.$overlay&&this._updateOverlay()});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);window.addEventListener("resize",this._updateOverlay);this._qrEnginePromise=e.createQrEngine()}static set WORKER_PATH(a){console.warn("Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions.")}static async hasCamera(){try{return!!(await e.listCameras(!1)).length}catch(a){return!1}}static async listCameras(a=
!1){if(!navigator.mediaDevices)return[];let b=async()=>(await navigator.mediaDevices.enumerateDevices()).filter(d=>"videoinput"===d.kind),c;try{a&&(await b()).every(d=>!d.label)&&(c=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0}))}catch(d){}try{return(await b()).map((d,f)=>({id:d.deviceId,label:d.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{c&&(console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"),e._stopVideoStream(c))}}async hasFlash(){let a;
try{if(this.$video.srcObject){if(!(this.$video.srcObject instanceof MediaStream))return!1;a=this.$video.srcObject}else a=(await this._getCameraStream()).stream;return"torch"in a.getVideoTracks()[0].getSettings()}catch(b){return!1}finally{a&&a!==this.$video.srcObject&&(console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"),e._stopVideoStream(a))}}isFlashOn(){return this._flashOn}async toggleFlash(){this._flashOn?await this.turnFlashOff():await this.turnFlashOn()}async turnFlashOn(){if(!this._flashOn&&
!this._destroyed&&(this._flashOn=!0,this._active&&!this._paused))try{if(!await this.hasFlash())throw"No flash available";await this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]})}catch(a){throw this._flashOn=!1,a;}}async turnFlashOff(){this._flashOn&&(this._flashOn=!1,await this._restartVideoStream())}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",
this._onVisibilityChange);window.removeEventListener("resize",this._updateOverlay);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(this._destroyed)throw Error("The QR scanner can not be started as it had been destroyed.");if(!this._active||this._paused)if("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),this._active=!0,!document.hidden)if(this._paused=
!1,this.$video.srcObject)await this.$video.play();else try{let {stream:a,facingMode:b}=await this._getCameraStream();!this._active||this._paused?e._stopVideoStream(a):(this._setVideoMirror(b),this.$video.srcObject=a,await this.$video.play(),this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{})))}catch(a){if(!this._paused)throw this._active=!1,a;}}stop(){this.pause();this._active=!1}async pause(a=!1){this._paused=!0;if(!this._active)return!0;this.$video.pause();this.$overlay&&(this.$overlay.style.display=
"none");let b=()=>{this.$video.srcObject instanceof MediaStream&&(e._stopVideoStream(this.$video.srcObject),this.$video.srcObject=null)};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;b();return!0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,h=!1){let m,n=!1;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in
b||"returnDetailedScanResult"in b)?(m=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1,n=!0):b||c||d||f||h?console.warn("You're using a deprecated api for scanImage which will be removed in the future."):console.warn("Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true.");b=!!c;try{let p,k;[c,p]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);
[d,k]=e._drawToCanvas(p,m,d,f);let q;if(c instanceof Worker){let g=c;b||e._postWorkerMessageSync(g,"inversionMode","both");q=await new Promise((l,v)=>{let w,u,r,y=-1;u=t=>{t.data.id===y&&(g.removeEventListener("message",u),g.removeEventListener("error",r),clearTimeout(w),null!==t.data.data?l({data:t.data.data,cornerPoints:e._convertPoints(t.data.cornerPoints,m)}):v(e.NO_QR_CODE_FOUND))};r=t=>{g.removeEventListener("message",u);g.removeEventListener("error",r);clearTimeout(w);v("Scanner error: "+(t?
t.message||t:"Unknown Error"))};g.addEventListener("message",u);g.addEventListener("error",r);w=setTimeout(()=>r("timeout"),1E4);let x=k.getImageData(0,0,d.width,d.height);y=e._postWorkerMessageSync(g,"decode",x,[x.data.buffer])})}else q=await Promise.race([new Promise((g,l)=>window.setTimeout(()=>l("Scanner error: timeout"),1E4)),(async()=>{try{var [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return{data:g.rawValue,cornerPoints:e._convertPoints(g.cornerPoints,m)}}catch(l){g=l.message||l;
if(/not implemented|service unavailable/.test(g))return e._disableBarcodeDetector=!0,e.scanImage(a,{scanRegion:m,canvas:d,disallowCanvasResizing:f,alsoTryWithoutScanRegion:h});throw`Scanner error: ${g}`;}})()]);return n?q:q.data}catch(p){if(!m||!h)throw p;let k=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return n?k:k.data}finally{b||e._postWorkerMessage(c,"close")}}setGrayscaleWeights(a,b,c,d=!0){e._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,
blue:c,useIntegerApproximation:d})}setInversionMode(a){e._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static async createQrEngine(a){a&&console.warn("Specifying a worker path is not required and not supported anymore.");a=()=>import("./qr-scanner-worker.min.js").then(c=>c.createWorker());if(!(!e._disableBarcodeDetector&&"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")))return a();let b=navigator.userAgentData;
return b&&b.brands.some(({brand:c})=>/Chromium/i.test(c))&&/mac ?OS/i.test(b.platform)&&await b.getHighEntropyValues(["architecture","platformVersion"]).then(({architecture:c,platformVersion:d})=>/arm/i.test(c||"arm")&&13<=parseInt(d||"13")).catch(()=>!0)?a():new BarcodeDetector({formats:["qr_code"]})}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateOverlay();this.$overlay&&(this.$overlay.style.display="");this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video);
this._updateOverlay()}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return{x:Math.round((a.videoWidth-b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,downScaledHeight:this._legacyCanvasSize}}_updateOverlay(){requestAnimationFrame(()=>{if(this.$overlay){var a=this.$video,b=a.videoWidth,c=a.videoHeight,d=a.offsetWidth,f=a.offsetHeight,h=a.offsetLeft,
m=a.offsetTop,n=window.getComputedStyle(a),p=n.objectFit,k=b/c,q=d/f;switch(p){case "none":var g=b;var l=c;break;case "fill":g=d;l=f;break;default:("cover"===p?k>q:k<q)?(l=f,g=l*k):(g=d,l=g/k),"scale-down"===p&&(g=Math.min(g,b),l=Math.min(l,c))}var [v,w]=n.objectPosition.split(" ").map((r,y)=>{const x=parseFloat(r);return r.endsWith("%")?(y?f-l:d-g)*x/100:x});n=this._scanRegion.width||b;q=this._scanRegion.height||c;p=this._scanRegion.x||0;var u=this._scanRegion.y||0;k=this.$overlay.style;k.width=
`${n/b*g}px`;k.height=`${q/c*l}px`;k.top=`${m+w+u/c*l}px`;c=/scaleX\(-1\)/.test(a.style.transform);k.left=`${h+(c?d-v-g:v)+(c?b-p-n:p)/b*g}px`;k.transform=a.style.transform}})}static _convertPoints(a,b){if(!b)return a;let c=b.x||0,d=b.y||0,f=b.width&&b.downScaledWidth?b.width/b.downScaledWidth:1;b=b.height&&b.downScaledHeight?b.height/b.downScaledHeight:1;for(let h of a)h.x=h.x*f+c,h.y=h.y*b+d;return a}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||("requestVideoFrameCallback"in
this.$video?this.$video.requestVideoFrameCallback.bind(this.$video):requestAnimationFrame)(async()=>{if(!(1>=this.$video.readyState)){var a=Date.now()-this._lastScanTimestamp,b=1E3/this._maxScansPerSecond;a<b&&await new Promise(d=>setTimeout(d,b-a));this._lastScanTimestamp=Date.now();try{var c=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(d){if(!this._active)return;this._onDecodeError(d)}!e._disableBarcodeDetector||await this._qrEnginePromise instanceof
Worker||(this._qrEnginePromise=e.createQrEngine());c?(this._onDecode?this._onDecode(c):this._legacyOnDecode&&this._legacyOnDecode(c.data),this.$codeOutlineHighlight&&(clearTimeout(this._codeOutlineHighlightRemovalTimeout),this._codeOutlineHighlightRemovalTimeout=void 0,this.$codeOutlineHighlight.setAttribute("viewBox",`${this._scanRegion.x||0} `+`${this._scanRegion.y||0} `+`${this._scanRegion.width||this.$video.videoWidth} `+`${this._scanRegion.height||this.$video.videoHeight}`),this.$codeOutlineHighlight.firstElementChild.setAttribute("points",
c.cornerPoints.map(({x:d,y:f})=>`${d},${f}`).join(" ")),this.$codeOutlineHighlight.style.display="")):this.$codeOutlineHighlight&&!this._codeOutlineHighlightRemovalTimeout&&(this._codeOutlineHighlightRemovalTimeout=setTimeout(()=>this.$codeOutlineHighlight.style.display="none",100))}this._scanFrame()})}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&console.log(a)}async _getCameraStream(){if(!navigator.mediaDevices)throw"Camera not found.";let a=/^(environment|user)$/.test(this._preferredCamera)?"facingMode":
"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}],c=b.map(d=>Object.assign({},d,{[a]:{exact:this._preferredCamera}}));for(let d of[...c,...b])try{let f=await navigator.mediaDevices.getUserMedia({video:d,audio:!1}),h=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:f,facingMode:h}}catch(f){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}static _stopVideoStream(a){for(let b of a.getTracks())b.stop(),
a.removeTrack(b)}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b,c,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,m=b&&b.width?b.width:a.videoWidth||a.width,n=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:m,b=b&&b.downScaledHeight?
b.downScaledHeight:n,c.width!==d&&(c.width=d),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,m,n,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof Image)return await e._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof
File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=f=>{a.removeEventListener("load",d);a.removeEventListener("error",d);f instanceof ErrorEvent?
c("Image load error"):b()};a.addEventListener("load",d);a.addEventListener("error",d)})}static async _postWorkerMessage(a,b,c,d){return e._postWorkerMessageSync(await a,b,c,d)}static _postWorkerMessageSync(a,b,c,d){if(!(a instanceof Worker))return-1;let f=e._workerMessageId++;a.postMessage({id:f,type:b,data:c},d);return f}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e._disableBarcodeDetector=!1;e._workerMessageId=0;return e})
//# sourceMappingURL=qr-scanner.umd.min.js.map

File diff suppressed because one or more lines are too long

367
client/root.js Normal file
View File

@ -0,0 +1,367 @@
class Root
{
static instance = null;
constructor() {
// Init values / parse cookies
if (Root.instance!==null) {
return;
}
Root.instance = this;
this.jwt = this.getCookie("jwt");
this.secToken = this.getCookie("secToken");
this.loggedIn = false;
this.url = "https://app.fw-innenstadt.de/index_new.php/";
this.scanner = new Scanner(this);
this.page = "Start";
this.routesChecksum = "";
const _this = this;
Root.AddEventListenerIfButtonExists("loginButton", function (event) {_this.Login();});
Root.AddEventListenerIfButtonExists("nav_start", function (event) {_this.Open("Start");});
Root.AddEventListenerIfButtonExists("nav_termine", function (event) {_this.Open("Termine");});
Root.AddEventListenerIfButtonExists("nav_dienstpläne", function (event) {_this.Open("Dienstpläne");});
Root.AddEventListenerIfButtonExists("nav_personal", function (event) {_this.Open("Personal");});
Root.AddEventListenerIfButtonExists("nav_prozesse", function (event) {_this.Open("Prozesse");});
Root.AddEventListenerIfButtonExists("nav_spinde", function (event) {_this.Open("Spinde");});
Root.AddEventListenerIfButtonExists("nav_einsätze", function (event) {_this.Open("Einsätze");});
Root.AddEventListenerIfButtonExists("nav_anwesenheiten", function (event) {_this.Open("Anwesenheiten");});
Root.AddEventListenerIfButtonExists("nav_fahrzeuge", function (event) {_this.Open("Fahrzeuge");});
Root.AddEventListenerIfButtonExists("nav_logout", function (event) {_this.Logout();});
document.getElementById("navigation_select").addEventListener("change", function (event) {_this.Open(document.getElementById("navigation_select").value);});
this.FetchRoutes(true);
}
Open(page) {
if ("_LOGOUT_"==page) {
this.Logout();
return;
}
this.setCookie("page", page);
switch (page) {
case "Termine":
//TerminePage.Open();
break;
case "Dienstpläne":
//DienstplänePage.Open();
break;
case "Personal":
PersonalPage.Open();
break;
case "Prozesse":
//ProzessePage.Open();
break;
case "Spinde":
//SpindePage.Open();
break;
case "Einsätze":
//EinsätzePage.Open();
break;
case "Anwesenheiten":
//AnwesenheitenPage.Open();
break;
case "Fahrzeuge":
FahrzeugePage.Open();
break;
default:
//StartPage.Open();
this.setCookie("page", "Start");
}
}
Login() {
var button = document.getElementById("loginButton");
button.disabled = true;
button.className = "login disabled";
const json = {
"login": document.getElementById("loginUser").value,
"password": document.getElementById("loginPassword").value/*,
"secToken": typeof(this.secToken)!="undefined" ? this.secToken : ""*/
};
const _this = this;
Root.SetLoadAnimation(true);
let request = new XMLHttpRequest();
request.open("POST", this.url, true);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
Root.SetLoadAnimation(false);
const {ok, json, etag} = _this.FinishResponse(request);
_this.changeLoginScreen(ok);
if (ok) {
_this.updatePages(etag, json);
_this.Open(_this.getCookie("page"));
setTimeout(Root.instance.FetchPages, 10000, false);
}
};
request.send(JSON.stringify(json));
}
Logout() {
const _this = this;
let request = new XMLHttpRequest();
Root.SetLoadAnimation(true);
request.open("DELETE", this.url, true);
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
Root.SetLoadAnimation(false);
_this.FinishResponse(request);
_this.changeLoginScreen(false);
};
request.send({secToken: this.secToken});
}
FetchRoutes(callOpenAfterOk = false) {
if (this.jwt!="") {
Root.SetLoadAnimation(true);
const _this = this;
let request = new XMLHttpRequest();
request.open("OPTIONS", this.url);
request.setRequestHeader("Authorization", "Bearer " + this.jwt);
request.setRequestHeader("Accept", "application/json");
request.onreadystatechange = function() {
if (request.readyState != 4) return;
Root.SetLoadAnimation(false);
const {ok, json, etag} = _this.FinishResponse(request);
if (ok) {
_this.updatePages(etag, json);
if (callOpenAfterOk) {
_this.Open(_this.getCookie("page"));
}
setTimeout(Root.instance.FetchPages, 10000, false);
} }
request.send();
} else {
this.changeLoginScreen(false);
}
}
FinishResponse(request, isHead = false) {
const {ok, json} = isHead ? {ok: true, json: {}} : Root.ParseJson(request.responseText);
if (!ok) {
return {ok: false, json: {}, etag: null};
}
if (typeof(json.messages)!="undefined") {
Root.ShowMessages(json.messages);
}
if (typeof(json.secToken)!="undefined") {
this.secToken = json.secToken;
this.setCookie("secToken", json.secToken);
}
if (typeof(json.jwt)!="undefined") {
this.jwt = json.jwt;
this.setCookie("jwt", json.jwt);
}
if (request.status>=300) {
let statusString = "";
switch (request.status) {
//case 200: statusString = "OK"; break;
//case 201: statusString = "Erstellt"; break;
case 400: statusString = "Fehlerhafte Anfrage"; break;
case 401: statusString = "Anmeldung erforderlich"; this.changeLoginScreen(false); break;
case 403: statusString = "Keine Berechtigung"; break;
case 404: statusString = "Nicht gefunden"; break;
case 500: statusString = "Fehler auf Server"; break;
default: statusString = "" + request.status + " = Unbekannt";
}
Root.ShowMessages(["Anfrage wurde mit Code " + request.status + " abgelehnt.<br />(" + statusString + ")"]);
} else {
const etag = typeof(request.getResponseHeader) === 'function' ? request.getResponseHeader("ETag") : null;
return {ok: true, json: json, etag: etag};
}
return {ok: false, json: {}, etag: null};
}
/* "Private" functions */
updatePages(etag, json) {
if (etag!=this.routesChecksum) {
//StartPage.SetVisibility(true);
//TerminePage.SetVisibility(json.pages.includes("Termine"));
//DienstplänePage.SetVisibility(json.pages.includes("Dienstpläne"));
PersonalPage.SetVisibility(json);
//ProzessePage.SetVisibility(json.pages.includes("Prozesse"));
//SpindePage.SetVisibility(json.pages.includes("Spinde"));
//EinsätzePage.SetVisibility(json.pages.includes("Einsätze"));
//AnwesenheitenPage.SetVisibility(json.pages.includes("Anwesenheiten"));
FahrzeugePage.SetVisibility(json);
this.pagesChecksum = etag;
} }
changeLoginScreen(newStatus) {
var s = document.getElementById('header').style;
if (this.loggedIn==null) {
if (!newStatus) {
s.display = "block";
s.opacity = 1;
}
} else if (newStatus) {
(function fadeOut() {(s.opacity-=0.1)<0 ? s.display="none" : setTimeout(fadeOut, 40)})();
} else {
var button = document.getElementById("loginButton");
button.disabled = false;
button.className = "login";
s.display = "block";
(function fadeIn(){(s.opacity-=-0.1)>1 ? s.opacity=1 : setTimeout(fadeIn, 40)})();
}
this.loggedIn = newStatus;
}
/*updateLoginStatus(loggedIn) {
if (loggedIn) {
if (loggedIn!=this.loggedIn) {
this.changeLoginScreen(true);
}
return true;
} else {
this.changeLoginScreen(false);
}
return false;
}*/
setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Strict;Secure";
}
getCookie(cname){
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
} }
return "";
}
/* Static functions */
static ShowMessages(messages) {
for (var i in messages) {
Toastify({
text: messages[i],
duration: 3000,
close: true,
gravity: "top", // `top` or `bottom`
position: "right", // `left`, `center` or `right`
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
stopOnFocus: true, // Prevents dismissing of toast on hover
}).showToast();
} }
static ToggleVisibility(idImg, idDiv) {
let div = document.getElementById(idDiv);
let img = document.getElementById(idImg);
if (div.style.display === "none") {
div.style.display = "flex";
img.src = "res/dark/hide.png";
} else {
div.style.display = "none";
img.src = "res/dark/show.png";
}
}
static AddEventListenerIfButtonExists(button, func) {
let buttonElement = document.getElementById(button);
if (buttonElement!==null) {
buttonElement.addEventListener('click', func);
}
}
static ParseJson(responseText) {
let json = {};
try {
json = JSON.parse(responseText);
} catch (SyntaxError) {
Root.ShowMessages(["Parsen der Antwort gescheitert. Es folgt die Antwort!", responseText]);
return {ok: false, json: {}};
}
return {ok: true, json: json};
}
static SetLoadAnimation(isLoading) {
var loadElement = document.getElementById("loadImage");
if (null!=loadElement) {
if (isLoading) {
loadElement.innerHTML = "<img src='/res/loading.gif' />";
} else {
loadElement.innerHTML = "";
}
}
}
/*RenderMain(root) {
var html = "<table class='moneyplanner_accounts'>";
html+= "<tr><th>" + (this.viewFrom>0 ? "<button id='view_both_left'><</button>" : "") + (this.viewTo<this.view.length-1 ? "<button id='view_both_right'>></button>" : "") + "</th>";
for (var v=this.viewFrom; v<=this.viewTo; v++) {
html+= "<th>"
+ (v==this.viewFrom && this.viewFrom>0 ? "<button id='view_from_left'><</button>" : "")
+ (v==this.viewTo && this.viewTo>this.viewFrom ? "<button id='view_to_left'><</button>" : "")
+ this.view[v].title
+ (v==this.viewFrom && this.viewFrom<this.viewTo ? "<button id='view_from_right'>></button>" : "")
+ (v==this.viewTo && this.viewTo<this.view.length-1 ? "<button id='view_to_right'>></button>" : "")
+ "</th>";
}
html+= "</tr>";
for (let a in Account.byOrdering) {
html+= Account.byOrdering[a].Render();
}
this.contentContainer.innerHTML = html+"</table>";
const _this = this;
addEventListenerIfButtonExists('view_from_left', function (event) {if (_this.viewFrom>0) {--_this.viewFrom; _this.RenderMain();}});
addEventListenerIfButtonExists('view_from_right', function (event) {if (_this.viewFrom<_this.viewTo) {++_this.viewFrom; _this.RenderMain();}});
addEventListenerIfButtonExists('view_to_left', function (event) {if (_this.viewTo>_this.viewFrom) {--_this.viewTo; _this.RenderMain();}});
addEventListenerIfButtonExists('view_to_right', function (event) {if (_this.viewTo<_this.view.length-1) {++_this.viewTo; _this.RenderMain();}});
addEventListenerIfButtonExists('view_both_left', function (event) {if (_this.viewFrom>0) {--_this.viewFrom; --_this.viewTo; _this.RenderMain();}});
addEventListenerIfButtonExists('view_both_right', function (event) {if (_this.viewTo<_this.view.length-1) {++_this.viewFrom; ++_this.viewTo; _this.RenderMain();}});
for (let a in Account.array) {
let accountId = Account.array[a].id;
addEventListenerIfButtonExists('balances_add_' + accountId, function (event) {Balance.Add(_this, _this.RenderMain, accountId);});
}
}*/
}
/*function ToggleEntryView(id) {api.activeRoute.ToggleEntryView(id);}
function Open(path) {api.Open(path);}
function OpenSelected() {
let s = document.getElementById("navigation_select").value;
if (s=="_LOGOUT_") {Logout();} else {api.Open(s);}
}
function Logout() {api.Open("/index.php/Logout");}
function FilterAdd() {api.activeRoute.FilterAdd();}
function FilterDelete(id) {api.activeRoute.FilterDelete(id);}
function FilterChange(id) {api.activeRoute.FilterChange(id);}
function Add() {api.activeRoute.Add();}
function Delete(id, data = {}) {api.Request("DELETE", data, id);}
function Edit(id) {api.activeRoute.Edit(id);}
function Cancel(id) {api.activeRoute.Cancel(id);}
function Save(id) {api.activeRoute.Save(id);}
function SubShowAdd(id, markers) {api.activeRoute.SubShowAdd(id, markers);}
function SubShowScan(id, subroute, parsers) {api.activeRoute.SubShowScan(id, subroute, parsers);}
function HideScanner() {api.scanner.Stop(); document.getElementById("qrscanner").style.display = "none";}
function SwitchCamera() {api.scanner.SwitchCamera();}
function SubAdd(id, markers, subroute) {api.activeRoute.SubAdd(id, markers, subroute);}
function SubDelete(id, subroute, subid = null) {api.Request("DELETE", {}, id, subroute, subid);}
function Request(method, data, id = null, subroute = null, subid = null) {api.Request(method, data, id, subroute, subid);}
function Print(printID, printName) {api.Request("GET", {Print: printID, PrintName: printName});}
function SelectChange(id, state) {api.activeRoute.SelectChange(id, state);}*/
document.addEventListener ("DOMContentLoaded", () => {
var root = new Root();
});

119
client/routes/fahrzeuge.js Normal file
View File

@ -0,0 +1,119 @@
class Fahrzeuge extends Route
{
static route = "Fahrzeuge";
static array = {};
static groups = {};
static subs = {
"Eingewiesene": {color: "chocolate", icon: "person.png", entries: []}
};
static selects = {};
static admin = false; // global page admin rights
static prints = [];
static filter = [];
//static selected = [];
//static dropzones = {};
static visible = false;
constructor(groupName, json) {
super(
groupName,
json,
Fahrzeuge.route,
Fahrzeuge.array,
Fahrzeuge.groups,
Fahrzeuge.subs,
Fahrzeuge.selects,
Fahrzeuge.admin/*,
Fahrzeuge.prints,
Fahrzeuge.filter,
Fahrzeuge.selected,
Fahrzeuge.dropzones*/
);
}
init(json) {
this.Kürzel = json.MAIN.KÜRZEL;
this.Name = json.MAIN.NAME;
this.Bild = json.MAIN.BILD;
}
renderEntry(drawEdit) {
let html = "<div class='bild'>";
if (this.Bild.EXISTIERT) {
html+= "<img src='/pic.php/Fahrzeuge/Bild/" + this.Bild.ADRESSE + "?h=260' />";
if (drawEdit) {
html+= "<div id='" + this.marker("Bild/Delete") + "' class='deleter'><img src='/res/dark/delete.png' /></div>";
}
} else {
html+= "<img src='/upl/Fahrzeuge/Bild/Nopic.svg' />";
}
if (drawEdit) {
html+= "<form action='/" + this.marker("Bilder") + "' class='dropzone' id='dropzone_" + this.ID + "'></form>";
}
html += "</div>";
if (drawEdit) {
html+= "<ul>"
+ "<li><img src='/res/dark/truck.png' /><input id='" + this.marker("Name") + "' value='" + this.Name + "' style='width: 8cm;'></li>"
+ "<li><img src='/res/dark/truck.png' /><input id='" + this.marker("Kürzel") + "' value='" + this.Kürzel + "' style='width: 4cm;'></li>"
+ "<li id='" + this.marker("Save") + "' class='button'><img src='/res/dark/save.png' /></li>"
+ "<li id='" + this.marker("Delete") + "' class='button'><img src='/res/dark/delete.png' /></li>"
+ "<li id='" + this.marker("Reset") + "' class='button'><img src='/res/dark/cancel.png' /></li>"
+ "</ul>";
} else {
html+= "<ul><li><img src='/res/dark/truck.png' />" + this.Name + "</li>"
"<li><img src='/res/dark/truck.png' />" + this.Kürzel + "</li>";
if (this.admin) {
html += "<li id='" + this.marker("Edit") + "' class='button'><img src='/res/dark/edit.png' title='Bearbeiten' /></li>";
}
html+= "</ul>";
}
document.getElementById(this.marker("Main")).innerHTML = html;
}
static RenderAdd() {
let html = "<h1>Fahrzeug hinzufügen</h1>"
+ "<div class='toggleVisibility'><ul><li>Name: <input id='Fahrzeuge/Add/Name' style='width: 8em;' /></li>"
+ "<li>Kürzel: <input id='Fahrzeuge/Add/Kürzel' style='width: 4em;' /></li>"
+ "<li id='Fahrzeuge/Add/Add' class='button'>Hinzufügen <img src='/res/dark/add.png' /></li></ul></div>";
return html;
}
renderGroup() {
return Fahrzeuge.RenderGroup(this.groupName);
}
static RenderGroup(groupName) {
return {
begin: "<article id='Group/" + groupName + "'><div class='group_title'><h1>" + groupName + "</h1></div>",
end: "</article>"
};
}
static New(groupName, json) {
return new Fahrzeuge(groupName, json);
}
collect() {
return Fahrzeuge.Collect(this.ID);
}
static Collect(id, contextId = null) {
return {
ID: id,
KÜRZEL: this.Value(id, "Kürzel"),
NAME: this.Value(id, "Name")
};
}
static RenderSelect(selectId, fahrzeugId, nullable = true) {
let html = "<select id='" + selectId + "'>" + (nullable ? "<option value='null'></option>" : "");
for (let group in Fahrzeuge.groups) {
for (let e in Fahrzeuge.groups[group]) {
const entry = Fahrzeuge.groups[group][e];
html+= "<option value='" + entry.ID + "'" + (entry.ID==fahrzeugId ? " selected" : "") + ">" + entry.Kürzel + "</option>";
}
}
html+= "</select>";
return html;
}
}

144
client/routes/personal.js Normal file
View File

@ -0,0 +1,144 @@
class Personal extends Route
{
static route = "Personal";
static array = {};
static groups = {};
static subs = {
"Abteilungen": {color: "purple", icon: "department.png", entries: []},
"Gruppen": {color: "olive", icon: "group.png", entries: []},
"Lehrgänge": {color: "teal", icon: "skill.png", entries: []},
"Einweisungen": {color: "chocolate", icon: "person.png", entries: []}
};
static selects = {
"Kategorie" : [],
"Funktion" : []
};
static admin = true; // global page admin rights
static prints = [];
static filter = [];
//static selected = [];
//static dropzones = {};
static visible = false;
constructor(groupName, json) {
super(
groupName,
json,
Personal.route,
Personal.array,
Personal.groups,
Personal.subs,
Personal.selects,
Personal.admin/*,
Personal.prints,
Personal.filter,
Personal.selected,
Personal.dropzones*/
);
}
init(json) {
this.OFNr = json.MAIN.OFNR;
this.PNr = json.MAIN.PNR;
this.Nr = json.MAIN.NR;
this.Login = json.MAIN.LOGIN;
this.Email = json.MAIN.EMAIL;
this.Vornamen = json.MAIN.VORNAMEN;
this.Nachnamen = json.MAIN.NACHNAMEN;
this.Name = json.MAIN.NAME;
this.Bild = json.MAIN.BILD;
this.Kategorie = json.MAIN.KATEGORIE;
this.Funktion = json.MAIN.FUNKTION;
}
renderEntry(drawEdit) {
let html = "<div class='bild'>";
if (this.Bild.EXISTIERT) {
html+= "<img src='/pic.php/Personal/Bildadresse/" + this.Bild.ADRESSE + "?h=260' />";
if (drawEdit) {
html+= "<div id='" + this.marker("Bild/Delete") + "' class='deleter'><img src='/res/dark/delete.png' /></div>";
}
} else {
html+= "<img src='/upl/Personal/Bildadresse/Nopic.svg' />";
}
if (drawEdit) {
html+= "<form action='/" + this.marker("Bilder") + "' class='dropzone' id='dropzone_" + this.ID + "'></form>";
}
html += "</div><ul>";
// TODO: Draw selectbox
if (drawEdit) {
html+= this.renderField("number.png", this.renderInput("OFNr", this.OFNr, 1.5, 2) + "-" + this.renderInput("PNr", this.PNr, 2, 3))
+ this.renderField("person.png", this.renderInput("Nachnamen", this.Nachnamen) + ", " + this.renderInput("Vornamen", this.Vornamen))
+ this.renderField("type.png", this.renderSelect("Kategorie", this.Kategorie.ID))
+ this.renderField("note.png", this.renderInput("Login", this.Login) + "@feuerwehr-bs.net")
+ this.renderField("star.png", this.renderSelect("Funktion", this.Funktion.ID, 2.5))
+ this.renderButton("save.png", "Save", "Speichern")
+ this.renderButton("delete.png", "Delete", "Löschen")
+ this.renderButton("cancel.png", "Reset", "Zurücksetzen");
} else {
html+= this.renderField("number.png", this.Nr)
+ this.renderField("person.png", "<b>" + this.Nachnamen + ", " + this.Vornamen + "</b>")
+ (this.Login=="" || this.Login===null ? this.renderField("note.png", "<span class='red'>Kein Login!</span>") : "")
+ this.renderField("star.png", this.Funktion!==null ? this.Funktion.KÜRZEL : "-");
if (this.admin) {
html += this.renderButton("edit.png", "Edit", "Bearbeiten");
}
}
html+= "</ul>";
document.getElementById(this.marker("Main")).innerHTML = html;
}
static RenderAdd() {
let html = "<h1>Fahrzeug hinzufügen</h1>"
+ "<div class='toggleVisibility'><ul><li>Name: <input id='Personal/Add/Name' style='width: 8em;' /></li>"
+ "<li>Kürzel: <input id='Personal/Add/Kürzel' style='width: 4em;' /></li>"
+ "<li id='Personal/Add/Add' class='button'>Hinzufügen <img src='/res/dark/add.png' /></li></ul></div>";
return html;
}
renderGroup() {
return Personal.RenderGroup(this.groupName);
}
static RenderGroup(groupName) {
return {
begin: "<article id='Group/" + groupName + "'><div class='group_title'><h1>" + groupName + "</h1></div>",
end: "</article>"
};
}
static New(groupName, json) {
return new Personal(groupName, json);
}
collect() {
return Personal.Collect(this.ID);
}
static Collect(id, contextId = null) {
return {
ID: id,
OFNR: this.Value(id, "OFNr"),
PNR: this.Value(id, "PNr"),
NACHNAMEN: this.Value(id, "Nachnamen"),
VORNAMEN: this.Value(id, "Vornamen"),
KATEGORIE: this.Value(id, "Kategorie"),
LOGIN: this.Value(id, "Login"),
FUNKTION: this.Value(id, "Funktion")
};
}
static RenderSelect(selectId, fahrzeugId, nullable = true) {
let html = "<select id='" + selectId + "'>" + (nullable ? "<option value='null'></option>" : "");
for (let group in Personal.groups) {
for (let e in Personal.groups[group]) {
const entry = Personal.groups[group][e];
html+= "<option value='" + entry.ID + "'" + (entry.ID==fahrzeugId ? " selected" : "") + ">" + entry.Kürzel + "</option>";
}
}
html+= "</select>";
return html;
}
}

92
client/scanner.js Normal file
View File

@ -0,0 +1,92 @@
class Scanner
{
static api;
static settings;
static scanned;
constructor(parent) {
Scanner.api = parent;
this.qr = null;
this.cameras = [];
}
Start(id, subroute, parser) {
Scanner.settings = {
subroute: subroute,
parser: parser,
id: id
};
Scanner.scanned = {primary: {}, secondary: {}};
for (let i in parser) {
if (i!=subroute) {
Scanner.scanned.secondary[parser[i]] = null;
} }
// Find already parsed IDs
const subData = Scanner.api.activeRoute.resourcesIndex[id].data.SUB[parser[subroute]];
for (let i in subData) {
Scanner.scanned.primary[subData[i].ID] = true;
}
this.qr = new QrScanner(
document.getElementById("qrscanner_video"),
Scanner.parse,
{returnDetailedScanResult: true, highlightScanRegion: true, highlightCodeOutline: true}
);
if (QrScanner.hasCamera()) {
let _this = this;
QrScanner.listCameras(true)
.then(result => _this.cameras = result)
.catch(error => alert(error || 'Camera detection failed.'));
}
this.cameraIndex = 0;
document.getElementById("qrscanner_text").innerHTML = "";
this.qr.start();
}
Stop() {
this.qr.stop();
this.qr.destroy();
this.qr = null;
}
SwitchCamera() {
if (this.qr!==null && QrScanner.hasCamera()) {
++this.cameraIndex;
if (this.cameraIndex>=this.cameras.length) {
this.cameraIndex = 0;
}
this.qr.setCamera(this.cameras[this.cameraIndex].id);
}
}
static parse(result) {
let scan = result.data.split("/");
if (scan.length==3) {
let route = scan[1];
let subid = scan[2];
// Is it a route we should parse?
if (typeof(Scanner.settings.parser[route])!="undefined") {
const marker = Scanner.settings.parser[route];
// Is it the primary route and if yes, has this ID been parsed before?
if (route==Scanner.settings.subroute && typeof(Scanner.scanned.primary[subid])=="undefined") {
Scanner.scanned.primary[subid] = true;
let index = -1;
const options = Scanner.api.activeRoute.options[marker];
for (let i in options) {
if (options[i].ID==subid) {
index = i;
break;
} }
// Has the ID been found?
if (index>=0) {
document.getElementById("qrscanner_text").innerHTML += options[index].NAME + "<br />";
Scanner.api.Request("POST", Scanner.scanned.secondary, Scanner.settings.id, Scanner.settings.subroute, subid);
}
} else if(route!=Scanner.settings.subroute) { // Is it a secondary field?
Scanner.scanned.secondary[marker] = subid;
} } } }
}

271
client/template.js Normal file
View File

@ -0,0 +1,271 @@
class TemplateEngine
{
constructor(tpl, adminData) {
this.tpl = tpl;
this.adminData = adminData;
this.modeFallback = {
":EDIT": ":ADMIN",
":ADMIN": "",
"": null
};
}
Render(content, marker, mode = "") {
return this.render(marker, content, this.extractSection(this.tpl, marker, mode), mode);
}
render(sections, content, tpl, mode = "") {
// content has to be an object of `marker` => `data`
// data can be a primitive, an array, or an object
// Boolean corresponds to a conditional section, call replaceCondition
// String and number correspond to a simple marker, call replaceMarker for it
// Array and object always correspond to a section
// Object can be handed recursively to a new instance of render. Be sure to extract the correct section of tpl though
// If data is an array, iterate over it and hand every entry to a new instance like with object. Concatenate the results
if (typeof(content)!="object" || tpl=="")
return "";
var ret = tpl;
for (var marker in content) {
var sectionMarker = (sections!="" ? sections+"."+marker : marker);
var type = typeof(content[marker]);
if (mode==":EDIT" && sections.indexOf("MAIN")!=-1 && typeof(this.adminData[marker])!="undefined") {
var data = this.adminData[marker];
var admin_tpl = this.extractSection(ret, sectionMarker, mode);
var admin_ret = "";
for (var i in data) {
if (content[marker]==null) {
data[i].SELECTED = data[i].ID=="__NULL__";
} else if (Array.isArray(content[marker])) {
var selected = false;
for (var j in content[marker]) {
if (data[i].ID==content[marker][j].ID) {
selected = true;
}
}
data[i].SELECTED = selected;
} else {
data[i].SELECTED = data[i].ID==content[marker].ID;
}
admin_ret+= this.render(sectionMarker, data[i], admin_tpl, "");
}
ret = this.replaceSection(ret, sectionMarker, admin_ret, "");
} else {
if (content[marker]==null) {
ret = this.replaceSection(ret, sectionMarker, "", mode);
ret = this.replaceMarker(ret, sectionMarker, "__NULL__", mode);
} if (type=="boolean") {
ret = this.replaceCondition(ret, sectionMarker, content[marker], mode);
} else if (type=="string" && "__CASE__:"==content[marker].substr(0, 9)) {
ret = this.replaceSwitch(ret, sectionMarker, content[marker].split(":"), mode);
} else if (type=="string" || type=="number") {
ret = this.replaceMarker(ret, sectionMarker, content[marker], mode);
} else if (type=="object" && Array.isArray(content[marker])) {
var arr_tpl = this.extractSection(ret, sectionMarker, mode);
var arr_ret = "";
for (var ind in content[marker]) {
arr_ret+= this.render(sectionMarker, content[marker][ind], arr_tpl, mode);
}
ret = this.replaceSection(ret, sectionMarker, arr_ret, mode);
} else if (type=="object") { // Hopefully a JSON
ret = this.replaceSection(ret, sectionMarker, this.render(sectionMarker, content[marker], this.extractSection(ret, sectionMarker, mode), mode), mode);
}
}
}
return ret;
}
makeMarker(marker) {
return "###" + marker + "###";
}
makeSectionMarker(marker, pos) { //creates START or END marker of section
return "<!-- " + marker + " " + pos + " -->";
}
findMarkerModes(tpl, section) {
var ret = [];
var sectionMarker = this.makeMarker(section+":");
var markerLength = 5 + sectionMarker.length - 3; // 5 = "<!-- ", 3 = "###"
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, markerLength);
for (var startIndex = 0; startIndex < tpl.length;) {
var foundIndex = tpl.indexOf(anyMarker, startIndex);
if (foundIndex==-1) {
startIndex = tpl.length;
} else {
var markerEnd = tpl.indexOf("###", foundIndex + markerLength);
var mode = tpl.substr(foundIndex + markerLength - 1, markerEnd - foundIndex - markerLength + 1); // Should be e.g. :EDIT
var endMarker = this.makeSectionMarker(this.makeMarker(section + mode), "END");
var endPos = tpl.indexOf(endMarker, markerEnd);
if (endPos==-1) {
startIndex = tpl.length;
} else {
startIndex = endPos + markerLength;
ret.push(mode);
} } }
return ret;
}
deleteSection(tpl, section) {
var sectionMarker = this.makeMarker(section);
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = tpl.indexOf(anyMarker);
var endPos = tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
return tpl;
}
var ret = (startPos>0 ? tpl.substr(0, startPos) : "");
if (endPos<tpl.length) ret+= tpl.substr(endPos);
return ret;
}
findAndDeleteUnneededModes(tpl, section, mode = "") {
var ret = tpl;
// Find and destroy unneeded mode siblings
var modes = this.findMarkerModes(tpl, section);
var chosenMode = mode;
while (!modes.includes(chosenMode) && this.modeFallback[chosenMode]!=null) {
chosenMode = this.modeFallback[chosenMode];
}
for (var i in modes) {
if (modes[i]!=chosenMode) {
ret = this.deleteSection(ret, section + modes[i]);
}
}
if (chosenMode!="") {
ret = this.deleteSection(ret, section);
}
return {"tpl": ret, "mode": chosenMode};
}
extractSection(tpl, section, mode = "") { //Extracts a section from a template
var sectionMarker = this.makeMarker(section + mode);
var startMarker = this.makeSectionMarker(sectionMarker, "START");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = tpl.indexOf(startMarker) + startMarker.length;
var endPos = tpl.lastIndexOf(endMarker);
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
return (this.modeFallback[mode]!=null ? this.extractSection(tpl, section, this.modeFallback[mode]) : "");
}
return tpl.substr(startPos,endPos-startPos);
}
replaceSection(tpl, section, value, mode = "") { //replaces a section in a template with a value
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var startMarker = this.makeSectionMarker(sectionMarker, "START");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = modeTpl.tpl.indexOf(startMarker);
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {return modeTpl.tpl;}
var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceCondition(tpl, section, bool, mode = "") { // selects one of two sections in a template
// Template looks like this:
// <!-- ###SECTION### IF -->
// True text
// <!-- ###SECTION### ELSE -->
// False text
// <!-- ###SECTION### END -->
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var ifMarker = this.makeSectionMarker(sectionMarker, "IF");
var elseMarker = this.makeSectionMarker(sectionMarker, "ELSE");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = modeTpl.tpl.indexOf(ifMarker);
var else_startPos = modeTpl.tpl.indexOf(elseMarker);
var else_endPos = else_startPos + elseMarker.length;
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (else_startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
if (bool) {
return modeTpl.tpl;
} else {
return this.replaceSection(modeTpl.tpl, section, "", modeTpl.mode);
}
}
var value = "";
if (bool) {
// Show if-part
value = modeTpl.tpl.substr(startPos, else_startPos - startPos);
} else {
// Show else-part
value = modeTpl.tpl.substr(else_endPos, endPos - else_endPos - endMarker.length);
}
var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceSwitch(tpl, section, keys, mode = "") { // selects one or more of a variable number of cases from a section in a template
// Template looks like this:
// <!-- ###SECTION### SWITCH -->
// Default text
// <!-- ###SECTION### CASE_A -->
// Text A
// <!-- ###SECTION### CASE_B -->
// Text B
// <!-- ###SECTION### END -->
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var switchMarker = this.makeSectionMarker(sectionMarker, "SWITCH");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
var switchPos = modeTpl.tpl.indexOf(switchMarker);
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if (switchPos==-1 || endPos==-1) {return modeTpl.tpl;}
var value = "";
for (var i = 1; i<keys.length; i++) {
var keyMarker = this.makeSectionMarker(sectionMarker, keys[i]);
var keyPos = modeTpl.tpl.indexOf(keyMarker);
if (keyPos==-1) {
keyPos = switchPos + switchMarker.length; // Case `key` wasn't found, use default case
} else {
keyPos+= keyMarker.length;
}
// Find the start of the next marker.
var nextPos = modeTpl.tpl.indexOf(anyMarker, keyPos);
if (nextPos==-1) {
// Shouldn't happen. If it does, endMarker is before keyMarker
nextPos = endPos;
}
value+= modeTpl.tpl.substr(keyPos, nextPos - keyPos);
}
var ret = (switchPos>0 ? modeTpl.tpl.substr(0, switchPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceMarker(tpl, marker, value) { //replaces a marker in a template with a value
return tpl.replaceAll(this.makeMarker(marker), value);
}
/* replaceMarkerArray(tpl, array) { //replaces all markers in a template with values from an array (index defines marker)
for (var ind in array)
{
tpl = tpl.replaceAll(this.makeMarker(ind), array[ind]);
}
return tpl;
}*/
}

79
client/toastify.css Normal file
View File

@ -0,0 +1,79 @@
/*!
* Toastify js 1.9.3
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
.toastify {
padding: 12px 20px;
color: #ffffff;
display: inline-block;
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
background: linear-gradient(135deg, #73a5ff, #5477f5);
position: fixed;
opacity: 0;
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
border-radius: 2px;
cursor: pointer;
text-decoration: none;
max-width: calc(50% - 20px);
z-index: 2147483647;
}
.toastify.on {
opacity: 1;
}
.toast-close {
opacity: 0.4;
padding: 0 5px;
}
.toastify-right {
right: 15px;
}
.toastify-left {
left: 15px;
}
.toastify-top {
top: -150px;
}
.toastify-bottom {
bottom: -150px;
}
.toastify-rounded {
border-radius: 25px;
}
.toastify-avatar {
width: 1.5em;
height: 1.5em;
margin: -7px 5px;
border-radius: 2px;
}
.toastify-center {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
max-width: -moz-fit-content;
}
@media only screen and (max-width: 360px) {
.toastify-right, .toastify-left {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
}
}

394
client/toastify.js Normal file
View File

@ -0,0 +1,394 @@
/*!
* Toastify js 1.9.3
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
(function(root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.Toastify = factory();
}
})(this, function(global) {
// Object initialization
var Toastify = function(options) {
// Returning a new init object
return new Toastify.lib.init(options);
},
// Library version
version = "1.9.3";
// Defining the prototype of the object
Toastify.lib = Toastify.prototype = {
toastify: version,
constructor: Toastify,
// Initializing the object with required parameters
init: function(options) {
// Verifying and validating the input object
if (!options) {
options = {};
}
// Creating the options object
this.options = {};
this.toastElement = null;
// Validating the options
this.options.text = options.text || "Hi there!"; // Display message
this.options.node = options.node // Display content as node
this.options.duration = options.duration === 0 ? 0 : options.duration || 3000; // Display duration
this.options.selector = options.selector; // Parent selector
this.options.callback = options.callback || function() {}; // Callback after display
this.options.destination = options.destination; // On-click destination
this.options.newWindow = options.newWindow || false; // Open destination in new window
this.options.close = options.close || false; // Show toast close icon
this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top"; // toast position - top or bottom
this.options.positionLeft = options.positionLeft || false; // toast position - left or right
this.options.position = options.position || ''; // toast position - left or right
this.options.backgroundColor = options.backgroundColor; // toast background color
this.options.avatar = options.avatar || ""; // img element src - url or a path
this.options.className = options.className || ""; // additional class names for the toast
this.options.stopOnFocus = options.stopOnFocus === undefined? true: options.stopOnFocus; // stop timeout on focus
this.options.onClick = options.onClick; // Callback after click
this.options.offset = options.offset || { x: 0, y: 0 }; // toast offset
// Returning the current object for chaining functions
return this;
},
// Building the DOM element
buildToast: function() {
// Validating if the options are defined
if (!this.options) {
throw "Toastify is not initialized";
}
// Creating the DOM object
var divElement = document.createElement("div");
divElement.className = "toastify on " + this.options.className;
// Positioning toast to left or right or center
if (!!this.options.position) {
divElement.className += " toastify-" + this.options.position;
} else {
// To be depreciated in further versions
if (this.options.positionLeft === true) {
divElement.className += " toastify-left";
console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')
} else {
// Default position
divElement.className += " toastify-right";
}
}
// Assigning gravity of element
divElement.className += " " + this.options.gravity;
if (this.options.backgroundColor) {
divElement.style.background = this.options.backgroundColor;
}
// Adding the toast message/node
if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
// If we have a valid node, we insert it
divElement.appendChild(this.options.node)
} else {
divElement.innerHTML = this.options.text;
if (this.options.avatar !== "") {
var avatarElement = document.createElement("img");
avatarElement.src = this.options.avatar;
avatarElement.className = "toastify-avatar";
if (this.options.position == "left" || this.options.positionLeft === true) {
// Adding close icon on the left of content
divElement.appendChild(avatarElement);
} else {
// Adding close icon on the right of content
divElement.insertAdjacentElement("afterbegin", avatarElement);
}
}
}
// Adding a close icon to the toast
if (this.options.close === true) {
// Create a span for close element
var closeElement = document.createElement("span");
closeElement.innerHTML = "&#10006;";
closeElement.className = "toast-close";
// Triggering the removal of toast from DOM on close click
closeElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.removeElement(this.toastElement);
window.clearTimeout(this.toastElement.timeOutValue);
}.bind(this)
);
//Calculating screen width
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Adding the close icon to the toast element
// Display on the right if screen width is less than or equal to 360px
if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
// Adding close icon on the left of content
divElement.insertAdjacentElement("afterbegin", closeElement);
} else {
// Adding close icon on the right of content
divElement.appendChild(closeElement);
}
}
// Clear timeout while toast is focused
if (this.options.stopOnFocus && this.options.duration > 0) {
var self = this;
// stop countdown
divElement.addEventListener(
"mouseover",
function(event) {
window.clearTimeout(divElement.timeOutValue);
}
)
// add back the timeout
divElement.addEventListener(
"mouseleave",
function() {
divElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
self.removeElement(divElement);
},
self.options.duration
)
}
)
}
// Adding an on-click destination path
if (typeof this.options.destination !== "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
if (this.options.newWindow === true) {
window.open(this.options.destination, "_blank");
} else {
window.location = this.options.destination;
}
}.bind(this)
);
}
if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.options.onClick();
}.bind(this)
);
}
// Adding offset
if(typeof this.options.offset === "object") {
var x = getAxisOffsetAValue("x", this.options);
var y = getAxisOffsetAValue("y", this.options);
var xOffset = this.options.position == "left" ? x : "-" + x;
var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y;
divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")";
}
// Returning the generated element
return divElement;
},
// Displaying the toast
showToast: function() {
// Creating the DOM object for the toast
this.toastElement = this.buildToast();
// Getting the root element to with the toast needs to be added
var rootElement;
if (typeof this.options.selector === "undefined") {
rootElement = document.body;
} else {
rootElement = document.getElementById(this.options.selector);
}
// Validating if root element is present in DOM
if (!rootElement) {
throw "Root element is not defined";
}
// Adding the DOM element
rootElement.insertBefore(this.toastElement, rootElement.firstChild);
// Repositioning the toasts in case multiple toasts are present
Toastify.reposition();
if (this.options.duration > 0) {
this.toastElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
this.removeElement(this.toastElement);
}.bind(this),
this.options.duration
); // Binding `this` for function invocation
}
// Supporting function chaining
return this;
},
hideToast: function() {
if (this.toastElement.timeOutValue) {
clearTimeout(this.toastElement.timeOutValue);
}
this.removeElement(this.toastElement);
},
// Removing the element from the DOM
removeElement: function(toastElement) {
// Hiding the element
// toastElement.classList.remove("on");
toastElement.className = toastElement.className.replace(" on", "");
// Removing the element from DOM after transition end
window.setTimeout(
function() {
// remove options node if any
if (this.options.node && this.options.node.parentNode) {
this.options.node.parentNode.removeChild(this.options.node);
}
// Remove the elemenf from the DOM, only when the parent node was not removed before.
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
// Calling the callback function
this.options.callback.call(toastElement);
// Repositioning the toasts again
Toastify.reposition();
}.bind(this),
400
); // Binding `this` for function invocation
},
};
// Positioning the toasts on the DOM
Toastify.reposition = function() {
// Top margins with gravity
var topLeftOffsetSize = {
top: 15,
bottom: 15,
};
var topRightOffsetSize = {
top: 15,
bottom: 15,
};
var offsetSize = {
top: 15,
bottom: 15,
};
// Get all toast messages on the DOM
var allToasts = document.getElementsByClassName("toastify");
var classUsed;
// Modifying the position of each toast element
for (var i = 0; i < allToasts.length; i++) {
// Getting the applied gravity
if (containsClass(allToasts[i], "toastify-top") === true) {
classUsed = "toastify-top";
} else {
classUsed = "toastify-bottom";
}
var height = allToasts[i].offsetHeight;
classUsed = classUsed.substr(9, classUsed.length-1)
// Spacing between toasts
var offset = 15;
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Show toast in center if screen with less than or qual to 360px
if (width <= 360) {
// Setting the position
allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
offsetSize[classUsed] += height + offset;
} else {
if (containsClass(allToasts[i], "toastify-left") === true) {
// Setting the position
allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
topLeftOffsetSize[classUsed] += height + offset;
} else {
// Setting the position
allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
topRightOffsetSize[classUsed] += height + offset;
}
}
}
// Supporting function chaining
return this;
};
// Helper function to get offset.
function getAxisOffsetAValue(axis, options) {
if(options.offset[axis]) {
if(isNaN(options.offset[axis])) {
return options.offset[axis];
}
else {
return options.offset[axis] + 'px';
}
}
return '0px';
}
function containsClass(elem, yourClass) {
if (!elem || typeof yourClass !== "string") {
return false;
} else if (
elem.className &&
elem.className
.trim()
.split(/\s+/gi)
.indexOf(yourClass) > -1
) {
return true;
} else {
return false;
}
}
// Setting up the prototype for the init object
Toastify.lib.init.prototype = Toastify.lib;
// Returning the Toastify function to be assigned to the window object/module
return Toastify;
});

7
conf/10_mysql.php.dist Normal file
View File

@ -0,0 +1,7 @@
<?php #var/00_mysql.php
//Mysql login
define("DBHOST", "localhost");
define("DBUSER", "");
define("DBPASS", "");
define("DBNAME", "");

4
conf/20_redis.php Normal file
View File

@ -0,0 +1,4 @@
<?php #var/01_redis.php
define("REDIS_SOCKET", "/run/redis/redis.sock");

12
conf/30_login.php Normal file
View File

@ -0,0 +1,12 @@
<?php #var/30_login.php
//Login related stuff
define("DESIRED_ITERATIONS", 160000);
define(
"JWT_KEY",
"v9upLZkm7uVGH9zqEGnp4sZAqeJX33vygYBTKEGdVqtsKeEKaSGp2KBnZmcVNPDb"
."3BkXGwdp9GMDwjaqp6BqfVNRYQf9X5PC8BP74UZPKGPujCeAsayMRpLv2GPBUgXz"
."TVY36KcpYy77SPUmZ7zY2q7ZTFEvM73jMBufdUV8fcEDp4eTQDqMpvp8dhkDxZPj"
."wVYtkHxLtkTHVa9DMm2pWmXqhsjJckgPMxdRsgngCSv8H8BBjXFkG2dn7ndJeApF"
);
define("JWT_VALID_TIME", 7 * 24 * 60 * 60);

6
conf/40_time.php Normal file
View File

@ -0,0 +1,6 @@
<?php #conf/40_time.php
// Date and time locale
setlocale(LC_TIME, "de_DE.UTF-8");
setlocale(LC_ALL,"de_DE.UTF8");
$formatter = new IntlDateFormatter('de_DE', IntlDateFormatter::LONG, IntlDateFormatter::NONE);

188
conf/50_misc.php Normal file
View File

@ -0,0 +1,188 @@
<?php #conf/50_misc.php
//Additional stuff
define ("TITLE", "OF Innenstadt-App");
define("MIME_MAP", [
'video/3gpp2' => '3g2',
'video/3gp' => '3gp',
'video/3gpp' => '3gp',
'application/x-compressed' => '7zip',
'audio/x-acc' => 'aac',
'audio/ac3' => 'ac3',
'application/postscript' => 'ai',
'audio/x-aiff' => 'aif',
'audio/aiff' => 'aif',
'audio/x-au' => 'au',
'video/x-msvideo' => 'avi',
'video/msvideo' => 'avi',
'video/avi' => 'avi',
'application/x-troff-msvideo' => 'avi',
'application/macbinary' => 'bin',
'application/mac-binary' => 'bin',
'application/x-binary' => 'bin',
'application/x-macbinary' => 'bin',
'image/bmp' => 'bmp',
'image/x-bmp' => 'bmp',
'image/x-bitmap' => 'bmp',
'image/x-xbitmap' => 'bmp',
'image/x-win-bitmap' => 'bmp',
'image/x-windows-bmp' => 'bmp',
'image/ms-bmp' => 'bmp',
'image/x-ms-bmp' => 'bmp',
'application/bmp' => 'bmp',
'application/x-bmp' => 'bmp',
'application/x-win-bitmap' => 'bmp',
'application/cdr' => 'cdr',
'application/coreldraw' => 'cdr',
'application/x-cdr' => 'cdr',
'application/x-coreldraw' => 'cdr',
'image/cdr' => 'cdr',
'image/x-cdr' => 'cdr',
'zz-application/zz-winassoc-cdr' => 'cdr',
'application/mac-compactpro' => 'cpt',
'application/pkix-crl' => 'crl',
'application/pkcs-crl' => 'crl',
'application/x-x509-ca-cert' => 'crt',
'application/pkix-cert' => 'crt',
'text/css' => 'css',
'text/x-comma-separated-values' => 'csv',
'text/comma-separated-values' => 'csv',
'application/vnd.msexcel' => 'csv',
'application/x-director' => 'dcr',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/x-dvi' => 'dvi',
'message/rfc822' => 'eml',
'application/x-msdownload' => 'exe',
'video/x-f4v' => 'f4v',
'audio/x-flac' => 'flac',
'video/x-flv' => 'flv',
'image/gif' => 'gif',
'application/gpg-keys' => 'gpg',
'application/x-gtar' => 'gtar',
'application/x-gzip' => 'gzip',
'application/mac-binhex40' => 'hqx',
'application/mac-binhex' => 'hqx',
'application/x-binhex40' => 'hqx',
'application/x-mac-binhex40' => 'hqx',
'text/html' => 'html',
'image/x-icon' => 'ico',
'image/x-ico' => 'ico',
'image/vnd.microsoft.icon' => 'ico',
'text/calendar' => 'ics',
'application/java-archive' => 'jar',
'application/x-java-application' => 'jar',
'application/x-jar' => 'jar',
'image/jp2' => 'jp2',
'video/mj2' => 'jp2',
'image/jpx' => 'jp2',
'image/jpm' => 'jp2',
'image/jpeg' => 'jpg',
'image/pjpeg' => 'jpg',
'application/x-javascript' => 'js',
'application/json' => 'json',
'text/json' => 'json',
'application/vnd.google-earth.kml+xml' => 'kml',
'application/vnd.google-earth.kmz' => 'kmz',
'text/x-log' => 'log',
'audio/x-m4a' => 'm4a',
'application/vnd.mpegurl' => 'm4u',
'audio/midi' => 'mid',
'application/vnd.mif' => 'mif',
'video/quicktime' => 'mov',
'video/x-sgi-movie' => 'movie',
'audio/mpeg' => 'mp3',
'audio/mpg' => 'mp3',
'audio/mpeg3' => 'mp3',
'audio/mp3' => 'mp3',
'video/mp4' => 'mp4',
'video/mpeg' => 'mpeg',
'application/oda' => 'oda',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'audio/ogg' => 'ogg',
'video/ogg' => 'ogg',
'application/ogg' => 'ogg',
'application/x-pkcs10' => 'p10',
'application/pkcs10' => 'p10',
'application/x-pkcs12' => 'p12',
'application/x-pkcs7-signature' => 'p7a',
'application/pkcs7-mime' => 'p7c',
'application/x-pkcs7-mime' => 'p7c',
'application/x-pkcs7-certreqresp' => 'p7r',
'application/pkcs7-signature' => 'p7s',
'application/pdf' => 'pdf',
'application/octet-stream' => 'pdf',
'application/x-x509-user-cert' => 'pem',
'application/x-pem-file' => 'pem',
'application/pgp' => 'pgp',
'application/x-httpd-php' => 'php',
'application/php' => 'php',
'application/x-php' => 'php',
'text/php' => 'php',
'text/x-php' => 'php',
'application/x-httpd-php-source' => 'php',
'image/png' => 'png',
'image/x-png' => 'png',
'application/powerpoint' => 'ppt',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.ms-office' => 'ppt',
'application/msword' => 'doc',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/x-photoshop' => 'psd',
'image/vnd.adobe.photoshop' => 'psd',
'audio/x-realaudio' => 'ra',
'audio/x-pn-realaudio' => 'ram',
'application/x-rar' => 'rar',
'application/rar' => 'rar',
'application/x-rar-compressed' => 'rar',
'audio/x-pn-realaudio-plugin' => 'rpm',
'application/x-pkcs7' => 'rsa',
'text/rtf' => 'rtf',
'text/richtext' => 'rtx',
'video/vnd.rn-realvideo' => 'rv',
'application/x-stuffit' => 'sit',
'application/smil' => 'smil',
'text/srt' => 'srt',
'image/svg+xml' => 'svg',
'application/x-shockwave-flash' => 'swf',
'application/x-tar' => 'tar',
'application/x-gzip-compressed' => 'tgz',
'image/tiff' => 'tiff',
'text/plain' => 'txt',
'text/x-vcard' => 'vcf',
'application/videolan' => 'vlc',
'text/vtt' => 'vtt',
'audio/x-wav' => 'wav',
'audio/wave' => 'wav',
'audio/wav' => 'wav',
'application/wbxml' => 'wbxml',
'video/webm' => 'webm',
'audio/x-ms-wma' => 'wma',
'application/wmlc' => 'wmlc',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-asf' => 'wmv',
'application/xhtml+xml' => 'xhtml',
'application/excel' => 'xl',
'application/msexcel' => 'xls',
'application/x-msexcel' => 'xls',
'application/x-ms-excel' => 'xls',
'application/x-excel' => 'xls',
'application/x-dos_ms_excel' => 'xls',
'application/xls' => 'xls',
'application/x-xls' => 'xls',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.ms-excel' => 'xlsx',
'application/xml' => 'xml',
'text/xml' => 'xml',
'text/xsl' => 'xsl',
'application/xspf+xml' => 'xspf',
'application/x-compress' => 'z',
'application/x-zip' => 'zip',
'application/zip' => 'zip',
'application/x-zip-compressed' => 'zip',
'application/s-compressed' => 'zip',
'multipart/x-zip' => 'zip',
'text/x-scriptzsh' => 'zsh',
]);

BIN
database.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

View File

@ -1,858 +0,0 @@
-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Host: localhost
-- Generation Time: Nov 25, 2020 at 05:27 PM
-- Server version: 10.5.8-MariaDB
-- PHP Version: 7.4.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `Feuerwehr_Kameradenverwaltung`
--
-- --------------------------------------------------------
--
-- Table structure for table `Abteilungen`
--
CREATE TABLE `Abteilungen` (
`ID` int(11) NOT NULL,
`Kürzel` varchar(3) NOT NULL,
`Name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":29}';
-- --------------------------------------------------------
--
-- Table structure for table `Ausbildungen`
--
CREATE TABLE `Ausbildungen` (
`ID` int(11) NOT NULL,
`Kürzel` varchar(3) DEFAULT NULL,
`Name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":24}';
-- --------------------------------------------------------
--
-- Table structure for table `Berechtigungen`
--
CREATE TABLE `Berechtigungen` (
`ID` int(11) NOT NULL,
`Name` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `Dienstbuch`
--
CREATE TABLE `Dienstbuch` (
`ID` int(11) NOT NULL,
`Abteilung` int(11) NOT NULL,
`Anfang` datetime NOT NULL,
`Ende` datetime NOT NULL,
`Leiter` int(11) NOT NULL,
`Ort` varchar(50) NOT NULL,
`Thema` varchar(50) NOT NULL,
`Bemerkungen` varchar(250) NOT NULL,
`Kategorie` int(11) NOT NULL,
`TM2Thema` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":11}';
-- --------------------------------------------------------
--
-- Table structure for table `Dienstpläne`
--
CREATE TABLE `Dienstpläne` (
`ID` int(11) NOT NULL,
`Abteilung` int(11) NOT NULL,
`Jahr` smallint(4) NOT NULL,
`Name` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":11,"target":"concat(Jahr, '' '', Name)"}';
-- --------------------------------------------------------
--
-- Table structure for table `Einsatzarten`
--
CREATE TABLE `Einsatzarten` (
`ID` int(11) NOT NULL COMMENT '{}',
`Einsatzkategorie` int(11) NOT NULL COMMENT '{"target":"name"}',
`Name` varchar(255) NOT NULL,
`Stichworte` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":31,"order":{"column":"Name","sorting":"asc"}}';
-- --------------------------------------------------------
--
-- Table structure for table `Einsatzkategorien`
--
CREATE TABLE `Einsatzkategorien` (
`ID` int(11) NOT NULL COMMENT '{}',
`Name` varchar(255) NOT NULL,
`Farbe` varchar(255) NOT NULL DEFAULT '#000000'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":30,"order":{"column":"Name","sorting":"asc"}}';
-- --------------------------------------------------------
--
-- Table structure for table `Einsätze`
--
CREATE TABLE `Einsätze` (
`ID` int(11) NOT NULL,
`Alarmierungszeit` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`Einsatzende` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`Adresse` varchar(255) NOT NULL,
`Einsatzart` int(11) DEFAULT NULL,
`Zusammenfassung` varchar(255) NOT NULL,
`Einsatzleiter` varchar(255) NOT NULL DEFAULT '',
`Überprüft` bit(1) NOT NULL DEFAULT b'0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":13,"order":{"column":"Alarmierungszeit","sorting":"desc"},"target":"concat(Adresse, date_format(Alarmierungszeit, '' (%d.%m.%Y)''))"}';
-- --------------------------------------------------------
--
-- Table structure for table `Kommandofunktionen`
--
CREATE TABLE `Kommandofunktionen` (
`ID` int(11) NOT NULL,
`Kürzel` varchar(3) NOT NULL,
`Name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":20}';
-- --------------------------------------------------------
--
-- Table structure for table `link_Abteilungen_Personal`
--
CREATE TABLE `link_Abteilungen_Personal` (
`Abteilungen` int(11) NOT NULL COMMENT '{"dynitem":true}',
`Personal` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `link_Ausbildungen_Personal`
--
CREATE TABLE `link_Ausbildungen_Personal` (
`Ausbildungen` int(11) NOT NULL COMMENT '{"dynitem":true}',
`Personal` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"show":false}';
-- --------------------------------------------------------
--
-- Table structure for table `link_Berechtigungen_Personalgruppen`
--
CREATE TABLE `link_Berechtigungen_Personalgruppen` (
`Berechtigungen` int(11) NOT NULL,
`Personalgruppen` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `link_Personal_Personalgruppen`
--
CREATE TABLE `link_Personal_Personalgruppen` (
`Personal` int(11) NOT NULL,
`Personalgruppen` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `Personal`
--
CREATE TABLE `Personal` (
`ID` int(11) NOT NULL,
`OFnr` tinyint(2) UNSIGNED DEFAULT 56 COMMENT '{"group":"nummer","group_title":"P-Nr.","postfix":"-"}',
`Pnr` smallint(3) UNSIGNED ZEROFILL DEFAULT NULL COMMENT '{"group":"nummer"}',
`Login` varchar(50) NOT NULL,
`Nachnamen` varchar(255) NOT NULL COMMENT '{"group":"Name","group_title":"Namen","group_sep":", "}',
`Vornamen` varchar(255) NOT NULL COMMENT '{"group":"Name"}',
`Bildadresse` varchar(255) DEFAULT NULL COMMENT '{"input":"file"}',
`Pool` int(11) DEFAULT NULL COMMENT '{"dynitem":true}',
`Kommandofkt` int(11) DEFAULT NULL COMMENT '{"dynitem":true}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":10,"order":{"column":"Name","sorting":"asc"},"target":"concat(left(Vornamen, 1), ''. '', Nachnamen)"}';
-- --------------------------------------------------------
--
-- Table structure for table `Personalgruppen`
--
CREATE TABLE `Personalgruppen` (
`ID` int(11) NOT NULL,
`Kürzel` varchar(3) NOT NULL,
`Name` varchar(255) DEFAULT NULL,
`Abteilung` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `Pools`
--
CREATE TABLE `Pools` (
`ID` int(11) NOT NULL,
`Name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":21}';
-- --------------------------------------------------------
--
-- Table structure for table `print_elements`
--
CREATE TABLE `print_elements` (
`ID` int(11) NOT NULL COMMENT '{}',
`key_layout_ID` int(11) NOT NULL COMMENT '{"title":"Drucklayout","dynitem":true,"target":"name"}',
`cond` varchar(255) NOT NULL COMMENT '{"title":"Bedingung"}',
`content` varchar(255) NOT NULL DEFAULT '#%field%#' COMMENT '{"title":"Inhalt"}',
`posl` decimal(10,0) NOT NULL COMMENT '{"title":"Pos. von links [mm]"}',
`post` decimal(10,0) NOT NULL COMMENT '{"title":"Pos. von oben [mm]"}',
`dimw` decimal(10,0) NOT NULL COMMENT '{"title":"Breite [mm]"}',
`dimh` decimal(10,0) NOT NULL COMMENT '{"title":"Höhe [mm]"}',
`type` varchar(255) NOT NULL DEFAULT 'text' COMMENT '{"title":"Art"}',
`params` varchar(255) NOT NULL DEFAULT '{}' COMMENT '{"title":"Parameter"}',
`ord` int(11) NOT NULL COMMENT '{"title":"Reihenfolge"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":99,"show":true,"title":"Druckelemente"}';
-- --------------------------------------------------------
--
-- Table structure for table `print_layouts`
--
CREATE TABLE `print_layouts` (
`ID` int(11) NOT NULL COMMENT '{}',
`name` varchar(255) NOT NULL COMMENT '{"title":"Bezeichnung"}',
`page` varchar(255) NOT NULL COMMENT '{"title":"Seite","options":"pages"}',
`width` int(10) UNSIGNED NOT NULL COMMENT '{"title":"Breite"}',
`height` int(10) UNSIGNED NOT NULL COMMENT '{"title":"Höhe"}',
`options` varchar(255) NOT NULL COMMENT '{"title":"Optionen"}',
`prio` tinyint(1) NOT NULL DEFAULT 0 COMMENT '{"title":"Bevorzugt"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":98,"show":true,"title":"Drucklayouts"}';
-- --------------------------------------------------------
--
-- Table structure for table `sys_failedlogins`
--
CREATE TABLE `sys_failedlogins` (
`ID` int(11) NOT NULL COMMENT '{"title":"ID", "target":"login"}',
`time` int(11) NOT NULL COMMENT '{"title":"Zeitstempel"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"title":"FailedLogin"}';
-- --------------------------------------------------------
--
-- Table structure for table `sys_iservhashes`
--
CREATE TABLE `sys_iservhashes` (
`ID` int(11) NOT NULL,
`Hash` char(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `sys_pass`
--
CREATE TABLE `sys_pass` (
`ID` int(11) NOT NULL COMMENT '{"title":"ID", "target":"login"}',
`iterations` int(11) NOT NULL COMMENT '{"title":"Iterations"}',
`salt` char(32) NOT NULL COMMENT '{"title":"Salt"}',
`hash` char(64) NOT NULL COMMENT '{"title":"Hash"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{}';
-- --------------------------------------------------------
--
-- Table structure for table `sys_uaccess`
--
CREATE TABLE `sys_uaccess` (
`ID` int(11) NOT NULL,
`key_sys_user_ID` int(11) NOT NULL COMMENT '{"title":"User", "target":"login"}',
`page` varchar(255) NOT NULL COMMENT '{"title":"Seite","options":"pages"}',
`access` tinyint(2) NOT NULL DEFAULT 0 COMMENT '{"title":"Seitenzugriff"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{}';
-- --------------------------------------------------------
--
-- Table structure for table `sys_user`
--
CREATE TABLE `sys_user` (
`ID` int(11) NOT NULL COMMENT '{"title":"ID"}',
`login` varchar(32) NOT NULL COMMENT '{"title":"Login"}',
`email` varchar(64) NOT NULL COMMENT '{"title":"eMail"}',
`key_personal_ID` int(11) DEFAULT NULL COMMENT '{"title":"Nachnamen","target":"nachnamen"}',
`access` tinyint(2) NOT NULL DEFAULT 0 COMMENT '{"title":"Hauptzugriff"}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{}';
-- --------------------------------------------------------
--
-- Table structure for table `Teilnahmestatus`
--
CREATE TABLE `Teilnahmestatus` (
`ID` int(11) NOT NULL,
`Name` varchar(10) NOT NULL,
`Loswert` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `Terminarten`
--
CREATE TABLE `Terminarten` (
`ID` int(11) NOT NULL COMMENT '{}',
`Name` varchar(255) NOT NULL,
`Präfix` varchar(50) NOT NULL DEFAULT '',
`Farbe` varchar(7) NOT NULL DEFAULT '#000000',
`Beschreibung` text NOT NULL,
`Verwaltungsrecht` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":31,"order":{"column":"Name","sorting":"asc"}}';
-- --------------------------------------------------------
--
-- Table structure for table `Termine`
--
CREATE TABLE `Termine` (
`ID` int(11) NOT NULL COMMENT '{}',
`Beginn` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`Ende` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`Thema` varchar(255) CHARACTER SET utf8 NOT NULL,
`Ort` varchar(255) CHARACTER SET utf8 NOT NULL,
`Verantwortliche` varchar(255) CHARACTER SET utf8 NOT NULL,
`Dienstplan` int(11) DEFAULT NULL,
`Terminart` int(11) DEFAULT NULL,
`Platzvergabe` int(11) DEFAULT NULL,
`Ausgelost` tinyint(1) NOT NULL DEFAULT 0,
`Personalgruppe` int(11) DEFAULT NULL,
`Hash` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '{"input":"hash"}',
`TeilnahmeGeändert` tinyint(1) DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{"sort":12,"order":{"column":"Beginn","sorting":"desc"},"target":"concat(date_format(Beginn, ''%d.%m.''), '' '', Thema)"}';
-- --------------------------------------------------------
--
-- Table structure for table `Terminplatzvergabe`
--
CREATE TABLE `Terminplatzvergabe` (
`ID` int(11) NOT NULL,
`Name` varchar(25) NOT NULL,
`Tage` int(11) NOT NULL DEFAULT 0,
`LoescheGruppeTage` int(11) DEFAULT NULL,
`MaxTeilnehmer` int(11) NOT NULL DEFAULT 0,
`MinZF` int(11) NOT NULL DEFAULT 0,
`MinGF` int(11) NOT NULL DEFAULT 0,
`MinF` int(11) NOT NULL DEFAULT 0,
`MinKFohneF` int(11) NOT NULL DEFAULT 0,
`MinKF` int(11) NOT NULL DEFAULT 0,
`MinAGTohneF` int(11) NOT NULL DEFAULT 0,
`MinAGT` int(11) NOT NULL DEFAULT 0,
`MinTHohneF` int(11) NOT NULL DEFAULT 0,
`MinTH` int(11) NOT NULL DEFAULT 0,
`MinNon` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `Terminteilnahmen`
--
CREATE TABLE `Terminteilnahmen` (
`Termin` int(11) NOT NULL,
`Personal` int(11) NOT NULL,
`Status` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `Abteilungen`
--
ALTER TABLE `Abteilungen`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `Ausbildungen`
--
ALTER TABLE `Ausbildungen`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `Berechtigungen`
--
ALTER TABLE `Berechtigungen`
ADD PRIMARY KEY (`ID`),
ADD UNIQUE KEY `Berechtigungen_UN` (`Name`);
--
-- Indexes for table `Dienstbuch`
--
ALTER TABLE `Dienstbuch`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_dienstplaene_abteilung` (`Abteilung`);
--
-- Indexes for table `Dienstpläne`
--
ALTER TABLE `Dienstpläne`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_dienstplaene_abteilung` (`Abteilung`);
--
-- Indexes for table `Einsatzarten`
--
ALTER TABLE `Einsatzarten`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_einsatzarten_einsatzkategorie` (`Einsatzkategorie`);
--
-- Indexes for table `Einsatzkategorien`
--
ALTER TABLE `Einsatzkategorien`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `Einsätze`
--
ALTER TABLE `Einsätze`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_einsatzberichte_einsatzart` (`Einsatzart`);
--
-- Indexes for table `Kommandofunktionen`
--
ALTER TABLE `Kommandofunktionen`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `link_Abteilungen_Personal`
--
ALTER TABLE `link_Abteilungen_Personal`
ADD PRIMARY KEY (`Abteilungen`,`Personal`),
ADD KEY `fk_link_Abteilungen_Personal_2` (`Personal`);
--
-- Indexes for table `link_Ausbildungen_Personal`
--
ALTER TABLE `link_Ausbildungen_Personal`
ADD PRIMARY KEY (`Ausbildungen`,`Personal`),
ADD KEY `fk_link_personal_ausbildungen_1` (`Personal`),
ADD KEY `fk_link_personal_ausbildungen_2` (`Ausbildungen`);
--
-- Indexes for table `link_Berechtigungen_Personalgruppen`
--
ALTER TABLE `link_Berechtigungen_Personalgruppen`
ADD PRIMARY KEY (`Berechtigungen`,`Personalgruppen`),
ADD KEY `fk_link_Berechtigungen_Personalgruppen_2` (`Personalgruppen`);
--
-- Indexes for table `link_Personal_Personalgruppen`
--
ALTER TABLE `link_Personal_Personalgruppen`
ADD PRIMARY KEY (`Personal`,`Personalgruppen`),
ADD KEY `fk_link_Personal_Personalgruppen_2` (`Personalgruppen`);
--
-- Indexes for table `Personal`
--
ALTER TABLE `Personal`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_personal_1` (`Kommandofkt`),
ADD KEY `fk_personal_2` (`Pool`);
--
-- Indexes for table `Personalgruppen`
--
ALTER TABLE `Personalgruppen`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_Personalgruppen_1` (`Abteilung`);
--
-- Indexes for table `Pools`
--
ALTER TABLE `Pools`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `print_elements`
--
ALTER TABLE `print_elements`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_print_elements_1` (`key_layout_ID`);
--
-- Indexes for table `print_layouts`
--
ALTER TABLE `print_layouts`
ADD PRIMARY KEY (`ID`),
ADD KEY `page` (`page`);
--
-- Indexes for table `sys_failedlogins`
--
ALTER TABLE `sys_failedlogins`
ADD PRIMARY KEY (`ID`,`time`);
--
-- Indexes for table `sys_iservhashes`
--
ALTER TABLE `sys_iservhashes`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `sys_pass`
--
ALTER TABLE `sys_pass`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `sys_uaccess`
--
ALTER TABLE `sys_uaccess`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_sys_access_1` (`key_sys_user_ID`);
--
-- Indexes for table `sys_user`
--
ALTER TABLE `sys_user`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_sys_users_1` (`key_personal_ID`);
--
-- Indexes for table `Teilnahmestatus`
--
ALTER TABLE `Teilnahmestatus`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `Terminarten`
--
ALTER TABLE `Terminarten`
ADD PRIMARY KEY (`ID`),
ADD KEY `Terminarten_FK` (`Verwaltungsrecht`);
--
-- Indexes for table `Termine`
--
ALTER TABLE `Termine`
ADD PRIMARY KEY (`ID`),
ADD KEY `fk_Termine_Dienstplan` (`Dienstplan`),
ADD KEY `fk_Termine_Terminart` (`Terminart`),
ADD KEY `Termine_FK` (`Platzvergabe`),
ADD KEY `fk_Termine_Personalgruppe` (`Personalgruppe`);
--
-- Indexes for table `Terminplatzvergabe`
--
ALTER TABLE `Terminplatzvergabe`
ADD PRIMARY KEY (`ID`);
--
-- Indexes for table `Terminteilnahmen`
--
ALTER TABLE `Terminteilnahmen`
ADD PRIMARY KEY (`Termin`,`Personal`),
ADD KEY `fk_terminteilnahmen_1` (`Personal`),
ADD KEY `fk_terminteilnahmen_3` (`Status`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `Abteilungen`
--
ALTER TABLE `Abteilungen`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Ausbildungen`
--
ALTER TABLE `Ausbildungen`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Berechtigungen`
--
ALTER TABLE `Berechtigungen`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Dienstbuch`
--
ALTER TABLE `Dienstbuch`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Dienstpläne`
--
ALTER TABLE `Dienstpläne`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Einsatzarten`
--
ALTER TABLE `Einsatzarten`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `Einsatzkategorien`
--
ALTER TABLE `Einsatzkategorien`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `Einsätze`
--
ALTER TABLE `Einsätze`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Kommandofunktionen`
--
ALTER TABLE `Kommandofunktionen`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Personal`
--
ALTER TABLE `Personal`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Personalgruppen`
--
ALTER TABLE `Personalgruppen`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Pools`
--
ALTER TABLE `Pools`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `print_elements`
--
ALTER TABLE `print_elements`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `print_layouts`
--
ALTER TABLE `print_layouts`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `sys_uaccess`
--
ALTER TABLE `sys_uaccess`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `sys_user`
--
ALTER TABLE `sys_user`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{"title":"ID"}';
--
-- AUTO_INCREMENT for table `Teilnahmestatus`
--
ALTER TABLE `Teilnahmestatus`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `Terminarten`
--
ALTER TABLE `Terminarten`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `Termine`
--
ALTER TABLE `Termine`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '{}';
--
-- AUTO_INCREMENT for table `Terminplatzvergabe`
--
ALTER TABLE `Terminplatzvergabe`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `Dienstpläne`
--
ALTER TABLE `Dienstpläne`
ADD CONSTRAINT `fk_dienstplaene_abteilung` FOREIGN KEY (`Abteilung`) REFERENCES `Abteilungen` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `Einsatzarten`
--
ALTER TABLE `Einsatzarten`
ADD CONSTRAINT `fk_einsatzarten_einsatzkategorie` FOREIGN KEY (`Einsatzkategorie`) REFERENCES `Einsatzkategorien` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `Einsätze`
--
ALTER TABLE `Einsätze`
ADD CONSTRAINT `fk_einsatzberichte_einsatzart` FOREIGN KEY (`Einsatzart`) REFERENCES `Einsatzarten` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `link_Abteilungen_Personal`
--
ALTER TABLE `link_Abteilungen_Personal`
ADD CONSTRAINT `fk_link_Abteilungen_Personal_1` FOREIGN KEY (`Abteilungen`) REFERENCES `Abteilungen` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `fk_link_Abteilungen_Personal_2` FOREIGN KEY (`Personal`) REFERENCES `Personal` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `link_Ausbildungen_Personal`
--
ALTER TABLE `link_Ausbildungen_Personal`
ADD CONSTRAINT `fk_link_personal_ausbildungen_1` FOREIGN KEY (`Personal`) REFERENCES `Personal` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `fk_link_personal_ausbildungen_2` FOREIGN KEY (`Ausbildungen`) REFERENCES `Ausbildungen` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `link_Berechtigungen_Personalgruppen`
--
ALTER TABLE `link_Berechtigungen_Personalgruppen`
ADD CONSTRAINT `fk_link_Berechtigungen_Personalgruppen_1` FOREIGN KEY (`Berechtigungen`) REFERENCES `Berechtigungen` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `fk_link_Berechtigungen_Personalgruppen_2` FOREIGN KEY (`Personalgruppen`) REFERENCES `Personalgruppen` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `link_Personal_Personalgruppen`
--
ALTER TABLE `link_Personal_Personalgruppen`
ADD CONSTRAINT `fk_link_Personal_Personalgruppen_1` FOREIGN KEY (`Personal`) REFERENCES `Personal` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `fk_link_Personal_Personalgruppen_2` FOREIGN KEY (`Personalgruppen`) REFERENCES `Personalgruppen` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `Personal`
--
ALTER TABLE `Personal`
ADD CONSTRAINT `fk_personal_1` FOREIGN KEY (`Kommandofkt`) REFERENCES `Kommandofunktionen` (`ID`) ON UPDATE CASCADE,
ADD CONSTRAINT `fk_personal_2` FOREIGN KEY (`Pool`) REFERENCES `Pools` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `Personalgruppen`
--
ALTER TABLE `Personalgruppen`
ADD CONSTRAINT `fk_Personalgruppen_1` FOREIGN KEY (`Abteilung`) REFERENCES `Abteilungen` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `print_elements`
--
ALTER TABLE `print_elements`
ADD CONSTRAINT `fk_print_elements_1` FOREIGN KEY (`key_layout_ID`) REFERENCES `print_layouts` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `sys_failedlogins`
--
ALTER TABLE `sys_failedlogins`
ADD CONSTRAINT `fk_sys_failedlogins_1` FOREIGN KEY (`ID`) REFERENCES `sys_user` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `sys_iservhashes`
--
ALTER TABLE `sys_iservhashes`
ADD CONSTRAINT `fk_sys_iservhashes_1` FOREIGN KEY (`ID`) REFERENCES `Personal` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `sys_pass`
--
ALTER TABLE `sys_pass`
ADD CONSTRAINT `fk_sys_pass_1` FOREIGN KEY (`ID`) REFERENCES `sys_user` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `sys_uaccess`
--
ALTER TABLE `sys_uaccess`
ADD CONSTRAINT `fk_sys_access_1` FOREIGN KEY (`key_sys_user_ID`) REFERENCES `sys_user` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `sys_user`
--
ALTER TABLE `sys_user`
ADD CONSTRAINT `fk_sys_users_1` FOREIGN KEY (`key_personal_ID`) REFERENCES `Personal` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE;
--
-- Constraints for table `Terminarten`
--
ALTER TABLE `Terminarten`
ADD CONSTRAINT `Terminarten_FK` FOREIGN KEY (`Verwaltungsrecht`) REFERENCES `Berechtigungen` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `Termine`
--
ALTER TABLE `Termine`
ADD CONSTRAINT `Termine_FK` FOREIGN KEY (`Platzvergabe`) REFERENCES `Terminplatzvergabe` (`ID`) ON UPDATE CASCADE,
ADD CONSTRAINT `fk_Termine_Dienstplan` FOREIGN KEY (`Dienstplan`) REFERENCES `Dienstpläne` (`ID`) ON UPDATE CASCADE,
ADD CONSTRAINT `fk_Termine_Personalgruppe` FOREIGN KEY (`Personalgruppe`) REFERENCES `Personalgruppen` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE,
ADD CONSTRAINT `fk_Termine_Terminart` FOREIGN KEY (`Terminart`) REFERENCES `Terminarten` (`ID`) ON UPDATE CASCADE;
--
-- Constraints for table `Terminteilnahmen`
--
ALTER TABLE `Terminteilnahmen`
ADD CONSTRAINT `fk_terminteilnahmen_1` FOREIGN KEY (`Personal`) REFERENCES `Personal` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `fk_terminteilnahmen_2` FOREIGN KEY (`Termin`) REFERENCES `Termine` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@ -0,0 +1,6 @@
<?php
namespace Firebase\JWT;
class BeforeValidException extends \UnexpectedValueException
{
}

6
ext/11_ExpiredException.php Executable file
View File

@ -0,0 +1,6 @@
<?php
namespace Firebase\JWT;
class ExpiredException extends \UnexpectedValueException
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace Firebase\JWT;
class SignatureInvalidException extends \UnexpectedValueException
{
}

512
ext/13_JWT.php Executable file
View File

@ -0,0 +1,512 @@
<?php
namespace Firebase\JWT;
use \DomainException;
use \InvalidArgumentException;
use \UnexpectedValueException;
use \DateTime;
/**
* JSON Web Token implementation, based on this spec:
* https://tools.ietf.org/html/rfc7519
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Neuman Vong <neuman@twilio.com>
* @author Anant Narayanan <anant@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWT
{
const ASN1_INTEGER = 0x02;
const ASN1_SEQUENCE = 0x10;
const ASN1_BIT_STRING = 0x03;
/**
* When checking nbf, iat or expiration times,
* we want to provide some extra leeway time to
* account for clock skew.
*/
public static $leeway = 0;
/**
* Allow the current timestamp to be specified.
* Useful for fixing a value within unit testing.
*
* Will default to PHP time() value if null.
*/
public static $timestamp = null;
public static $supported_algs = array(
'ES256' => array('openssl', 'SHA256'),
'HS256' => array('hash_hmac', 'SHA256'),
'HS384' => array('hash_hmac', 'SHA384'),
'HS512' => array('hash_hmac', 'SHA512'),
'RS256' => array('openssl', 'SHA256'),
'RS384' => array('openssl', 'SHA384'),
'RS512' => array('openssl', 'SHA512'),
);
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param string|array|resource $key The key, or map of keys.
* If the algorithm used is asymmetric, this is the public key
* @param array $allowed_algs List of supported verification algorithms
* Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
*
* @return object The JWT's payload as a PHP object
*
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
*
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
public static function decode($jwt, $key, array $allowed_algs = array())
{
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
if (empty($key)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = \explode('.', $jwt);
if (\count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid header encoding');
}
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
throw new UnexpectedValueException('Invalid signature encoding');
}
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
if (!\in_array($header->alg, $allowed_algs)) {
throw new UnexpectedValueException('Algorithm not allowed');
}
if ($header->alg === 'ES256') {
// OpenSSL expects an ASN.1 DER sequence for ES256 signatures
$sig = self::signatureToDER($sig);
}
if (\is_array($key) || $key instanceof \ArrayAccess) {
if (isset($header->kid)) {
if (!isset($key[$header->kid])) {
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
}
$key = $key[$header->kid];
} else {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
}
// Check the signature
if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
// Check the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
);
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
return $payload;
}
/**
* Converts and signs a PHP object or array into a JWT string.
*
* @param object|array $payload PHP object or array
* @param string $key The secret key.
* If the algorithm used is asymmetric, this is the private key
* @param string $alg The signing algorithm.
* Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
* @param mixed $keyId
* @param array $head An array with header elements to attach
*
* @return string A signed JWT
*
* @uses jsonEncode
* @uses urlsafeB64Encode
*/
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
{
$header = array('typ' => 'JWT', 'alg' => $alg);
if ($keyId !== null) {
$header['kid'] = $keyId;
}
if (isset($head) && \is_array($head)) {
$header = \array_merge($head, $header);
}
$segments = array();
$segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
$segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
$signing_input = \implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
return \implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource $key The secret key
* @param string $alg The signing algorithm.
* Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
*
* @return string An encrypted message
*
* @throws DomainException Unsupported algorithm was specified
*/
public static function sign($msg, $key, $alg = 'HS256')
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'hash_hmac':
return \hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
$signature = '';
$success = \openssl_sign($msg, $signature, $key, $algorithm);
if (!$success) {
throw new DomainException("OpenSSL unable to sign data");
} else {
if ($alg === 'ES256') {
$signature = self::signatureFromDER($signature, 256);
}
return $signature;
}
}
}
/**
* Verify a signature with the message, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
* @param string $alg The algorithm
*
* @return bool
*
* @throws DomainException Invalid Algorithm or OpenSSL failure
*/
private static function verify($msg, $signature, $key, $alg)
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'openssl':
$success = \openssl_verify($msg, $signature, $key, $algorithm);
if ($success === 1) {
return true;
} elseif ($success === 0) {
return false;
}
// returns 1 on success, 0 on failure, -1 on error.
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
case 'hash_hmac':
default:
$hash = \hash_hmac($algorithm, $msg, $key, true);
if (\function_exists('hash_equals')) {
return \hash_equals($signature, $hash);
}
$len = \min(static::safeStrlen($signature), static::safeStrlen($hash));
$status = 0;
for ($i = 0; $i < $len; $i++) {
$status |= (\ord($signature[$i]) ^ \ord($hash[$i]));
}
$status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
return ($status === 0);
}
}
/**
* Decode a JSON string into a PHP object.
*
* @param string $input JSON string
*
* @return object Object representation of JSON string
*
* @throws DomainException Provided string was invalid JSON
*/
public static function jsonDecode($input)
{
if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
* to specify that large ints (like Steam Transaction IDs) should be treated as
* strings, rather than the PHP default behaviour of converting them to floats.
*/
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
} else {
/** Not all servers will support that, however, so for older versions we must
* manually detect large ints in the JSON string and quote them (thus converting
*them to strings) before decoding, hence the preg_replace() call.
*/
$max_int_length = \strlen((string) PHP_INT_MAX) - 1;
$json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
$obj = \json_decode($json_without_bigints);
}
if ($errno = \json_last_error()) {
static::handleJsonError($errno);
} elseif ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input');
}
return $obj;
}
/**
* Encode a PHP object into a JSON string.
*
* @param object|array $input A PHP object or array
*
* @return string JSON representation of the PHP object or array
*
* @throws DomainException Provided object could not be encoded to valid JSON
*/
public static function jsonEncode($input)
{
$json = \json_encode($input);
if ($errno = \json_last_error()) {
static::handleJsonError($errno);
} elseif ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input');
}
return $json;
}
/**
* Decode a string with URL-safe Base64.
*
* @param string $input A Base64 encoded string
*
* @return string A decoded string
*/
public static function urlsafeB64Decode($input)
{
$remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= \str_repeat('=', $padlen);
}
return \base64_decode(\strtr($input, '-_', '+/'));
}
/**
* Encode a string with URL-safe Base64.
*
* @param string $input The string you want encoded
*
* @return string The base64 encode of what you passed in
*/
public static function urlsafeB64Encode($input)
{
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
}
/**
* Helper method to create a JSON error.
*
* @param int $errno An error number from json_last_error()
*
* @return void
*/
private static function handleJsonError($errno)
{
$messages = array(
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
);
throw new DomainException(
isset($messages[$errno])
? $messages[$errno]
: 'Unknown JSON error: ' . $errno
);
}
/**
* Get the number of bytes in cryptographic strings.
*
* @param string $str
*
* @return int
*/
private static function safeStrlen($str)
{
if (\function_exists('mb_strlen')) {
return \mb_strlen($str, '8bit');
}
return \strlen($str);
}
/**
* Convert an ECDSA signature to an ASN.1 DER sequence
*
* @param string $sig The ECDSA signature to convert
* @return string The encoded DER object
*/
private static function signatureToDER($sig)
{
// Separate the signature into r-value and s-value
list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2));
// Trim leading zeros
$r = \ltrim($r, "\x00");
$s = \ltrim($s, "\x00");
// Convert r-value and s-value from unsigned big-endian integers to
// signed two's complement
if (\ord($r[0]) > 0x7f) {
$r = "\x00" . $r;
}
if (\ord($s[0]) > 0x7f) {
$s = "\x00" . $s;
}
return self::encodeDER(
self::ASN1_SEQUENCE,
self::encodeDER(self::ASN1_INTEGER, $r) .
self::encodeDER(self::ASN1_INTEGER, $s)
);
}
/**
* Encodes a value into a DER object.
*
* @param int $type DER tag
* @param string $value the value to encode
* @return string the encoded object
*/
private static function encodeDER($type, $value)
{
$tag_header = 0;
if ($type === self::ASN1_SEQUENCE) {
$tag_header |= 0x20;
}
// Type
$der = \chr($tag_header | $type);
// Length
$der .= \chr(\strlen($value));
return $der . $value;
}
/**
* Encodes signature from a DER object.
*
* @param string $der binary signature in DER format
* @param int $keySize the number of bits in the key
* @return string the signature
*/
private static function signatureFromDER($der, $keySize)
{
// OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
list($offset, $_) = self::readDER($der);
list($offset, $r) = self::readDER($der, $offset);
list($offset, $s) = self::readDER($der, $offset);
// Convert r-value and s-value from signed two's compliment to unsigned
// big-endian integers
$r = \ltrim($r, "\x00");
$s = \ltrim($s, "\x00");
// Pad out r and s so that they are $keySize bits long
$r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
$s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
return $r . $s;
}
/**
* Reads binary DER-encoded data and decodes into a single object
*
* @param string $der the binary data in DER format
* @param int $offset the offset of the data stream containing the object
* to decode
* @return array [$offset, $data] the new offset and the decoded object
*/
private static function readDER($der, $offset = 0)
{
$pos = $offset;
$size = \strlen($der);
$constructed = (\ord($der[$pos]) >> 5) & 0x01;
$type = \ord($der[$pos++]) & 0x1f;
// Length
$len = \ord($der[$pos++]);
if ($len & 0x80) {
$n = $len & 0x1f;
$len = 0;
while ($n-- && $pos < $size) {
$len = ($len << 8) | \ord($der[$pos++]);
}
}
// Value
if ($type == self::ASN1_BIT_STRING) {
$pos++; // Skip the first contents octet (padding indicator)
$data = \substr($der, $pos, $len - 1);
$pos += $len - 1;
} elseif (!$constructed) {
$data = \substr($der, $pos, $len);
$pos += $len;
} else {
$data = null;
}
return array($pos, $data);
}
}

5
ext/20_PHPMailer.php Normal file
View File

@ -0,0 +1,5 @@
<?php // ext/XX_PHPMailer.php
require_once("ext/PHPMailer/src/Exception.php");
require_once("ext/PHPMailer/src/PHPMailer.php");
require_once("ext/PHPMailer/src/SMTP.php");

1
ext/PHPMailer Submodule

@ -0,0 +1 @@
Subproject commit 48392076504a2ee4b091d46fec2c3089b71f804a

1
ext/php-jwt Submodule

@ -0,0 +1 @@
Subproject commit 474047dbd8442a730ab03810f2835839b107cd29

1
ext/php-qrcode Submodule

@ -0,0 +1 @@
Subproject commit d650fe73067c2559519f09949e9b71b466ab2bee

@ -0,0 +1 @@
Subproject commit 98ccc1b31b31a53bcb563465c4961879b2b93096

48
index.php Normal file → Executable file
View File

@ -1,35 +1,27 @@
<?php #/index.php
<?php #/index.php
error_reporting(E_ALL);
ini_set("display_errors", 1);
require_once "bin/init.php";
//Parse page(s)
//array $page = (int main, string sub, array css = [strings], array js = [strings])
//array $output = (string stat, string main, string navi)
if ($userID = lgnCheckLogin($mysqli)) {
$pages = pgsLoadPages($mysqli, $userID);
if (sizeof($pages)==0) {
$output["main"] = "No pages to show";
if ("JSON"==$man->RequestedContentType()) { // User wants JSON. So tell him what he wants. What he really really wants.
http_response_code($man->ResponseCode());
header('Content-type: application/json');
echo trim(json_encode($man->Output()));
} elseif ("PRINT"==$man->RequestedContentType()) { // User wants a PRINTOUT. Oh my.
header('Content-type: text/html');
if (200 <= $man->ResponseCode() && 300 > $man->ResponseCode()) {
$man->printer->CreateHtml($man->input["print"]);
http_response_code($man->printer->ResponseCode());
echo trim($man->printer->GetHtml());
} else {
$page["main"] = (isset($input["main"]) && isset($pages[$input["main"]])) ? $input["main"] : key($pages);
$page["sub"] = isset($input["sub"]) ? $input["sub"] : "main";
include "bin/navi.php";
$files["pgs"] = pgsIncPage($pages[$page["main"]]["path"]);
if (file_exists("pgs/".$pages[$page["main"]]["path"]."/lib.php")) {
include "pgs/".$pages[$page["main"]]["path"]."/lib.php";
}
if ($files["pgs"]) {
include $files["pgs"];
}
$output["showlogin"] = "off";
http_response_code($man->ResponseCode());
}
} else { // Say hello to new connection. And then crush him with our very bad HTML-JS-Client. It will automatically try to authenticate if jwt is found in cookies
http_response_code(200);
header('Content-type: text/html');
include "res/main.html";
}
//Include error-handling, print page and quit
include "bin/errors.php"; //Parse error reporting
include "bin/print.php"; //Print page
include "bin/exit.php"; //Conclusions
?>
include "bin/exit.php"; // Closing connections

15
index_new.php Normal file
View File

@ -0,0 +1,15 @@
<?php #/index.php
error_reporting(E_ALL);
ini_set("display_errors", 1);
require_once "bin/init_new.php";
Request::Read();
if (Request::IsServeClient()) {
http_response_code(200);
header('Content-type: text/html');
include "client/index.html";
} else {
Manager::Answer();
}

55
lib/00_main.php Normal file → Executable file
View File

@ -7,71 +7,26 @@
* Version: alpha (incomplete, tested, commented) *
********************************************************************************/
#### INCOMPLETE! TBD: ##############
# Mehr Datums- und Zeitformate #
####################################
function SS($param)
{ //Prevents injections from strings
global $mysqli;
return $mysqli->escape_string($param);
}
function SI($param)
{ //Prevents injections from integers
return intval($param);
}
function myDate($time = 0)
{ //Formats timestamp to standard date format
function myDate($time = 0) { //Formats timestamp to standard date format
if ($time==0) $time = time();
return date(FORMATDATE, $time);
}
function myTime($time = 0)
{ //Formats timestamp to standard time format
function myTime($time = 0) { //Formats timestamp to standard time format
if ($time==0) $time = time();
return date(FORMATTIME, $time);
}
function myDateTime($time = 0)
{ //Formats timestamp to standard date and time format
function myDateTime($time = 0) { //Formats timestamp to standard date and time format
return myDate($time)." ".myTime($time);
}
function fmtTime($format, $time = 0)
{ //Formats timestamp to a preset value
function fmtTime($format, $time = 0) { //Formats timestamp to a preset value
global $times;
return (isset($times[$format]) ? date($times[$format], $time) : myDateTime);
}
function addError($ident, $add)
{ //Lists an error
function addError($ident, $add) { //Lists an error
global $err;
if (isset($err[$ident])) $err["out"][] = $err[$ident].$add;
else $err["out"][] = $err["unknown"].$ident.$add;
}
function addLibrary($lib, $notify=true)
{ //Includes a php-library
global $err;
if (!(substr($lib,0,4)=="lib/")) $lib = "lib/".$lib;
if (!(substr($lib,-4,4)==".php")) $lib.= ".php";
if (file_exists($lib)) include_once($lib);
elseif ($notify) addError("noLib", $lib);
}
function addStyle($css, $notify=true)
{ //adds a stylesheet to $output-array
global $output, $err;
if (file_exists($css) && ! in_array($css,$output["css"])) $output["css"][] = $css;
elseif ($notify) addError("noCss", $css);
}
function addJscript($js, $notify=true)
{ //adds a javascript to $output-array
global $output, $err;
if (file_exists($js) && ! in_array($js,$output["js"])) $output["js"][] = $js;
elseif ($notify) addError("noJs", $js);
}
?>

View File

@ -1,254 +1,323 @@
<?php #lib/10_login.php
/********************************************************************************
* Content: Login related functions *
* Content: Login class *
* Author: Nils Otterpohl *
* Last modification: 24.06.2019 *
* Version: alpha (incomplete, tested, commented) *
* Last modification: 13.12.2020 *
* Version: alpha (object, incomplete, uncommented, untested) *
********************************************************************************/
function lgnGenHash($password, $salt, $iterations, $length = 32, $algo = "sha256") {
return hash_pbkdf2($algo, $password, $salt, $iterations, $length*2);
}
function lgnGenSalt($length = 16) {
return bin2hex(random_bytes($length));
}
function lgnTransformPassword($password = "") {
$salt = lgnGenSalt();
if ($password=="")
$password = lgnGenSalt(8);
return array(
"password" => $password,
"iterations" => DESIRED_ITERATIONS,
"salt" => $salt,
"hash" => lgnGenHash($password, $salt, DESIRED_ITERATIONS)
);
}
function lgnSecSessionStart() {
// Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen
class Login
{
public static $instance = null;
public static $id = ""; // ID of authenticated user
public static $loggedIn = false;
private $key; // JWT Server private key
private $man; // Owning API manager
// Zwingt die Sessions nur Cookies zu benutzen.
if (ini_set("session.use_only_cookies", 1) === FALSE) {
exit();
private $jwt = null; // JSON Web Token (JWT), which was submitted or generated
private $jwt_expires = 0; // Expiration date of JWT
private $jwt_renewed = false;//
private $status = [ // Status array to be issued to client
"loggedIn" => false, // Status of login
"newjwt" => null, // If not null, client needs to use this new jwt from now on
"secToken" => null // Client needs to use this secToken to accompany the next input
];
private $login = ""; // Login name of authenticated user
private $vornamen = ""; // Prenames of authenticated user
private $nachnamen = ""; // Surnames of authenticated user
private $permissions = null;// Buffer to hold Personalgruppen, Abteilungen and Rechte
private $secTokenUse = null; // (One time) Security token to be issued to client to be used with incoming data
private $secTokenVerify = null; // Last Security token issued. Incoming data will be verified against this
/***** Static functions *****/
// Generates a salted password hash of desired length and with the desired algorithm
public static function GenHash($password, $salt, $iterations, $length = 32, $algo = "sha256") {
return hash_pbkdf2($algo, $password, $salt, $iterations, $length*2);
}
// Holt Cookie-Parameter.
$cookieParams = session_get_cookie_params();
// Erstes true = Nur https Zugriff, Zweites true = Blockt JavaScript Zugriff
session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], true, true);
$sessionName = "SessionOfHigherSecureness";
session_name($sessionName);
session_start(); // Startet die PHP-Sitzung
session_regenerate_id(); // Erneuert die Session, löscht die alte.
}
function lgnRegenerateToken() {
if (isset($_SESSION["secTokenUse"])) {
$_SESSION["secTokenVerify"] = $_SESSION["secTokenUse"];
} else {
$_SESSION["secTokenVerify"] = "";
// Generates a random salt (or also password) of desired length
public static function GenSalt($length = 16) {
return bin2hex(random_bytes($length));
}
$_SESSION["secTokenUse"] = lgnGenSalt(16);
}
function lgnLogin($mysqli, $login, $password) {
// Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen
// and https://github.com/nextcloud/user_external/blob/master/lib/webdavauth.php
/***** Getter functions and Constructor *****/
// Das Benutzen vorbereiteter Statements verhindert SQL-Injektion.
$login = strtolower($login);
$login = str_replace("@feuerwehr-bs.net", "", $login);
if ($stmt = $mysqli->prepare("SELECT p.ID, p.login FROM Personal p WHERE p.login = ? LIMIT 1")) {
$stmt->bind_param("s", $login); // Bind "$login" to parameter.
$stmt->execute(); // Führe die vorbereitete Anfrage aus.
$stmt->store_result();
public function JWT() {return $this->jwt;}
public function SecToken() {return $this->secTokenUse;}
public function Status() {return $this->status;}
public function LoggedIn() {return self::$loggedIn;}
public function ID() {return self::$id;}
public function Login() {return $this->login;}
public function Vornamen() {return $this->vornamen;}
public function Nachnamen() {return $this->nachnamen;}
// hole Variablen von result.
$stmt->bind_result($userID, $userLogin);
$stmt->fetch();
if ($stmt->num_rows == 1) {
$url= 'https://'.urlencode($userLogin).':'.urlencode($password).'@feuerwehr-bs.net/webdav';
$headers = get_headers($url);
if($headers === false) {
addError("loginFailed", 'ERROR: Not possible to connect to WebDAV Url: "https://feuerwehr-bs.net/webdav"');
return false;
}
$returnCode= substr($headers[0], 9, 3);
if(substr($returnCode, 0, 1) === '2') {
// Passwort ist korrekt!
// Hole den user-agent string des Benutzers.
$userBrowser = $_SERVER["HTTP_USER_AGENT"];
// XSS-Schutz, denn eventuell wird der Wert gedruckt
$userID = preg_replace("/[^0-9]+/", "", $userID);
$_SESSION["userID"] = $userID;
// XSS-Schutz, denn eventuell wird der Wert gedruckt
$userLogin = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $userLogin);
$_SESSION["userLogin"] = $userLogin;
// Generiere einen Hash aus dem Passwort mit zufälligem Salt und speichere ihn in der Datenbank
$hash = lgnGenHash($password, lgnGenSalt(), DESIRED_ITERATIONS);
$_SESSION["loginString"] = lgnGenHash($hash, $userBrowser, 10, 64, "sha512");
$mysqli->query("REPLACE INTO sys_iservhashes(ID, Hash) VALUES ('".$userID."', '".$hash."')");
// Login erfolgreich.
return true;
} else {
// Passwort ist nicht korrekt
addError("loginFailed", " Passwort nicht korrekt. Fehlercode (bitte an Nils senden): ".$returnCode);
}
} else {
addError("loginFailed", "Benutzername inkorrekt ".$login);
}
}
if ($mysqli->error!="") {
addError("mysql", $mysqli->error);
}
return false;
}
function lgnLogout() {
// Setze alle Session-Werte zurück
$_SESSION = array();
// hole Session-Parameter
$params = session_get_cookie_params();
// Lösche das aktuelle Cookie.
setcookie(
session_name(),
'',
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
// Vernichte die Session
session_destroy();
header('Location: index.php');
}
function lgnChangePass($mysqli, $userID, $passOld, $passNew, $passRepeat) {
$ret = false;
if ($passOld==$passNew) {
addError("passChangeFail", "Neues und altes Passwort sind nicht unterschiedlich?");
} else if (strlen($passNew)<12) {
addError("passChangeFail", "Passwort muss mindestens 12 Zeichen haben!");
} else if ($passNew!=$passRepeat) {
addError("passChangeFail", "Passwortwiederholung falsch!");
} else {
if ($stmt = $mysqli->prepare("SELECT iterations, salt, hash FROM users WHERE ID = ? LIMIT 1")) {
$stmt->bind_param("i", $_SESSION["userID"]);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($passIterations, $passSalt, $passHash);
$stmt->fetch();
if ($stmt->num_rows == 1) {
if (lgnGenHash($passOld, $passSalt, $passIterations)!=$passHash) {
addError("passChangeFail", "Altes Passwort inkorrekt!");
} else {
$newSalt = lgnGenSalt();
$newHash = lgnGenHash($passNew, $newSalt, DESIRED_ITERATIONS);
$mysqli->query("UPDATE users SET iterations='".DESIRED_ITERATIONS."', salt='".$newSalt."', hash='".$newHash."' "
."WHERE ID='".$_SESSION["userID"]."'");
$userBrowser = $_SERVER["HTTP_USER_AGENT"];
$_SESSION["loginString"] = lgnGenHash($newHash, $userBrowser, 10, 64, "sha512");
$ret = true;
}
}
} else {
addError("passChangeFailed", $mysqli->error);
}
}
return $ret;
}
function lgnCheckBrute($mysqli, $userID) {
// Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen
// Hole den aktuellen Zeitstempel
// Alle Login-Versuche der letzten zwei Stunden werden gezählt.
if ($stmt = $mysqli->prepare("SELECT time FROM failedlogins WHERE ID = ? AND time > DATE_SUB(NOW(), INTERVAL 2 HOUR)")) {
$stmt->bind_param("i", $userID);
// Führe die vorbereitet Abfrage aus.
$stmt->execute();
$stmt->store_result();
// Wenn es mehr als 5 fehlgeschlagene Versuche gab
if ($stmt->num_rows > 5) {
return true;
}
}
return false;
}
function lgnCheckLogin($mysqli) {
// Überprüfe, ob alle Session-Variablen gesetzt sind
if (isset($_SESSION["userID"], $_SESSION["userLogin"], $_SESSION["loginString"])) {
$userID = $_SESSION["userID"];
$loginString = $_SESSION["loginString"];
$userLogin = $_SESSION["userLogin"];
// Hole den user-agent string des Benutzers.
$userBrowser = $_SERVER["HTTP_USER_AGENT"];
public function __construct($database, $keyvaluestorage, $jwtKey, $manager, $jwt, $input, $issueNewSecToken = true, $allowJwtRenewal = false) {
self::$instance = $this;
$this->key = $jwtKey;
$this->man = $manager;
$this->jwt = $jwt;
// Die UserID wird hier nochmal abgerufen, damit dem Rückgabewert der Funktion vertraut werden kann
if ($stmt = $mysqli->prepare("SELECT ID, Hash FROM sys_iservhashes WHERE ID = ? LIMIT 1")) {
$stmt->bind_param("i", $userID);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// Wenn es den Benutzer gibt, hole die Variablen von result.
$stmt->bind_result($uID, $uHash);
$stmt->fetch();
if (lgnGenHash($uHash, $userBrowser, 10, 64, "sha512") == $loginString) {
// Eingeloggt!!!!
return $uID;
}
// Try to login via JWT, if it was submitted
if ($this->jwt!==null) {
$this->authenticateJWT($allowJwtRenewal);
}
// Try to login via password, if not yet logged in
if (!self::$loggedIn) {
if (isset($input["login"], $input["password"])) {
$this->authenticatePWD($input["login"], $input["password"]);
}
}
}
// Nicht eingeloggt
if ($mysqli->error!="") {
addError("mysql", $mysqli->error);
}
return false;
}
function lgnCheckRight($mysqli, $right, $userID = null) {
if ($right==null) {
return true;
}
if ($userID==null) {
$userID = lgnCheckLogin($mysqli);
}
$query = "SELECT b.name FROM Personal p "
."LEFT JOIN link_Personal_Personalgruppen lpp ON p.ID=lpp.Personal "
."LEFT JOIN link_Berechtigungen_Personalgruppen lbp ON lbp.Personalgruppen = lpp.Personalgruppen "
."LEFT JOIN Berechtigungen b ON b.ID = lbp.Berechtigungen "
."WHERE p.ID = ".$userID." and b.Name ";
if (is_array($right)) {
$query.= "IN ('".implode("', '", $right)."')";
} else {
$query.= "= '".$right."'";
}
if ($res = $mysqli->query($query)) {
if ($res->num_rows>0) {
return true;
if (self::$loggedIn) {
// Erzeuge neues Security-Token
$this->secTokenUse = $this->secTokenVerify;
if ($issueNewSecToken || $this->secTokenUse==null) {
$this->secTokenUse = Login::GenSalt(16);
}
Manager::$kv->set("secToken:".self::$id, $this->secTokenUse);
Manager::$kv->expire("secToken:".self::$id, 60*60); // secToken ist für 60 min * 60 s/min gültig
$this->status["secToken"] = $this->secTokenUse;
}
}
return false;
}
?>
/***** Public functions *****/
// Blacklists the issued JWT and sets logged in status to false
public function Logout() {
Manager::$kv->set("JWT:Blacklist:".self::$id, $this->jwt);
Manager::$kv->expireAt("JWT:Blacklist:".self::$id, $this->jwt_expires);
$this->jwt = null;
$this->status["loggedIn"] = false; // DEPRECATED
self::$loggedIn = false;
}
static function AppendJwtAndSecToken(&$json) {
if (self::$instance->secTokenVerify!=$this->secTokenUse) {
$json["secToken"] = $this->secTokenUse;
}
if ($this->jwt_renewed) {
$json["jwt"] = $this->jwt;
}
}
public function VerifySecToken($secToken) {
return ($this->secTokenVerify==null || $secToken==$this->secTokenVerify);
}
// Returns true if logged in user has the (or, if array, one of the) named right(s)
// Fetched rights will be buffered, so recurring queries are no performance problem
static function HasRight($rightNames) {
if (!self::$loggedIn) {
return false;
}
if ($rightNames==null) {
return true;
}
if (!isset(self::$instance->permissions["rights"])) {
self::$instance->permissions["rights"] = []; // TODO: private member not accessible DÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖT
$qry = "SELECT r.Name FROM Personal_Verwalter pv "
."LEFT JOIN Rechte_Verwalter rv ON rv.Verwalter = pv.Verwalter "
."LEFT JOIN Rechte r ON r.ID = rv.Rechte "
."WHERE pv.Personal = ".self::$instance->ID();
$res = Manager::$db->query($qry);
while ($row = $res->fetch_assoc()) {
self::$instance->permissions["rights"][] = $row["Name"];
}
}
if (is_array($rightNames)) {
foreach ($rightNames as $r) {
if (in_array($r, self::$instance->permissions["rights"])) {
return true;
}
}
return false;
} else {
return in_array($rightNames, self::$instance->permissions["rights"]);
}
}
// Returns true if logged in user is in the (or, if array, one of the) named department(s) ("Abteilungen")
// Fetched departments will be buffered, so recurring queries are no performance problem
public function InAbteilung($abteilungIDs) {
if (!self::$loggedIn) {
return false;
}
if ($abteilungIDs==null) {
return true;
}
if (!isset($this->permissions["abteilungenIDs"])) {
$this->permissions["abteilungenIDs"] = [];
$qry = "SELECT Abteilungen FROM Personal_Abteilungen WHERE Personal = ".self::$id;
$res = Manager::$db->query($qry);
while ($row = $res->fetch_assoc()) {
$this->permissions["abteilungenIDs"][] = $row["Abteilungen"];
}
}
if (is_array($abteilungIDs)) {
foreach ($abteilungIDs as $aID) {
if (in_array($aID, $this->permissions["abteilungenIDs"])) {
return true;
}
}
return false;
} else {
return in_array($abteilungIDs, $this->permissions["abteilungenIDs"]);
}
}
// Returns true if logged in user is in the (or, if array, one of the) named group(s) ("Gruppen")
// Fetched groups will be buffered, so recurring queries are no performance problem
public function InGruppe($gruppenIDs) {
if (!self::$loggedIn) {
return false;
}
if ($gruppenIDs==null) {
return true;
}
if (!isset($this->permissions["gruppenIDs"])) {
$this->permissions["gruppenIDs"] = [];
$qry = "SELECT Gruppen FROM Personal_Gruppen WHERE Personal = ".self::$id;
$res = Manager::$db->query($qry);
while ($row = $res->fetch_assoc()) {
$this->permissions["gruppenIDs"][] = $row["Gruppen"];
}
}
if (is_array($gruppenIDs)) {
foreach ($gruppenIDs as $gID) {
if (in_array($gID, $this->permissions["gruppenIDs"])) {
return true;
}
}
return false;
} else {
return in_array($gruppenIDs, $this->permissions["gruppenIDs"]);
}
}
/***** Private functions *****/
// Tries to decode the submitted JWT, checks blacklisting of JWT and sets logged in status accordingly
private function authenticateJWT($allowJwtRenewal) {
try {
$decoded = \Firebase\JWT\JWT::decode($this->jwt, $this->key, ['HS256']);
// What has been an array before encoding is now an object
// Read member variables from decoded data
self::$id = $decoded->data->userID==58 && isset($_GET["emulate"]) ? $_GET["emulate"] : $decoded->data->userID;
$this->login = $decoded->data->userLogin;
$this->vornamen = $decoded->data->userVornamen;
$this->nachnamen = $decoded->data->userNachnamen;
$this->jwt_expires = $decoded->exp;
} catch (Exception $e) {
// JWT will throw exceptions if it cannot decode the JWT. In this case - don't login
return;
}
if (Manager::$kv->get("JWT:Blacklist:".self::$id)!=$this->jwt) {
// Not blacklisted, can log in
$this->status["loggedIn"] = true; // DEPRECATED
self::$loggedIn = true;
$this->secTokenVerify = Manager::$kv->get("secToken:".self::$id);
$now = time();
if ($this->jwt_expires-$now <= JWT_VALID_TIME*0.5 && $allowJwtRenewal) {
$this->man->AddMessage("Extending JWT");
$this->encodeJWT();
}
} else {
$this->jwt = null;
self::$id = -99;
}
}
private function encodeJWT() {
$time = time();
$this->jwt_expires = $time + JWT_VALID_TIME;
$issuer = "http://fw-innenstadt.de/";
$token = [
"iat" => $time,
"exp" => $this->jwt_expires,
"iss" => "fw-innenstadt.de",
"data" => [
"userID" => self::$id,
"userLogin" => $this->login,
"userVornamen" => $this->vornamen,
"userNachnamen" => $this->nachnamen
]
];
$this->jwt = \Firebase\JWT\JWT::encode($token, $this->key);
$this->jwt_renewed = true;
$this->status["newjwt"] = $this->jwt;
}
private function authenticatePWD($login, $password) {
// Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen
// and https://github.com/nextcloud/user_external/blob/master/lib/webdavauth.php
// and https://codeofaninja.com/2018/09/rest-api-authentication-example-php-jwt-tutorial.html
// Login zum gewünschten Format zurechtbiegen
$login = strtolower($login);
$login = str_replace("@feuerwehr-bs.net", "", $login);
// Das Benutzen vorbereiteter Statements verhindert SQL-Injektion.
if ($stmt = Manager::$db->prepare("SELECT p.ID, p.login, p.Vornamen, p.Nachnamen FROM Personal p WHERE p.login = ? LIMIT 1")) {
$stmt->bind_param("s", $login); // Bind "$login" to parameter.
$stmt->execute(); // Führe die vorbereitete Anfrage aus.
$stmt->store_result();
// hole Variablen von result.
$stmt->bind_result(self::$id, $this->login, $this->vornamen, $this->nachnamen);
$stmt->fetch();
if ($stmt->num_rows == 1) {
$url= 'https://'.urlencode($this->login).':'.urlencode($password).'@feuerwehr-bs.net/webdav';
$headers = get_headers($url);
if($headers === false) {
//addError("loginFailed", 'ERROR: Not possible to connect to WebDAV Url: "https://feuerwehr-bs.net/webdav"');
// THROW
return;
}
$returnCode= substr($headers[0], 9, 3);
if (substr($returnCode, 0, 1) === '2') {
// Passwort ist korrekt!
// XSS-Schutz, denn eventuell wird der Wert gedruckt
self::$id = preg_replace("/[^0-9]+/", "", self::$id);
$this->login = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $this->login);
// Generiere einen Hash aus dem Passwort mit zufälligem Salt und speichere ihn in der Datenbank
// Question: Why?
$hash = Login::GenHash($password, Login::GenSalt(), DESIRED_ITERATIONS);
Manager::$db->query("REPLACE INTO sys_iservhashes(ID, Hash) VALUES ('".self::$id."', '".$hash."')");
// Login erfolgreich.
$this->encodeJWT();
$this->status["loggedIn"] = true; // DEPRECATED
self::$loggedIn = true;
$this->man->AddMessage("Login erfolgreich!");
return;
} else if ($returnCode === "401") {
// Passwort ist nicht korrekt
$this->man->AddMessage("Das Passwort ist vermutlich nicht korrekt. Bitte erneut probieren oder an Nils wenden.");
} else if ($returnCode === "503") {
// Passwort ist nicht korrekt
$this->man->AddMessage("IServ verweigert im Moment die Anmeldung. Vermutlich gab es temporär zu viele falsche Passworteingaben "
."(auch von anderen Nutzern). Bitte erst in 10 Minuten neu probieren!");
} else {
// Unbekannter Fehler
$this->man->AddMessage("Anmeldung konnte aus unbekannten Gründen nicht durchgeführt werden. Fehlercode (bitte an Nils senden): ".$returnCode);
}
} else {
$this->man->AddMessage("Benutzername inkorrekt: ".$login);
}
}
if (Manager::$db->error!="") {
$this->man->AddMessage("Mysql error: ".Manager::$db->error);
}
return;
}
}

31
lib/20_template.php Normal file → Executable file
View File

@ -56,6 +56,35 @@ function tplReplSection($tpl, $section, $value)
return $ret;
}
function tplReplCondition($tpl, $section, $bool)
{ // selects one of two sections in a template
tplCheckMarker($section);
$mif = tplMakeSectionMarker($section, "IF");
$melse = tplMakeSectionMarker($section, "ELSE");
$mend = tplMakeSectionMarker($section, "END");
$start = strpos($tpl, $mif);
$else_start = strpos($tpl, $melse);
$else_end = $else_start + strlen($melse);
$end = strrpos($tpl, $mend)+strlen($mend);
if (($start===false) || ($else_start == false) || ($end===false) || ($end<=$start)) return $tpl;
$value = "";
if ($bool) {
// Show if-part
$value = substr($tpl, $start,$else_end-$start);
} else {
// Show else-part
$value = substr($tpl, $else_start,$end-$else_start);
}
$ret = ($start>0 ? substr($tpl, 0, $start) : "");
$ret.= $value;
if ($end<strlen($tpl)) $ret.= substr($tpl, $end);
return $ret;
}
function tplReplMarker($tpl, $marker, $value)
{ //replaces a marker in a template with a value
tplCheckMarker($marker);
@ -71,5 +100,3 @@ function tplReplMarkerArray($tpl, $array)
}
return $tpl;
}
?>

300
lib/30_pages.php Normal file → Executable file
View File

@ -7,61 +7,259 @@
* Version: stable (complete, tested, uncommented) *
********************************************************************************/
function pgsCreatePage($label, $link, $class)
{
#Creates array with markers required for navigation
return array(
"###NAVLABEL###" => $label,
"###NAVHREF###" => $link,
"###NAVCLASS###" => $class
);
/**************************************************/
/******************** BaseApp *********************/
/**************************************************/
abstract class BaseApp {
protected $db; // Database connection
protected $kv; // Redis connection
protected $man; // Manager link
protected $output = []; // Output
protected $meta = [];
protected $useRight = null;
protected $adminRight = null;
protected $table = "";
public function __construct($database, $keyvaluestore, $manager, $info) {
$this->db = $database;
$this->kv = $keyvaluestore;
$this->man = $manager;
$this->useRight = $info["useRight"];
$this->adminRight = $info["adminRight"];
$this->table = $info["table"];
}
protected function canEdit() {
return $this->man->user->HasRight($this->adminRight);
}
}
function pgsLoadPages($mysqli, $userID) {
/* userID unused for now, maybe will add page access control if neccessary */
$ret = array();
/**************************************************/
/******************** BaseLink ********************/
/**************************************************/
$dir = scandir("pgs");
abstract class BaseLink extends BaseApp
{
private $page;
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info);
$this->page = $page;
}
public function DoRequest($method, $ids) {
switch ($method) {
case "GET":
return $this->get($ids);
case "POST":
return $this->canEdit() ? $this->insert($ids) : 403;
case "PATCH":
return $this->canEdit() ? $this->update($ids) : 403;
case "DELETE":
return $this->canEdit() ? $this->remove($ids) : 403;
default:
return 501;
} }
protected function insert($ids) {
return 501;
}
protected function get($ids) {
return 501;
}
protected function update($ids) {
return 501;
}
protected function remove($ids) {
return 501;
}
}
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 (lgnCheckRight($mysqli, $json["useRight"], $userID)) {
$key = $json["moduleName"];
$ret[$key]["ID"] = $json["moduleName"];
$ret[$key]["name"] = $json["title"];
$ret[$key]["path"] = $val;
$ret[$key]["tpl"] = pgsCreatePage($val, "", "");
}
}
/**************************************************/
/******************** BasePage ********************/
/**************************************************/
abstract class BasePage extends BaseApp
{
protected $title; // Title of Hyperlink
protected $route; // Module name, i.e. the route to open this page (/index.php/page/filter/%ROUTE%/filter)
protected $path; // Internal path to files (/pgs/PATH/)
protected $links = []; // List of existing submodules
protected $output = []; // Output
protected $groupIndex = [];
protected $options = [];
protected $optionsIndex = [];
public function __construct($database, $keyvaluestore, $manager, $info) {
parent::__construct($database, $keyvaluestore, $manager, $info);
$this->title = $info["title"];
$this->route = $info["route"];
$this->path = $info["path"];
$this->links = $info["links"];
$this->options = $this->fillOptions($this->canEdit());
foreach ($this->options as $field => $rows) {
foreach ($rows as $index => $row) {
$this->optionsIndex[$field][$row["ID"]] = &$this->options[$field][$index];
}
}
}
return $ret;
public function GetLink($link) {
if (isset($this->links[$link])) {
return $this->links[$link];
} else {
return null;
} }
public function GetTemplate() {
if (file_exists($this->FullPath()."template.html")) {
return $this->FullPath()."template.html";
} else {
return "";
} }
public function FullPath() {
return "pgs/".$this->path."/";
}
public function DoRequest($method) {
$id = $this->man->Main();
switch ($method) {
case "HEAD":
return $this->head();
case "GET":
return $this->get($id);
case "OPTIONS":
return $this->info();
case "POST":
return $this->canEdit() ? $this->insert() : 403;
case "PATCH":
case "DELETE":
if ($id===null) {
$this->man->AddMessage("Keine ID spezifiziert!");
return 400;
}
if (!$this->canEdit()) {
return 403;
}
return $method=="PATCH" ? $this->update($id) : $this->remove($id);
}
return 501;
}
protected function insert() {
return 501;
}
protected function head() {
return 501;
}
protected function get($id) {
return 501;
}
protected function info() {
$this->man->output["options"] = $this->options;
return 200;
}
protected function update($id) {
return 501;
}
protected function remove($id) {
if ($stmt = $this->db->prepare("DELETE FROM ".$this->table." WHERE ID = ?")) {
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Erfolgreich entfernt!");
return 200;
} else if (0==$stmt->affected_rows) {
$this->man->AddMessage("Fehler: Es wurde nichts entfernt!");
} else {
$this->man->AddMessage("Fehler: Es wurden mehrere Einträge entfernt!");
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500;
}
public function GetResult() {
return $this->output;
}
public function GetMeta() {
return $this->meta;
}
public function GetOptions($admin = false) {
return $this->options;
}
/******************** Protected functions ********************/
protected function getFilterString($op, $allowedOps, $table, $field, $value) {
$tblString = ""!=$table ? $table."." : "";
if (in_array($op, $allowedOps)) {
switch ($op) {
case "=":
return $value===null ? $tblString."`".$field."` IS NULL " : $tblString."`".$field."` = '".$value."' ";
case "<>":
return $value===null ? $tblString."`".$field."` IS NOT NULL " : $tblString."`".$field."` <> '".$value."' ";
case "<":
return $tblString."`".$field."` < '".$value."' ";
case ">":
return $tblString."`".$field."` > '".$value."' ";
case "<=":
return $tblString."`".$field."` <= '".$value."' ";
case ">=":
return $tblString."`".$field."` >= '".$value."' ";
case "like":
return $tblString."`".$field."` LIKE '%".$value."%' ";
case "not like":
return $tblString."`".$field."` NOT LIKE '%".$value."%' ";
case "in":
return "'".$value."' IN ".$tblString.$field."' ";
case "not in":
return "'".$value."' NOT IN ".$tblString.$field."' ";
} }
return "1 ";
}
protected function registerGroup($group) {
if (!array_key_exists($group, $this->groupIndex)) {
$len = array_push($this->output, ["ID" => $group, "ENTRIES" => []]);
$this->groupIndex[$group] = &$this->output[$len-1];
} }
protected function addEntryToOutput($group, $entry, $single) {
if ($single) {
$entry["GROUP"] = $group;
$this->output = $entry;
} else {
$this->registerGroup($group);
$this->groupIndex[$group]["ENTRIES"][] = $entry;
} }
protected function fillOptions($admin = false) {
return [];
}
protected function getSub($field, $string) {
$ret = [];
$entries = explode(",", $string ?? "");
foreach ($entries as $entry) {
if (""!=$entry && isset($this->optionsIndex[$field][$entry])) {
$ret[] = $this->optionsIndex[$field][$entry];
} }
return $ret;
}
}
function pgsIncPage($name)
{
#Checks if a mainpage exists, completes filename and returns valid page or default
$path = "pgs/".$name."/";
$possiblefiles = array("index.php", $name.".php");
foreach ($possiblefiles as $val)
{
if (file_exists($path.$val)) return $path.$val;
}
}
function pgsInclSub($name)
{ //
global $pages,$page;
$possibleexts = array("", ".php", ".htm", ".html", ".txt");
$file = "pgs/".$pages[$page["main"]]["path"]."/".$name;
foreach ($possibleexts as $val)
{
if (file_exists($file.$val)) return $file.$val;
}
addError("pgsInc", $name);
return false;
}
?>

File diff suppressed because it is too large Load Diff

271
lib/40_email.php Executable file
View File

@ -0,0 +1,271 @@
<?php #lib/40_email.php
/********************************************************************************
* Content: email related functions *
* Author: Nils Otterpohl *
********************************************************************************/
function emlSendEmail($to, $subject, $text) {
global $mail;
$mail->setFrom(MAILNOREPLY, "Webserver OF Innenstadt");
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $text;
$mail->addAddress($to);
$mail->send();
$mail->clearAddresses();
}
// Aus pgs/xx_termine/lib.php
function printTeilnehmer($val) {
$ret = "<tr><td>".$val["Nachnamen"].", ".$val["Vornamen"]."</td><td>";
$traits = array();
if ($val["ZF"]) $traits[] = "ZF";
if ($val["GF"]) $traits[] = "GF";
if ($val["KF"]) $traits[] = "KF";
if ($val["AGT"]) $traits[] = "AGT";
if ($val["TH"]) $traits[] = "TH";
if (!$val["TM"]) $traits[] = "Kein Grundlehrgang";
if (count($traits)) $ret.= implode(", ", $traits);
$ret.= "</td><td>Corona-Unterweisung: ".($val["CoronaUnt"] ? "Ja" : "Nein")."</td>"
."<td>".($val["Teilnehmer"] ? "Dabei" : "Will")."</td></tr>\n";
return $ret;
}
function informParticipant($name, $mail, $can, $dienst, $verantwortliche) {
$text = "Hallo ".$name."!<br/><br/>\nDu hast dich für ".$dienst." angemeldet.<br/><br/>";
if ($can) {
$text.= "Du wurdest zu diesem Dienst als Teilnehmer ausgelost und <b>DARFST KOMMEN</b>. Sei bitte pünktlich und bring eine Maske mit!<br/>"
."Wenn Du dich am Tag des Dienstes nicht gesund fühlst, bleib zuhause! Wenn Du aus welchem Grund auch immer doch nicht kommen kannst, "
."teile dieses bitte unverzüglich deinen Diensthabenden mit: ".$verantwortliche."<br/><br/>\n";
} else {
$text.= "Leider hattest Du diesmal Pech und <u>DARFST N I C H T KOMMEN</u>. Dafür wirst Du bei den nächsten Verlosungen bevorzugt.<br/><br/>\n";
}
$text.= "Viele Grüße vom Webserver der OF Innenstadt!";
emlSendEmail($mail, "Ergebnis der Platzvergabe für ".$dienst, $text);
}
function informParticipantFromID($mysqli, $terminID, $personalID, $can) {
$qry = "SELECT Beginn,Thema,Verantwortliche,a.Name FROM Termine t LEFT JOIN Termine_Kategorien a ON a.ID = t.Kategorie WHERE t.ID = '".$terminID."'";
$res = $mysqli->query($qry);
$termin = $res->fetch_assoc();
$qry = "SELECT Vornamen,Login FROM Personal p WHERE p.ID = '".$personalID."'";
$res = $mysqli->query($qry);
if ($personal = $res->fetch_assoc())
informParticipant($personal["Vornamen"], $personal["Login"]."@feuerwehr-bs.net", $can, $termin["Name"]." am ".date("d.m.Y H:i", strtotime($termin["Beginn"])), $termin["Verantwortliche"]);
}
function sendParticipantsListToResponsibles($mysqli, $terminID) {
$qry = "SELECT t.Beginn,IF(t.Beginn>=NOW(),1,0) nichtvorbei,t.Thema,t.Verantwortliche,a.Name as aName FROM Termine t "
."LEFT JOIN Termine_Kategorien a ON a.ID=t.Kategorie "
."WHERE t.ID='".$terminID."'";
$res = $mysqli->query($qry);
$tmrow = $res->fetch_assoc();
if ("1" == $tmrow["nichtvorbei"]) {
// Generate participants list
$qry = "SELECT p.ID,p.Login,p.Nachnamen,p.Vornamen,pk.Name pkName,pf.Kürzel pfKürzel,GROUP_CONCAT(l.Kürzel) lKürzel, tt.Status "
."FROM Termine_Teilnahmen tt "
."LEFT JOIN Personal p ON p.ID=tt.Personal "
."LEFT JOIN Personal_Kategorien pk ON pk.ID=p.Kategorie "
."LEFT JOIN Personal_Funktionen pf ON pf.ID=p.Funktion "
."LEFT JOIN Personal_Lehrgänge pl ON pl.Personal=p.ID "
."LEFT JOIN Lehrgänge l ON l.ID=pl.Lehrgänge "
."WHERE tt.Termine='".$terminID."' "
."GROUP BY p.ID ORDER BY tt.Status DESC, p.Kategorie DESC";
$res = $mysqli->query($qry);
$status = "";
$recipients = array();
$liste = "<h3>Teilnahmeliste für ".$tmrow["aName"]." am ".date("d.m.Y H:i", strtotime($tmrow["Beginn"])).": ".$tmrow["Thema"]."</h3>\n<table border=1>\n";
$markedCauser = false;
$liste.= "<tr><th>Name</th><th>Funktionen</th><th>Corona-Unterweisung</th></tr>\n";
while ($row = $res->fetch_assoc()) {
if ($status!=$row["Status"]) {
$status = $row["Status"];
if ("2" == $status) {
$liste.= "<tr><th colspan=3>Diensthabende</th></tr>\n";
} else if ("1" == $status) {
$liste.= "<tr><th colspan=3>Erlaubte Teilnehmer</th></tr>\n";
} else if ("0" == $status) {
$liste.= "<tr><th colspan=3>Weitere Interessenten</th></tr>\n";
} }
$liste.= "<tr><td>".$row["Nachnamen"].", ".$row["Vornamen"]."</td><td>";
$ausb = explode(",", $row["lKürzel"]);
$traits = array();
if ($row["pfKürzel"]!=null) $traits[] = $row["pfKürzel"];
if (in_array("KF", $ausb)) $traits[] = "KF";
if (in_array("MA", $ausb)) $traits[] = "MA";
if (in_array("AGT", $ausb)) $traits[] = "AGT";
if (in_array("TH", $ausb)) $traits[] = "TH";
if (!in_array("TM1", $ausb)) $traits[] = "Ohne TM1";
if (count($traits)) $liste.= implode(", ", $traits);
$liste.= "</td><td>".(in_array("CU", $ausb) ? "Ja" : "Nein")."</td></tr>\n";
if ("2" == $row["Status"]) {
$recipients[] = $row["Login"]."@feuerwehr-bs.net";
}
}
$liste.= "</table>\n";
if (!in_array("nils.otterpohl@feuerwehr-bs.net", $recipients))
$recipients[] = "nils.otterpohl@feuerwehr-bs.net";
foreach ($recipients as $recipient) {
emlSendEmail($recipient, "Teilnahmeliste - Dienst ".date("D, den d.m.Y um H:i", strtotime($tmrow["Beginn"])), $liste);
}
return $liste;
}
return "";
}
//
// mysql_con mysqli
// string subject
// string text
// int notificationLvl : {1: Neuer Thread, 2: Neue Nachricht}
// string neededRecipientRight
/*function emlSendNotification($mysqli, $sendUser, $subject, $text, $notificationLvl, $neededRecipientRight = null) {
global $mail;
$mail->setFrom(MAILNOREPLY, "Sophia und Nils Hochzeitswebsite");
$mail->isHTML(false);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$qry = "SELECT DISTINCT u.email, u.groupID FROM users u "
.($neededRecipientRight!==null ? "LEFT JOIN rolerights rr ON rr.roleID=u.roleID LEFT JOIN rights r ON r.ID=rr.rightID " : "")
."WHERE u.ID!=".$sendUser." AND u.notifications>=".$notificationLvl." ".($neededRecipientRight!==null ? "AND r.name='".$neededRecipientRight."' " : "");
$res = $mysqli->query($qry);
while ($row = $res->fetch_assoc()) {
$nameres = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$row["groupID"]);
$to = "";
while ($namerow = $nameres->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $namerow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($row["email"]);
$mail->send();
$mail->clearAddresses();
}
}*/
// Sendet eine Mail an ALLE User (die nicht abgesagt haben)
// mysql_con mysqli
// int sendUser
// string from
// string subject
// string text
// string neededRecipientRight
/*function emlSendMassMail($mysqli, $sendUser, $from, $subject, $text, $neededRecipientRight = null) {
global $mail;
if (!$res = $mysqli->query("SELECT DISTINCT IFNULL(u.eMailFrom, u.email) email FROM users u WHERE u.ID=".$sendUser))
addError("Mysql", $mysqli->error);
$from_mail = $res->fetch_assoc()["email"];
$mail->setFrom($from_mail, $from);
$mail->isHTML(true);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$qry = "SELECT DISTINCT u.email, u.groupID FROM guests g LEFT JOIN users u ON u.groupID=g.groupID "
.($neededRecipientRight!==null ? "LEFT JOIN rolerights rr ON rr.roleID=u.roleID LEFT JOIN rights r ON r.ID=rr.rightID " : "")
."WHERE u.ID!=".$sendUser." AND IFNULL(g.comes, 99)!=0 ".($neededRecipientRight!==null ? "AND r.name='".$neededRecipientRight."' " : "");
$res = $mysqli->query($qry);
while ($row = $res->fetch_assoc()) {
$tores = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$row["groupID"]);
$to = "";
while ($torow = $tores->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $torow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($row["email"]);
$mail->send();
$mail->clearAddresses();
}
}*/
// Sendet eine Mail an EINEN User
// mysql_con mysqli
// int targetUser
// string subject
// string text
/*function emlSendSingleMail($mysqli, $targetUser, $subject, $text) {
global $mail;
$ret = false;
$targetMail = null;
$groupID = null;
if ($stmt = $mysqli->prepare("SELECT email, groupID FROM users u WHERE u.ID = ?")) {
$stmt->bind_param("i", $targetUser);
$stmt->execute();
$stmt->bind_result($targetMail, $groupID);
$stmt->fetch();
$stmt->close();
} else {
addError("Mysql", $mysqli->error);
}
if ($targetMail!=null && $groupID!=null) {
$mail->setFrom(MAILNOREPLY, "Sophia und Nils Hochzeitswebsite");
$mail->isHTML(true);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$tores = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$groupID);
$to = "";
while ($torow = $tores->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $torow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($targetMail);
$mail->send();
$mail->clearAddresses();
$ret = true;
}
return $ret;
}*/

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
<?php
/**
* PHPMailer Exception class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2017 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer exception handler.
*
* @author Marcus Bointon <phpmailer@synchromedia.co.uk>
*/
class Exception extends \Exception
{
/**
* Prettify error message output.
*
* @return string
*/
public function errorMessage()
{
return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
}
}

View File

@ -1,192 +0,0 @@
<?php #lib/main.php
/********************************************************************************
* Content: email related functions *
* Author: Nils Otterpohl *
********************************************************************************/
function emlSendEmail($to, $subject, $text) {
global $mail;
$mail->setFrom(MAILNOREPLY, "Webserver OF Innenstadt");
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $text;
$mail->addAddress($to);
$mail->send();
$mail->clearAddresses();
}
// string $email
// array $prenames
// array $surnames
// string $password
function emlSendInvitation($email, $invites, $password) {
global $mail;
$mail->setFrom(MAILNOREPLY, "Sophia und Nils Hochzeitswebsite");
$mail->addAddress($email);
$mail->isHTML(true);
$mail->Subject = "Einladung zur Hochzeit von Sophia und Nils";
$tpl = tplLoadFile("res/invitation.html");
$text = tplExtrSection($tpl, "###INVITATION###");
$to_tpl = tplExtrSection($text, "###TO###");
$to = "";
$count = 0;
foreach ($invites as $names) {
if ($names["companion"]==0) {
$count++;
$replace = array(
"###ADDRESSING###" => $names["addressing"],
"###PRENAMES###" => $names["prenames"],
"###SURNAMES###" => $names["surnames"],
"###NICKNAME###" => $names["nickname"]=="" ? $names["prenames"] : $names["nickname"]
);
$to.= tplReplMarkerArray($to_tpl, $replace);
}
}
$text = tplReplSection($text, "###TO###", $to);
$text = tplReplSection($text, "###INTRO###", $count>1 ? tplExtrSection($tpl, "###INTRO.MULTI###") : tplExtrSection($tpl, "###INTRO.SINGLE###"));
$text = tplReplSection($text, "###EXPLANATION###", $count>1 ? tplExtrSection($tpl, "###EXPLANATION.MULTI###") : tplExtrSection($tpl, "###EXPLANATION.SINGLE###"));
$text = tplReplSection($text, "###MULTIPLEASE###", $count>1 ? tplExtrSection($tpl, "###MULTIPLEASE###") : "");
$url = parse_url("https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
$text = tplReplMarker($text, "###PAGEURL###", "https://".$url["host"].$url["path"]);
$text = tplReplMarker($text, "###EMAIL###", $email);
$text = tplReplMarker($text, "###PASSWORD###", $password);
$mail->Body = $text;
$attachments = explode(";", tplExtrSection($tpl, "###ATTACHMENTS###"));
foreach ($attachments as $attachment) {
if ($attachment!="") {
$attributes = explode("|", $attachment);
if (count($attributes)>1 && $attributes[0]=="embedded") {
$mail->addEmbeddedImage($attributes[1].$attributes[2], $attributes[3], $attributes[2]);
} else {
$mail->addAttachment($attachment);
}
}
}
//$mail->addEmbeddedImage("res/Einladung.png", "cid_einladung", "Einladung.png");
$mail->send();
$mail->clearAddresses();
$mail->clearAttachments();
return true;
}
// mysql_con mysqli
// string subject
// string text
// int notificationLvl : {1: Neuer Thread, 2: Neue Nachricht}
// string neededRecipientRight
function emlSendNotification($mysqli, $sendUser, $subject, $text, $notificationLvl, $neededRecipientRight = null) {
global $mail;
$mail->setFrom(MAILNOREPLY, "Sophia und Nils Hochzeitswebsite");
$mail->isHTML(false);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$qry = "SELECT DISTINCT u.email, u.groupID FROM users u "
.($neededRecipientRight!==null ? "LEFT JOIN rolerights rr ON rr.roleID=u.roleID LEFT JOIN rights r ON r.ID=rr.rightID " : "")
."WHERE u.ID!=".$sendUser." AND u.notifications>=".$notificationLvl." ".($neededRecipientRight!==null ? "AND r.name='".$neededRecipientRight."' " : "");
$res = $mysqli->query($qry);
while ($row = $res->fetch_assoc()) {
$nameres = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$row["groupID"]);
$to = "";
while ($namerow = $nameres->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $namerow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($row["email"]);
$mail->send();
$mail->clearAddresses();
}
}
// Sendet eine Mail an ALLE User (die nicht abgesagt haben)
// mysql_con mysqli
// int sendUser
// string from
// string subject
// string text
// string neededRecipientRight
function emlSendMassMail($mysqli, $sendUser, $from, $subject, $text, $neededRecipientRight = null) {
global $mail;
if (!$res = $mysqli->query("SELECT DISTINCT IFNULL(u.eMailFrom, u.email) email FROM users u WHERE u.ID=".$sendUser))
addError("Mysql", $mysqli->error);
$from_mail = $res->fetch_assoc()["email"];
$mail->setFrom($from_mail, $from);
$mail->isHTML(true);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$qry = "SELECT DISTINCT u.email, u.groupID FROM guests g LEFT JOIN users u ON u.groupID=g.groupID "
.($neededRecipientRight!==null ? "LEFT JOIN rolerights rr ON rr.roleID=u.roleID LEFT JOIN rights r ON r.ID=rr.rightID " : "")
."WHERE u.ID!=".$sendUser." AND IFNULL(g.comes, 99)!=0 ".($neededRecipientRight!==null ? "AND r.name='".$neededRecipientRight."' " : "");
$res = $mysqli->query($qry);
while ($row = $res->fetch_assoc()) {
$tores = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$row["groupID"]);
$to = "";
while ($torow = $tores->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $torow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($row["email"]);
$mail->send();
$mail->clearAddresses();
}
}
// Sendet eine Mail an EINEN User
// mysql_con mysqli
// int targetUser
// string subject
// string text
function emlSendSingleMail($mysqli, $targetUser, $subject, $text) {
global $mail;
$ret = false;
$targetMail = null;
$groupID = null;
if ($stmt = $mysqli->prepare("SELECT email, groupID FROM users u WHERE u.ID = ?")) {
$stmt->bind_param("i", $targetUser);
$stmt->execute();
$stmt->bind_result($targetMail, $groupID);
$stmt->fetch();
$stmt->close();
} else {
addError("Mysql", $mysqli->error);
}
if ($targetMail!=null && $groupID!=null) {
$mail->setFrom(MAILNOREPLY, "Sophia und Nils Hochzeitswebsite");
$mail->isHTML(true);
$mail->Subject = $subject;
$to_tpl = tplExtrSection($text, "###TO###");
$tores = $mysqli->query("SELECT prenames `###PRENAMES###`, surnames `###SURNAMES###`, addressing `###ADDRESSING###`, IFNULL(nickname, prenames) `###NICKNAME###` "
."FROM guests WHERE companion=0 AND groupID=".$groupID);
$to = "";
while ($torow = $tores->fetch_assoc()) {
$to.= tplReplMarkerArray($to_tpl, $torow);
}
$mail->Body = tplReplSection($text, "###TO###", $to);
$mail->addAddress($targetMail);
$mail->send();
$mail->clearAddresses();
$ret = true;
}
return $ret;
}
?>

6
lib/50_content.php Normal file → Executable file
View File

@ -14,12 +14,8 @@ function cntCipherTextSym($text, $cipher)
{
for ($j=0; $j<strlen($cipher) && $i<strlen($text); $j++, $i++)
{
$outText .= $text{$i} ^ $cipher{$j};
$outText .= $text[$i] ^ $cipher[$j];
}
}
return $outText;
}
?>

444
lib/60_printer.php Normal file
View File

@ -0,0 +1,444 @@
<?php #ajax/print.php
class Printer
{
private $db;
private $man;
private $page;
private $responseCode = 500;
private $prints = [];
private $html = "";
public function __construct($db, $man) {
$this->db = $db;
$this->man = $man;
$this->page = $this->man->Route();
$qry = "SELECT l.ID,l.Bezeichnung,l.Hauptblock,b.Css,b.Breite,b.Höhe FROM sys_print_layouts l "
."LEFT JOIN sys_print_blocks b ON l.Hauptblock=b.ID WHERE Seite=? ORDER BY l.Bevorzugt DESC, l.Bezeichnung ASC";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("s", $this->page);
$stmt->execute();
$res = $stmt->get_result();
while ($row = $res->fetch_assoc()) {
$this->prints[] = [
"ID" => strval($row["ID"]),
"Bezeichnung" => $row["Bezeichnung"],
"Hauptblock" => $row["Hauptblock"],
"Css" => $row["Css"],
"Breite" => $row["Breite"],
"Höhe" => $row["Höhe"],
];
} } }
public function GetPrints() {
return $this->prints;
}
public function CreateHtml($printID) {
// Find requested layout
$layout = null;
foreach ($this->prints as $key => $print) {
if (strval($printID)==$print["ID"]) {
$layout = $print;
break;
} }
// Breakoff if layout doesn't exist
if (null == $layout) {
$this->responseCode = 400;
return;
}
// Get content
$content = $this->man->Output()["content"];
$block = [
"ID" => $layout["Hauptblock"],
"Bezeichnung" => $layout["Bezeichnung"],
"Css" => $layout["Css"],
"Breite" => $layout["Breite"],
"Höhe" => $layout["Höhe"],
"Links" => 0,
"Oben" => 0
];
$entries = $this->prepareEntries($content);
$this->printBlock($block, $entries);
$this->html.= $this->joinHtml($entries);
$this->responseCode = 200;
}
public function GetHtml() {
return $this->html;
}
public function ResponseCode() {
return $this->responseCode;
}
/******************** Private functions *************************/
private function prepareEntries($content) {
$ret = [];
$selectionExists = sizeof($this->man->selected)>0;
foreach ($content as $group) {
foreach ($group["ENTRIES"] as $entry) {
if (!$selectionExists || in_array($entry["ID"], $this->man->selected)) {
$ret[$entry["ID"]] = [
"html" => "",
"floating" => [],
"content" => $entry,
];
} } }
return $ret;
}
private function joinHtml($entries) {
$ret = "";
foreach ($entries as $entry) {
$ret.= $entry["html"];
}
return $ret;
}
private function printBlock($block, &$entries) {
// Initialize block
$div = "<div name='".$block["Bezeichnung"]."' style='".$block["Css"]
."width:".($block["Breite"]/10)."mm;height:".($block["Höhe"]/10)."mm;left:".($block["Links"]/10)."mm;top:".($block["Oben"]/10)."mm;'>";
foreach ($entries as &$entry) {
$entry["html"].= $div;
}
unset($entry); // $entry was used as a reference, so it needs to be unset
// Fetch sub-blocks
$qry = "SELECT spb.*,sph.Links,sph.Oben FROM sys_print_hierarchy sph LEFT JOIN sys_print_blocks spb ON spb.ID=sph.Kindblock WHERE sph.Block=".$block["ID"];
$res = $this->db->query($qry);
while ($subblock = $res->fetch_assoc()) {
$this->printBlock($subblock, $entries);
}
// Fetch block elements
$elements = [];
if ($res = $this->db->query("SELECT * FROM sys_print_elements WHERE Block=".$block["ID"]." ORDER BY Reihenfolge ASC")) {
while ($element = $res->fetch_assoc()) {
$this->printEntryElement($element, $entries);
//$elements[] = $row;
} }
// Finalize block
foreach ($entries as &$entry) {
$entry["html"].= "</div>";
}
unset($entry);
}
private function printEntryElement($element, &$entries) {
foreach ($entries as &$entry) {
$do_print = false;
$cond = explode("|", $element["Bedingung"]);
if ($element["Parameter"]=="") {
$element["Parameter"] = "{}";
}
$params = json_decode($element["Parameter"], true);
switch ($cond[0]) {
case null:
case "":
case "0": //Print always
$do_print = true;
//echo " - Print always";
break;
case "1": //Field has to be NOT null, not false and not empty text
//echo " - Print if NOT null/false/empty";
if (sizeof($cond)>1 && ($this->getContent($entry["content"], $cond[1])!==null && (
$this->getContent($entry["content"], $cond[1])!==false || $this->getContent($entry["content"], $cond[1], true)!=""
) ) ) {
$do_print = true;
}
break;
case "2": //Field has to be null, false or empty text
//echo " - Print if null/false/empty";
if (sizeof($cond)>1 && ($this->getContent($entry["content"], $cond[1])===null || $this->getContent($entry["content"], $cond[1])===false || $this->getContent($entry["content"], $cond[1], true)==""))
$do_print = true;
break;
case "3": //Field has to be value (second parameter) or include value if is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes value ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1]);
$arr = is_array($result);
$do_print = ($arr && in_array($cond[2], $result)) || (!$arr && $cond[2]==$result);
}
break;
case "4": //Field has to be NOT value (second parameter) or DONT include value if is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes NOT value ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1]);
$arr = is_array($result);
$do_print = ($arr && !in_array($cond[2], $result)) || (!$arr && $cond[2]!=$result);
}
break;
case "5": //Field has to be text (second parameter) or include text if it is link
if (sizeof($cond)>2) {
//echo " - Print if has/includes text ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1], 1);
$testtexts = explode(",", $cond[2]);
if (is_array($result)) {
$do_print = true;
foreach ($testtexts as $testtext) {
if (!in_array($testtext, $result)) {
$do_print = false;
break;
} }
} else {
$do_print = $cond[2]==$result;
} }
break;
case "6": //Field has to be NOT text (second parameter) or DONT include text if it is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes NOT text ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1], 1);
$testtexts = explode(",", $cond[2]);
if (is_array($result)) {
$do_print = true;
foreach ($testtexts as $testtext) {
if (in_array($testtext, $result)) {
$do_print = false;
break;
} }
} else {
$do_print = $cond[2]!=$result;
} }
break;
}
$print_count = 0;
if ($do_print) {
$print_count = $params["repeat"] ?? 1;
}
while ($print_count>0) {
//echo " - Printing";
$print_count--;
$border = array();
$border["top"] = isset($params["border-top"]) ? $params["border-top"] : (isset($params["border"]) ? $params["border"] : "0");
$border["bottom"] = isset($params["border-bottom"]) ? $params["border-bottom"] : (isset($params["border"]) ? $params["border"] : "0");
$border["left"] = isset($params["border-left"]) ? $params["border-left"] : (isset($params["border"]) ? $params["border"] : "0");
$border["right"] = isset($params["border-right"]) ? $params["border-right"] : (isset($params["border"]) ? $params["border"] : "0");
$border["color"] = isset($params["border-color"]) ? $params["border-color"] : "#000000";
$border["string"] = "";
if ($border["left"]!="0") {
$border["string"].= "border-left: 1px solid ".$border["color"]."; ";
}
if ($border["right"]!="0") {
$border["string"].= "border-right: 1px solid ".$border["color"]."; ";
}
if ($border["top"]!="0") {
$border["string"].= "border-top: 1px solid ".$border["color"]."; ";
}
if ($border["bottom"]!="0") {
$border["string"].= "border-bottom: 1px solid ".$border["color"]."; ";
}
/*if (isset($params["border"])) {
$border["string"] = "border: 1px solid ".$border["color"].";";
}*/
$cnt = $element["Inhalt"];
$fields = array();
if (preg_match_all("/#([^#\[\]]+)(?:\[(\d*):(\d*)\])?#/", $cnt, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$replace = "";
$fields[] = $match[1];
$replace = $this->getContent($entry["content"], $match[1], 1);
if (sizeof($match)>3) {
if ($match[3]!="") {
$replace = substr($replace, 0, intval($match[3]));
}
if ($match[2]!="") {
$replace = substr($replace, intval($match[2]));
}
}
$cnt = str_replace($match[0], $replace, $cnt);
}
}
$left = $element["Links"]/10;
$top = $element["Oben"]/10;
$width = $element["Breite"]/10;
if ($width == 0) {
$width = 10;
}
$height = $element["Höhe"]/10;
if ($height == 0) {
$height = 10;
}
$this->moveIfOverlap($entry["floating"], $left, $top, $width, $height, isset($params["float"]) ? $params["float"] : "none");
$position = "position:absolute;";
if (isset($params["position"])) {
$position = "position:".$params["position"].";";
}
$position.= "z-index:".$element["Reihenfolge"].";";
switch ($element["Art"]) {
case "img":
if (isset($params["aspect"]) && $params["aspect"]=="keep") {
$imgsize = "max-width: ".$width."mm; max-height: ".$height."mm; width: auto; height: auto; ";
}
if (isset($params["aspect"]) && $params["aspect"]=="height") {
$imgsize = "height: ".$height."mm; width: auto; ";
} else {
$imgsize = "width: ".$width."mm; height: ".$height."mm; ";
}
$path = $cnt;
if (count($fields)) {
$path = $cnt;
}
//if (file_exists($path)) {
$entry["html"].= "<img src='".$path."' style='".$position." left: ".$left."mm; top: ".$top."mm; ".$imgsize."' />";
//}
break;
case "text":
$rotate = "";
if (isset($params["rotate"]) && $params["rotate"]=="1") {
$rotate = "transform: rotate(-90deg); ";
$left = "calc(".($left + ($height - $width)/2)."mm - ".$border["left"]."px); ";
$top = "calc(".($top + ($width - $height)/2)."mm - ".$border["top"]."px); ";
} else {
$left.= "mm; ";
$top.= "mm; ";
}
$font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
$font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
if (isset($params["weight"])) {
$font.= "font-weight: ".$params["weight"]."; ";
}
if (isset($params["align"])) {
$font.= "text-align: ".$params["align"]."; ";
}
if (isset($params["color"])) {
$font.= "color: ".$params["color"]."; ";
}
$entry["html"].= "<span style='".$position."overflow:hidden;white-space:nowrap;left:".$left."top:".$top."width:".$width."mm;height:"
.$height."mm;".$font.$border["string"].$rotate."'>".$cnt."</span>";
break;
case "circle":
$left.= "mm; ";
$top.= "mm; ";
$display = "display: inline-flex; align-items: center; justify-content: space-around; ";
$font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
$font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
if (isset($params["weight"])) {
$font.= "font-weight: ".$params["weight"]."; ";
}
if (isset($params["color"])) {
$font.= "color: ".$params["color"]."; ";
}
$font.= "text-align: center; ";
$background = isset($params["background"]) ? "background-color: ".$params["background"].";" : "";
$entry["html"].= "<span style='".$position."left:".$left."top:".$top."width:".$width."mm;height:".$height."mm;".$display.$background.$font
.$border["string"]."border-radius:50%;'>".$cnt."</span>";
break;
case "quartercircle":
$left.= "mm; ";
$top.= "mm; ";
$background = isset($params["background"]) ? "background-color: ".$params["background"].";" : "";
$border_radius = isset($params["corner"]) ? "border-".$params["corner"]."-radius:100%;" : "border-top-left-radius:100%;";
$entry["html"].= "<span style='".$position."left:".$left."top:".$top."width:".$width."mm;height:".$height."mm;"/*.$display*/.$background/*.$font*/
.$border_radius."'>".$cnt."</span>";
break;
} } }
unset($entry);
}
private function getTextFromReference($ref, $getText) {
if (1==$getText && array_key_exists("KÜRZEL", $ref)) {
return $ref["KÜRZEL"];
} elseif (1<=$getText && array_key_exists("NAME", $ref)) {
return $ref["NAME"];
} elseif (array_key_exists("ID", $ref)) {
return $ref["ID"];
}
return "";
}
// $entry Content-array of entry ([ID, MAIN, SUB])
// $key Requested field
// $getText 0 = ID is sufficient, 1 = Kürzel (prio.) or Name desired, 2 = Name desired
private function getContent($entry, $key, $getText = 0) {
// Check if requested field are actually nested keys separated by dots
$dotPos = strpos($key, ".");
if ($dotPos!==false) {
$subkey = substr($key, 0, $dotPos);
if (array_key_exists($subkey, $entry)) {
return $this->getContent($entry[$subkey], substr($key, $dotPos+1, strlen($key)-$dotPos-1));
} }
// Check if requested field exists
if (array_key_exists($key, $entry)) {
// Check if requested field is an array (which means it was selected through a foreign reference or link table)
if (is_array($entry[$key])) {
// Check if array is size 0 -> This means an unset reference, i.e. NULL
if (0==sizeof($entry[$key])) {
return null;
} elseif (array_key_exists("ID", $entry[$key])) { // Check if requested field is a normal foreign reference -> It will have the ID field then.
// Foreign reference with ID, NAME and maybe KÜRZEL
return $this->getTextFromReference($entry[$key], $getText);
} else { // If not, we assume to have an sequential array which means a n:m-link-table
$ret = [];
foreach ($entry[$key] as $i => $ref) {
$ret[] = $this->getTextFromReference($ref, $getText);
}
return $ret;
}
} else { // Requested field is a normal value
return $entry[$key];
}
} elseif (array_key_exists("MAIN", $entry)) { // If we are in the root structure of a content object, we will have sections MAIN and SUB and check those recursively
// Search the MAIN-array for field
$ret = $this->getContent($entry["MAIN"], $key, $getText);
// If only the field name was returned check SUB-array if exists
if ($key==$ret && array_key_exists("SUB", $entry)) {
$ret = $this->getContent($entry["SUB"], $key, $getText);
}
return $ret;
} else {
// Nothing was found, we return the field name
return $key;
} }
private function moveIfOverlap(&$floating, &$left, &$top, $width, $height, $dir = "none", $lastdir = "none") {
$moved = false;
foreach ($floating as $key => $float) {
if (
$left < $float["right"] &&
$left+$width > $float["left"] &&
$top < $float["bottom"] &&
$top+$height > $float["top"]
) {
if ($float["dir"]=="left" && $lastdir!="right") {
$left = $float["right"];
} else if ($float["dir"]=="right" && $lastdir!="left") {
$left = $float["left"] - $width;
} else if ($float["dir"]=="up" && $lastdir!="down") {
$top = $float["bottom"];
} else if ($float["dir"]=="down" && $lastdir!="up") {
$top = $float["top"] - $height;
}
$lastdir = $float["dir"];
$moved = true;
}
}
if ($moved) {
$this->moveIfOverlap($floating, $left, $top, $width, $height, $dir, $lastdir);
} else if ($dir!="none") {
$floating[] = array(
"left" => $left,
"right" => $left + $width,
"top" => $top,
"bottom" => $top + $height,
"dir" => $dir
);
} }
}

332
lib/99_manager.php Executable file
View File

@ -0,0 +1,332 @@
<?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();
}
}

74
lib_new/10_response.php Normal file
View File

@ -0,0 +1,74 @@
<?php #lib/90_response.php
class Response
{
public $code = 500;
private $body = ["messages" => [], "content" => []];
private $etag = null;
private $html = null;
private $hasBody = true;
private static $instance = null;
/***** Static functions *****/
public static function &Get() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/***** Con-/Destructor *****/
private function __construct() {}
public function __destruct() {
http_response_code($this->code);
if (is_null($this->html)) {
header("Content-type: application/json");
} else {
header("Content-type: text/html");
}
if (!is_null($this->etag)) {
header("ETag: ".$this->etag);
}
if ($this->hasBody) {
if ($this->IsOk() && Request::IsPrint()) {
echo trim(Printer::Print(Request::PrintID, $this->body["content"]));
} else {
echo trim(json_encode($this->body));
} } }
/***** Public functions *****/
public function IsOk() {return 200==$this->code;}
public function &Message($message) {$this->body["messages"][] = $message; return $this;}
public function &Code($code) {$this->code = $code; return $this;}
public function &Json($name, $json) {$this->body[$name] = $json; return $this;}
public function &Content($json) {$this->body["content"] = $json; return $this;}
public function &Etag($etag) {$this->etag = $etag; return $this;}
public function &Html($html) {$this->html = $html; return $this;}
public function &DisableBody() {$this->hasBody = false; return $this;}
public function &Good() {$this->Code(200); return $this;}
public function &Inserted($name) {$this->Code(201)->Message($name." hinzugefügt"); return $this;}
public function &Deleted($name) {$this->Code(200)->Message($name." entfernt"); return $this;}
public function &Updated($name) {$this->Code(200)->Message($name." aktualisiert"); return $this;}
public function &SubInserted() {$this->Code(201)->Message("Verknüpfung hinzugefügt"); return $this;}
public function &SubDeleted() {$this->Code(200)->Message("Verknüpfung entfernt"); return $this;}
public function &SubUpdated() {$this->Code(200)->Message("Verknüpfung aktualisiert"); return $this;}
public function &FileUploaded() {$this->Code(201)->Message("Datei hochgeladen"); return $this;}
public function &FileErased() {$this->Code(200)->Message("Datei gelöscht"); return $this;}
public function &Forbidden() {$this->Code(403); return $this;}
public function &NotFound() {$this->Code(404)->Message("Resource not found"); return $this;}
public function &MissingID() {$this->Code(400)->Message("Required ID was not specified"); return $this;}
public function &IllegalInput() {$this->Code(400)->Message("You are forbidden to change at least something in the provided dataset."); return $this;}
public function &IllegalQuery() {$this->Code(400)->Message("You are forbidden to request at least something in the queried dataset."); return $this;}
public function &MissingContent() {$this->Code(400)->Message("Required input 'content' is missing"); return $this;}
public function &NotImplemented() {$this->Code(405)->Message("This method is not (yet) supported by this resource!"); return $this;}
public function &SubRouteNotExisting($name) {$this->Code(404)->Message("Subroute `".$name."` not existant for this resource"); return $this;}
public function &DbError() {$this->Code(500)->Message("Mysql error: ".Db::Get()->error); return $this;}
}

127
lib_new/20_request.php Normal file
View File

@ -0,0 +1,127 @@
<?php #lib/90_response.php
/********************************************************************************
* Content: Request class *
* Author: Nils Otterpohl *
* Last modification: 03.04.2023 *
* Version: alpha (object, incomplete, uncommented, untested) *
********************************************************************************/
class Request
{
private static $jwt = null;
private static $accept = "HTML";
private static $method = "GET";
private static $route = "/";
private static $id = null;
private static $subroute = null;
private static $subid = null;
private static $input = [];
private static $filter = [];
private static $selected = [];
private static $printID = null;
private static $detailDepth = 0;
/***** Public Static functions *****/
public static function JWT() {return self::$jwt;}
public static function Accept() {return self::$accept;}
public static function Method() {return self::$method;}
public static function Route() {return self::$route;}
public static function ID() {return self::$id;}
public static function Subroute() {return self::$subroute;}
public static function SubID() {return self::$subid;}
public static function Input($name) {return isset(self::$input[$name]) ? self::$input[$name] : null;}
public static function Filter() {return self::$filter;}
public static function Selected() {return self::$selected;}
public static function PrintID() {return self::$printID;}
public static function Read() {
// 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));
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)) {
self::$jwt = $matches[1];
} }
// End of copied
// Read accepted/requested content type
if (isset($_SERVER["HTTP_ACCEPT"]) && "application/json"==$_SERVER["HTTP_ACCEPT"]) {
self::$accept = "JSON";
}
// Read the requested route (resource) and method
self::$method = $_SERVER['REQUEST_METHOD'];
if ("HEAD"==self::$method) {
Response::Get()->DisableBody();
}
if (preg_match('$/([^/]+)/?([^/]+)?/?([^/]+)?/?([^/]+)?$', $_SERVER['PATH_INFO'] ?? "", $matches, PREG_UNMATCHED_AS_NULL)) {
self::$route = $matches[1];
self::$id = $matches[2];
self::$subroute = $matches[3];
self::$subid = $matches[4];
} }
public static function ParseInput() {
// Clean input
if (isset($_SERVER["CONTENT_TYPE"]) && "application/json"==$_SERVER["CONTENT_TYPE"]) {
$input = self::cleanInput(json_decode(file_get_contents("php://input"), true));
if (!empty($input)) {
self::$input = $input;
}
} else if (in_array(self::$method, ["HEAD", "GET"])) {
self::$filter = isset($_GET["filter"]) ? self::cleanInput(json_decode($_GET["filter"], true)) : [];
self::$selected = isset($_GET["selected"]) ? self::cleanInput(json_decode($_GET["selected"], true)) : [];
self::$printID = isset($_GET["print"]) ? self::cleanInput($_GET["print"]) : null;
self::$detailDepth = \Login::HasRight("ADMIN") && isset($_GET["depth"]) ? self::cleanInput($_GET["depth"]) : null;
} }
public static function VerifyInputSecToken($verifyToken) {
if (!empty(self::$input) && (!isset(self::$input["secToken"]) || self::$input["secToken"]!=$verifyToken)) {
//Response::Get()->Message("Wrong or Missing SecToken! Ignoring input.");
//self::$input = [];
} }
public static function IsRoot() {return "/"==self::$route;}
public static function IsServeClient() {return self::IsRoot() && "GET"==self::$method/* && "HTML"==self::$accept*/;}
public static function IsLogout() {return self::IsRoot() && "DELETE"==self::$method;}
public static function IsPrint() {return "HTML"==self::$accept && !is_null(self::$printID);}
public static function DetailDepth() {return self::$detailDepth;}
public static function IssueNewSecToken() {return in_array(self::$method, ["POST", "PATCH", "DELETE"]);}
public static function AllowJwtRenewal() {return "HEAD"!=self::$method && "HTML"!=self::$accept;}
/***** Private Static Functions *****/
private static function cleanInput($value) { // Cleans input
if (is_null($value)) {
return null;
} else if (is_array($value)) {
$ret = [];
foreach ($value as $key => $element) {
$ret[$key] = self::cleanInput($element);
}
return $ret;
} else if (is_string($value)) {
return DB::Get()->escape_string($value);
} else {
return intval($value);
}
}
}

111
lib_new/21_filter.php Normal file
View File

@ -0,0 +1,111 @@
<?php
namespace Filter;
class Condition
{
private $table;
private $field;
private $op;
private $value;
public function Get() {
$tblString = !is_null($this->table) ? $this->table."." : "";
switch ($this->op) {
case "=":
return is_null($this->value) ? $tblString."`".$this->field."` IS NULL " : $tblString."`".$this->field."` = '".$this->value."'";
case "<>":
return is_null($this->value) ? $tblString."`".$this->field."` IS NOT NULL " : $tblString."`".$this->field."` <> '".$this->value."'";
case "<":
return $tblString."`".$this->field."` < '".$this->value."'";
case ">":
return $tblString."`".$this->field."` > '".$this->value."'";
case "<=":
return $tblString."`".$this->field."` <= '".$this->value."'";
case ">=":
return $tblString."`".$this->field."` >= '".$this->value."'";
case "like":
return $tblString."`".$this->field."` LIKE '%".$this->value."%'";
case "not like":
return $tblString."`".$this->field."` NOT LIKE '%".$this->value."%'";
case "in":
return "'".$this->value."' IN ".$tblString.$this->field."'";
case "not in":
return "'".$this->value."' NOT IN ".$tblString.$this->field."'";
}
return "1 ";
}
public function __construct($field, $op, $value, $table = null) {
$this->field = $field;
$this->op = $op;
$this->value = $value;
$this->table = $table;
}
}
class ConditionList
{
private $mode = "or";
private $list = [];
public function Get() {
if (count($this->list)==0) {
return "1";
}
$strings = [];
foreach ($this->list as $entry) {
$strings[] = $entry->Get();
}
$strings = array_unique($strings);
$string = join(" ".$this->mode." ", $strings);
return count($strings)>1 ? "(".$string.")" : $string;
}
public function __construct($mode) {
$this->mode = $mode;
}
public function Add($condition) {
$this->list[] = $condition;
return $this;
}
}
class Filter
{
private static $fields = [];
private static $requested = null;
private static $restricted = null;
public static function Where() {
if (is_null(self::$requested) && is_null(self::$restricted)) {
return "";
}
$ret = " WHERE ";
if (!is_null(self::$requested)) {
$ret.= self::$requested->Get();
}
if (!is_null(self::$restricted)) {
$ret.= (is_null(self::$requested) ? "" : "AND ").self::$restricted->Get();
}
return $ret;
}
public static function Selected($field) {
return empty(self::$fields) || in_array($field, self::$fields);
}
public static function Limit($fields) {
self::$fields = $fields;
}
public static function Request($condition) {
self::$requested = $condition;
}
public static function Restrict($condition) {
self::$restricted = $condition;
}
}

41
lib_new/30_database.php Normal file
View File

@ -0,0 +1,41 @@
<?php #lib/90_response.php
/********************************************************************************
* Content: Database class *
* Author: Nils Otterpohl *
* Last modification: 03.04.2023 *
* Version: alpha (object, incomplete, uncommented, untested) *
********************************************************************************/
class DB
{
private $mysqli = null;
private static $instance = null;
/***** Static functions *****/
public static function Get() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance->Connection();
}
public static function LeftJoin($target, $targetShort, $targetField, $sourceShort, $sourceField) {
return "LEFT JOIN ".$target." ".$targetShort." ON ".$targetShort.".".$targetField."=".$sourceShort.".".$sourceField." ";
}
/***** Con-/Destructor *****/
private function __construct() {
$this->mysqli = new mysqli(DBHOST, DBUSER, DBPASS, DBNAME);
$this->mysqli->set_charset("utf8mb4");
}
public function __destruct() {
$this->mysqli->close();
}
/***** Public functions *****/
public function Connection() {return $this->mysqli;}
}

38
lib_new/40_keyvalue.php Normal file
View File

@ -0,0 +1,38 @@
<?php #lib/90_response.php
/********************************************************************************
* Content: Database class *
* Author: Nils Otterpohl *
* Last modification: 03.04.2023 *
* Version: alpha (object, incomplete, uncommented, untested) *
********************************************************************************/
class KV
{
private $redis = null;
private static $instance = null;
/***** Static functions *****/
public static function Get() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance->Connection();
}
/***** Con-/Destructor *****/
private function __construct() {
$this->redis = new Redis();
$this->redis->connect(REDIS_SOCKET);
$this->redis->select(1);;
}
public function __destruct() {
$this->redis->close();
}
/***** Public functions *****/
public function Connection() {return $this->redis;}
}

276
lib_new/50_login.php Normal file
View File

@ -0,0 +1,276 @@
<?php #lib/10_login.php
/********************************************************************************
* Content: Login class *
* Author: Nils Otterpohl *
* Last modification: 13.12.2020 *
* Version: alpha (object, incomplete, uncommented, untested) *
********************************************************************************/
class Login
{
private static $id = ""; // ID of authenticated user
private static $loggedIn = false;
private static $user = ""; // Login name of authenticated user
private static $vornamen = ""; // Prenames of authenticated user
private static $nachnamen = ""; // Surnames of authenticated user
private static $groups = null;
private static $departments = null;
private static $rights = null;
private static $jwt_expires = 0;
private static $secTokenUse = null; // (One time) Security token to be issued to client to be used with incoming data
private static $secTokenVerify = null; // Last Security token issued. Incoming data will be verified against this
/***** Public Static functions *****/
// Generates a salted password hash of desired length and with the desired algorithm
public static function GenHash($password, $salt, $iterations, $length = 32, $algo = "sha256") {
return hash_pbkdf2($algo, $password, $salt, $iterations, $length*2);
}
// Generates a random salt (or also password) of desired length
public static function GenSalt($length = 16) {
return bin2hex(random_bytes($length));
}
public static function LoggedIn() {return self::$loggedIn;}
public static function ID() {return self::$id;}
public static function User() {return self::$user;}
public static function Vornamen() {return self::$vornamen;}
public static function Nachnamen() {return self::$nachnamen;}
public static function Login() {
// Try to login via JWT, if it was submitted
if (!is_null(Request::JWT())) {
self::authenticateJWT();
}
// Try to login via password, if not yet logged in
if (!self::$loggedIn && !is_null(Request::Input("login")) && !is_null(Request::Input("password"))) {
self::authenticatePWD(Request::Input("login"), Request::Input("password"));
}
if (self::$loggedIn) {
// Erzeuge neues Security-Token
self::$secTokenUse = self::$secTokenVerify;
if (Request::IssueNewSecToken() || self::$secTokenUse==null) {
self::$secTokenUse = self::GenSalt(16);
Response::Get()->Json("secToken", self::$secTokenUse);
}
KV::Get()->set("secToken:".self::$id, self::$secTokenUse);
KV::Get()->expire("secToken:".self::$id, 60*60); // secToken ist für 60 min * 60 s/min gültig
if (!is_null(self::$secTokenVerify)) {
Request::VerifyInputSecToken(self::$secTokenVerify);
} } }
// Blacklists the issued JWT and sets logged in status to false
public static function Logout() {
KV::Get()->set("JWT:Blacklist:".self::$id, Request::JWT());
KV::Get()->expireAt("JWT:Blacklist:".self::$id, self::$jwt_expires);
self::$loggedIn = false; // DEPRECATED
self::$loggedIn = false;
}
public static function VerifySecToken($secToken) {
Response::Get()->Message("sTV: ".self::$secTokenVerify);
return (self::$secTokenVerify==null || $secToken==self::$secTokenVerify);
}
// Returns true if logged in user has the (or, if array, one of the) named right(s)
// If null or empty array is passed, no rights shall be required and return will be true (if logged in).
// Fetched rights will be buffered, so recurring queries are no performance problem
static function HasRight($rightNames) {
if (!self::$loggedIn) {
return false;
}
if ((is_array($rightNames) && empty($rightNames)) || is_null($rightNames)) {
return true;
}
if (is_null(self::$rights)) {
self::$rights = [];
$qry = "SELECT r.Name FROM Personal_Verwalter pv "
."LEFT JOIN Rechte_Verwalter rv ON rv.Verwalter = pv.Verwalter "
."LEFT JOIN Rechte r ON r.ID = rv.Rechte "
."WHERE pv.Personal = ".self::$id;
$res = Db::Get()->query($qry);
while ($row = $res->fetch_assoc()) {
self::$rights[] = $row["Name"];
} }
if (is_array($rightNames)) {
foreach ($rightNames as $r) {
if (in_array($r, self::$rights)) {
return true;
} }
return false;
} else {
return in_array($rightNames, self::$rights);
} }
// Returns true if logged in user is in the (or, if array, one of the) named department(s) ("Abteilungen")
// Fetched departments will be buffered, so recurring queries are no performance problem
public static function InDepartment($departmentIDs) {
if (!self::$loggedIn) {
return false;
}
if ((is_array($departmentIDs) && empty($departmentIDs)) || is_null($departmentIDs)) {
return true;
}
if (!isset(self::$departments)) {
self::$departments = [];
$qry = "SELECT Abteilungen FROM Personal_Abteilungen WHERE Personal = ".self::$id;
$res = Db::Get()->query($qry);
while ($row = $res->fetch_assoc()) {
self::$departments[] = $row["Abteilungen"];
} }
if (is_array($departmentIDs)) {
foreach ($departmentIDs as $dID) {
if (in_array($dID, self::$departments)) {
return true;
} }
return false;
} else {
return in_array($departmentIDs, self::$departments);
} }
// Returns true if logged in user is in the (or, if array, one of the) named group(s) ("Gruppen")
// Fetched groups will be buffered, so recurring queries are no performance problem
public static function InGroup($groupIDs) {
if (!self::$loggedIn) {
return false;
}
if ($groupIDs==null) {
return true;
}
if (!isset(self::$groups)) {
self::$groups = [];
$qry = "SELECT Gruppen FROM Personal_Gruppen WHERE Personal = ".self::$id;
$res = Db::Get()->query($qry);
while ($row = $res->fetch_assoc()) {
self::$groups[] = $row["Gruppen"];
} }
if (is_array($groupIDs)) {
foreach ($groupIDs as $gID) {
if (in_array($gID, self::$groups)) {
return true;
} }
return false;
} else {
return in_array($groupIDs, self::$groups);
} }
/***** Private Static functions *****/
// Tries to decode the submitted JWT, checks blacklisting of JWT and sets logged in status accordingly
private static function authenticateJWT() {
try {
$decoded = \Firebase\JWT\JWT::decode(Request::JWT(), JWT_KEY, ['HS256']);
// What has been an array before encoding is now an object
// Read member variables from decoded data
self::$id = $decoded->data->userID==58 && isset($_GET["emulate"]) ? $_GET["emulate"] : $decoded->data->userID;
self::$user = $decoded->data->userLogin;
self::$vornamen = $decoded->data->userVornamen;
self::$nachnamen = $decoded->data->userNachnamen;
self::$jwt_expires = $decoded->exp;
} catch (Exception $e) {
// JWT will throw exceptions if it cannot decode the JWT. In this case - don't login
return;
}
if (KV::Get()->get("JWT:Blacklist:".self::$id)!=Request::JWT()) {
// Not blacklisted, can log in
self::$loggedIn = true;
self::$secTokenVerify = KV::Get()->get("secToken:".self::$id);
$now = time();
if (self::$jwt_expires-$now <= JWT_VALID_TIME*0.5 && Request::AllowJwtRenewal()) {
Response::Get()->Message("Extending JWT");
self::encodeJWT();
}
} else {
self::$id = -99;
} }
private static function encodeJWT() {
$time = time();
self::$jwt_expires = $time + JWT_VALID_TIME;
$issuer = "http://fw-innenstadt.de/";
$token = [
"iat" => $time,
"exp" => self::$jwt_expires,
"iss" => "fw-innenstadt.de",
"data" => [
"userID" => self::$id,
"userLogin" => self::$user,
"userVornamen" => self::$vornamen,
"userNachnamen" => self::$nachnamen
]
];
Response::Get()->Json("jwt", \Firebase\JWT\JWT::encode($token, JWT_KEY));
}
private static function authenticatePWD($user, $password) {
// Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen
// and https://github.com/nextcloud/user_external/blob/master/lib/webdavauth.php
// and https://codeofaninja.com/2018/09/rest-api-authentication-example-php-jwt-tutorial.html
// Login zum gewünschten Format zurechtbiegen
$user = strtolower($user);
$user = str_replace("@feuerwehr-bs.net", "", $user);
// Das Benutzen vorbereiteter Statements verhindert SQL-Injektion.
if ($stmt = Db::Get()->prepare("SELECT p.ID, p.login, p.Vornamen, p.Nachnamen FROM Personal p WHERE p.login = ? LIMIT 1")) {
$stmt->bind_param("s", $user); // Bind "$user" to parameter.
$stmt->execute(); // Führe die vorbereitete Anfrage aus.
$stmt->store_result();
// hole Variablen von result.
$stmt->bind_result(self::$id, self::$user, self::$vornamen, self::$nachnamen);
$stmt->fetch();
if ($stmt->num_rows == 1) {
$url= 'https://'.urlencode(self::$user).':'.urlencode($password).'@feuerwehr-bs.net/webdav';
$headers = get_headers($url);
if($headers === false) {
Response::Get()->Message("ERROR: Not possible to connect to WebDAV Url: https://feuerwehr-bs.net/webdav");
// THROW
return;
}
$returnCode= substr($headers[0], 9, 3);
if (substr($returnCode, 0, 1) === '2') {
// Passwort ist korrekt!
// XSS-Schutz, denn eventuell wird der Wert gedruckt
self::$id = preg_replace("/[^0-9]+/", "", self::$id);
self::$user = preg_replace("/[^a-zA-Z0-9_\-]+/", "", self::$user);
// Generiere einen Hash aus dem Passwort mit zufälligem Salt und speichere ihn in der Datenbank
// Question: Why?
$hash = Login::GenHash($password, Login::GenSalt(), DESIRED_ITERATIONS);
Db::Get()->query("REPLACE INTO sys_iservhashes(ID, Hash) VALUES ('".self::$id."', '".$hash."')");
// Login erfolgreich.
self::encodeJWT();
self::$loggedIn = true;
Response::Get()->Message("Login erfolgreich!");
return;
} else if ($returnCode === "401") {
// Passwort ist nicht korrekt
Response::Get()->Message("Das Passwort ist vermutlich nicht korrekt. Bitte erneut probieren oder an Nils wenden.");
} else if ($returnCode === "503") {
// Passwort ist nicht korrekt
Response::Get()->Message("IServ verweigert im Moment die Anmeldung. Vermutlich gab es temporär zu viele falsche Passworteingaben "
."(auch von anderen Nutzern). Bitte erst in 10 Minuten neu probieren!");
} else {
// Unbekannter Fehler
Response::Get()->Code(500)->Message("Anmeldung konnte aus unbekannten Gründen nicht durchgeführt werden. Fehlercode (bitte an Nils senden): ".$returnCode);
}
} else {
Response::Get()->Message("Benutzername inkorrekt: ".$user);
} }
if (Db::Get()->error!="") {
Response::Get()->Message("Mysql error: ".Db::Get()->error);
}
return;
}
}

411
lib_new/60_resources.php Normal file
View File

@ -0,0 +1,411 @@
<?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;
}
}

92
lib_new/61_links.php Normal file
View File

@ -0,0 +1,92 @@
<?php #lib/20_routes.php
namespace Links;
abstract class Link {
protected static $instances = [];
protected $leftIndex = [];
protected $rightIndex = [];
public static function Get() {
$class = get_called_class();
if (!isset($instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
private function __construct() {}
public function Table() {return $this->names["table"];}
public function Short() {return $this->names["short"];}
public function TableWithShort() {return $this->names["table"]." ".$this->names["short"];}
public function OtherResourceClass($callerClass) {
$callFromRight = $callerClass=="Resources\\".$this->definitions["right"]["resourceClass"];
return $this->definitions[$callFromRight ? "left" : "right"]["resourceClass"];
}
public function Refs($id, $from, $callerList) {
if ($from==$this->definitions["right"]["ident"]) {
// We come from right
if (!isset($this->rightIndex[$id])) {
$this->load("left", $id, $this->rightIndex, $callerList);
}
return $this->rightIndex[$id];
} else {
// We come from left
if (!isset($this->leftIndex[$id])) {
$this->load("right", $id, $this->leftIndex, $callerList);
}
return $this->leftIndex[$id];
} }
protected function load($to, $id, &$index, $callerList) {
$index[$id] = [];
$from = "left" == $to ? "right" : "left";
$fromField = $this->definitions[$from]["ident"];
$toField = $this->definitions[$to]["ident"];
$toClass = "\\Resources\\".$this->definitions[$to]["resourceClass"];
$qry = "SELECT ".$toField." FROM ".$this->Table()." WHERE ".$fromField." = ?";
if ($stmt = \DB::Get()->prepare($qry)) {
if (!is_null($id)) {
$stmt->bind_param("s", $id);
}
if ($stmt->execute()) {
$res = $stmt->get_result();
while ($row = $res->fetch_assoc()) {
$index[$id][] = $toClass::Get()->Ref($row[$toField], $callerList);
}
return;
} }
\Response::Get()->DbError();
}
public function Unset($leftid, $rightid) {
$qry = "DELETE FROM ".$this->Table()." WHERE ".$this->definitions["left"]["ident"]." = ? AND ".$this->definitions["right"]["ident"]." = ?";
if ($stmt = \DB::Get()->prepare($qry)) {
$stmt->bind_param("ss", $leftid, $rightid);
if ($stmt->execute()) {
if (1==$stmt->affected_rows) {
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 Set($leftid, $rightid) {
$qry = "INSERT INTO ".$this->Table()." (".$this->definitions["left"]["ident"].",".$this->definitions["right"]["ident"].") VALUES (?, ?)";
if ($stmt = \DB::Get()->prepare($qry)) {
$stmt->bind_param("ss", $leftid, $rightid);
if ($stmt->execute()) {
return true;
} }
\Response::Get()->DbError();
return false;
}
}

161
lib_new/70_routes.php Normal file
View File

@ -0,0 +1,161 @@
<?php
namespace Routes;
/**************************************************/
/********************* Route **********************/
/**************************************************/
abstract class Route {
public function __construct() {}
public function Answer() {
$subroute = \Request::Subroute();
if (is_null($subroute)) {
switch (\Request::Method()) {
case "OPTIONS":
return $this->info();
case "HEAD":
$id = \Request::ID();
return $this->restrictFilter($id) && $this->head($id);
case "GET":
$id = \Request::ID();
if (is_null($id)) {
return $this->restrictFilter() && $this->head() && $this->getAll();
} else {
$json = [];
return $this->head($id) && $this->getOne($id, $json) && $this->checkOutput($id, $json);
}
case "POST":
$json = \Request::Input("content");
return $this->checkInput("insert", null, $json) && $this->insert($json) && $this->head();
case "PATCH":
$json = \Request::Input("content");
$id = \Request::ID();
return $this->checkInput("update", $id, $json) && $this->update($id, $json) && $this->head();
case "DELETE":
$id = \Request::ID();
return $this->checkInput("remove", $id) && $this->remove($id) && $this->head();
default:
\Response::Get()->NotImplemented();
return false;
}
} else {
$id = \Request::ID();
return $this->answerSub($subroute, $id) && $this->head($id) && $this->get($id) && $this->checkOutput($id, );
} }
abstract public static function Rights();
protected function info() {
$class = get_called_class();
\Response::Get()->Good()->Json("rights", $class::Rights()->Export());
return true;
}
protected function answerSub($subroute, $id) {
$class = "\\Resources\\".$this->resource;
$answerFnc = "answer_".$class::Get()->Table()."_".$subroute;
if (method_exists($this, $answerFnc)) {
$subid = \Request::SubID();
return $this->$answerFnc($id, $subid);
} else if (method_exists($this, "answer_Files") && $class::Get()->HasFile($subroute)) {
return $this->answer_Files($this->resource, $id, $subroute);
}
\Response::Get()->SubRouteNotExisting($subroute);
return false;
}
protected function head($id = null) {
$class = "\\Resources\\".$this->resource;
\Response::Get()->Good()->Etag($class::Get()->Checksum($id));
return true; // Needed for &&-chain-call
}
protected function getOne($id, &$json) {
$class = "\\Resources\\".$this->resource;
$resource = $class::Get()->Ref($id);
if ($resource->Load()) {
$json = $resource->Json();
return true;
}
\Response::Get()->NotFound();
return false;
}
protected function getAll() {
$class = "\\Resources\\".$this->resource;
$list = $class::Get()->RefAll();
$json = [];
foreach ($list as $resource) {
$json[] = $resource->Json();
}
\Response::Get()->Good()->Content($json);
return true;
\Response::Get()->NotFound();
return false;
}
protected function insert($json) {
$class = "\\Resources\\".$this->resource;
$newid = null;
if ($class::Get()->Insert($json, $newid)) {
\Response::Get()->Inserted($this->resource)->Content($class::Get()->Ref($newid)->Json());
return true;
}
return false;
}
protected function update($id, $json) {
$class = "\\Resources\\".$this->resource;
$resource = $class::Get()->Ref($id);
if ($resource->Load()) {
$resource->Patch($json);
if ($resource->Store()) {
\Response::Get()->Updated($this->resource)->Content($resource->Json());
return true;
} }
return false;
}
protected function remove($id) {
$class = "\\Resources\\".$this->resource;
if ($class::Get()->Remove($id)) {
\Response::Get()->Deleted($this->resource);
return true;
}
return false;
}
protected function restrictFilter() {
if (get_called_class()::Rights()->RestrictFilter()) {
return true;
}
\Response::Get()->IllegalQuery();
return false;
}
protected function checkOutput($id, $json) {
if (get_called_class()::Rights()->CheckInput("get", $id, $json)) {
\Response::Get()->Good()->Content($json);
return true;
}
\Response::Get()->IllegalQuery();
return false;
}
protected function checkInput($action, $id = null, $json = []) {
if (is_null($id) && in_array($action, ["update", "remove"])) {
\Response::Get()->MissingID();
return false;
} else if ((is_null($json) || empty($json)) && in_array($action, ["insert", "update"])) {
\Response::Get()->MissingContent();
return false;
} else if (!get_called_class()::Rights()->CheckInput($action, $id, $json)) {
\Response::Get()->IllegalInput();
return false;
}
return true;
}
}

303
lib_new/71_rights.php Normal file
View File

@ -0,0 +1,303 @@
<?php #lib/20_routes.php
/**************************************************/
/********************* Route **********************/
/**************************************************/
class Condition
{
protected $mode = "and";
protected $list = [];
protected $subs = [];
public function __construct($mode = "and") {$this->mode = $mode;}
public function Add($field, $value, $table = null) {
$this->list[] = ["field" => $field, "value" => $value, "table" => $table];
return $this;
}
public function Sub(Condition $condition) {
$this->subs[] = $condition;
return $this;
}
private function checkValue($is, $should) {
if (is_array($is)) {
if (array_key_exists("ID", $is)) {
return $should==$is["ID"];
} else {
foreach ($is as $entry) {
if ($this->checkValue($entry, $should)) {
return true;
} }
return false;
}
return in_array($should, $is);
} else {
return $should==$is;
} }
public function CheckInput($json) {
if ($this->mode=="and") {
foreach ($this->list as $cond) {
if (!$this->checkValue($json[$cond["field"]], $cond["value"])) {
return false;
} }
foreach ($this->subs as $sub) {
if (!$sub->CheckInput($json)) {
return false;
} }
return true;
} else if ($this->mode=="or") {
foreach ($this->list as $cond) {
if ($this->checkValue($json[$cond["field"]], $cond["value"])) {
return true;
} }
foreach ($this->subs as $sub) {
if ($sub->CheckInput($json)) {
return true;
} }
return false;
}
return false; // Failsafe if mode was misconfigured
}
public function GetFilterCondition() {
$conditionList = new \Filter\ConditionList($this->mode);
foreach ($this->list as $cond) {
$conditionList->Add(new \Filter\Condition($cond["field"], "=", $cond["value"], $cond["table"]));
}
foreach ($this->subs as $sub) {
$conditionList->Add($sub->GetFilterCondition());
}
return $conditionList;
}
public function Export() {
if (sizeof($this->list) + sizeof($this->subs) == 1) {
return empty($this->list) ? $this->subs[0]->Export() : $this->list[0];
}
$ret = [
"mode" => $this->mode,
"conditions" => $this->list
];
foreach ($this->subs as &$condition) {
$ret["conditions"][] = $condition->Export();
}
return $ret;
}
public static function Make($mode = "and") {
return (new self($mode));
}
}
class Restriction
{
protected $condition = null;
protected $limits = [];
public function AddCondition(\Condition $condition) {
if (is_null($this->condition)) {
$this->condition = \Condition::Make("or");
}
$this->condition->Sub($condition);
return $this;
}
public function AddLimits($fields) {
if (is_array($fields)) {
foreach ($fields as $field) {
$this->limits[] = $field;
}
} else {
$this->limits[] = $fields;
}
return $this;
}
public function CheckInput($id = null, $json = []) {
// Check limits
if (!empty($json) && !empty($this->limits)) {
foreach ($json as $field => $value) {
if (!in_array($field, $this->limits)) {
return false;
} } }
// Check condition
return is_null($this->condition) || $this->condition->CheckInput(array_merge(["ID" => $id], $json));
}
public function GetFilterCondition() {
return is_null($this->condition) ? null : $this->condition->GetFilterCondition();
}
public function Export() {
return [
"limits" => $this->limits,
"condition" => is_null($this->condition) ? null : $this->condition->Export()
];
}
}
class Right
{
protected $requiredRight = null;
protected $restriction = null;
protected $allow = [];
public function __construct($requiredRight = null) {
$this->requiredRight = $requiredRight;
}
public function IsApplicable() {
return is_null($this->requiredRight) || \Login::HasRight($this->requiredRight);
}
public function IsAdmin() {
return sizeof($this->allow)==6 && is_null($this->restriction);
}
public function Allow($allow = "readonly") {
switch ($allow) {
case "admin":
$this->allow = ["get", "update", "insert", "remove", "upload", "erase"];
break;
case "nodelete":
$this->allow = ["get", "update", "insert", "upload"];
break;
case "justdata":
$this->allow = ["get", "update", "insert", "remove"];
break;
case "update":
$this->allow = ["get", "update"];
break;
case "readonly":
$this->allow = ["get"];
break;
default:
$this->allow = [];
}
return $this;
}
public function Require(\Condition $condition) {
if (is_null($this->restriction)) {
$this->restriction = new \Restriction();
}
$this->restriction->AddCondition($condition);
return $this;
}
public function Limit($fields) {
if (is_null($this->restriction)) {
$this->restriction = new \Restriction();
}
$this->restriction->AddLimits($fields);
return $this;
}
public function GetAllowed() {
return $this->allow;
}
public function GetRestriction() {
return $this->restriction;
}
public static function Make($requiredRight = null) {
return (new self($requiredRight));
}
public static function AllowSelf() {
return self::Make()->Allow("readonly")->Require(\Condition::Make()->Add("ID", \Login::ID()));
}
public static function AllowUpdateSelf() {
return self::Make()->Allow("update")->Require(\Condition::Make()->Add("ID", \Login::ID()));
}
}
class Rights {
// false == not allowed
// array with refs to restrictions [a ... n] == restrictions apply
// true == allowed without restrictions
protected $actions = [
"get" => false,
"update" => false,
"insert" => false,
"remove" => false,
"upload" => false,
"erase" => false
];
public function __construct() {}
public function Add($right) {
// If user already has admin rights or does not posess this right, skip
if ($right->IsApplicable()) {
$restriction = $right->GetRestriction();
// Parse the right for every action
foreach ($right->GetAllowed() as $action) {
if (is_null($restriction)) {
// No restriction in the added right, so just overwrite current restrictions
$this->actions[$action] = true;
} else if (is_array($this->actions[$action])) {
// Restrictions applied before (or nothing was allowed) and in the new one, so add new restrictions
$this->actions[$action][] = $restriction;
} else if ($this->actions[$action]===false) {
// Not allowed before, but now restrictions apply
$this->actions[$action] = [$restriction];
} } }
return $this;
}
public function CheckInput($action, $id = null, $json = []) {
if (!is_array($this->actions[$action])) {
return $this->actions[$action];
}
// At this point, some but not everything is allowed for this user and this action, so check more carefully
foreach ($this->actions[$action] as $restriction) {
if ($restriction->CheckInput($id, $json)) {
return true;
} }
return false;
}
public function RestrictFilter() {
if (is_array($this->actions["get"])) {
$restrictions = new \Filter\ConditionList("or");
foreach ($this->actions["get"] as $restriction) {
$filterCondition = $restriction->GetFilterCondition();
if (!is_null($filterCondition)) {
$restrictions->Add($filterCondition);
}
}
\Filter\Filter::Restrict($restrictions);
return true;
} else {
return $this->actions["get"];
} }
public function Export() {
$ret = [];
foreach ($this->actions as $action => $restrictions) {
if (is_array($restrictions)) {
$ret[$action] = [];
foreach ($restrictions as $restriction) {
$ret[$action][] = $restriction->Export();
}
} else {
$ret[$action] = $restrictions;
} }
return $ret;
}
public static function Make() {
return (new self())->Add(\Right::Make("ADMIN")->Allow("admin"));
}
}

444
lib_new/80_printer.php Normal file
View File

@ -0,0 +1,444 @@
<?php #ajax/print.php
class Printer
{
private $page;
private $responseCode = 500;
private $prints = [];
private $html = "";
public function __construct() {
$this->page = $this->man->Route();
$qry = "SELECT l.ID,l.Bezeichnung,l.Hauptblock,b.Css,b.Breite,b.Höhe FROM sys_print_layouts l "
."LEFT JOIN sys_print_blocks b ON l.Hauptblock=b.ID WHERE Seite=? ORDER BY l.Bevorzugt DESC, l.Bezeichnung ASC";
if ($stmt = Db::Get()->prepare($qry)) {
$stmt->bind_param("s", $this->page);
$stmt->execute();
$res = $stmt->get_result();
while ($row = $res->fetch_assoc()) {
$this->prints[] = [
"ID" => strval($row["ID"]),
"Bezeichnung" => $row["Bezeichnung"],
"Hauptblock" => $row["Hauptblock"],
"Css" => $row["Css"],
"Breite" => $row["Breite"],
"Höhe" => $row["Höhe"],
];
} } }
public function GetPrints() {
return $this->prints;
}
public static function Print($printID, $json) {
return "<div>Not yet implemented</div>";
}
public function CreateHtml($printID) {
// Find requested layout
$layout = null;
foreach ($this->prints as $key => $print) {
if (strval($printID)==$print["ID"]) {
$layout = $print;
break;
} }
// Breakoff if layout doesn't exist
if (null == $layout) {
$this->responseCode = 400;
return;
}
// Get content
$content = $this->man->Output()["content"];
$block = [
"ID" => $layout["Hauptblock"],
"Bezeichnung" => $layout["Bezeichnung"],
"Css" => $layout["Css"],
"Breite" => $layout["Breite"],
"Höhe" => $layout["Höhe"],
"Links" => 0,
"Oben" => 0
];
$entries = $this->prepareEntries($content);
$this->printBlock($block, $entries);
$this->html.= $this->joinHtml($entries);
$this->responseCode = 200;
}
public function GetHtml() {
return $this->html;
}
public function ResponseCode() {
return $this->responseCode;
}
/******************** Private functions *************************/
private function prepareEntries($content) {
$ret = [];
$selectionExists = sizeof($this->man->selected)>0;
foreach ($content as $group) {
foreach ($group["ENTRIES"] as $entry) {
if (!$selectionExists || in_array($entry["ID"], $this->man->selected)) {
$ret[$entry["ID"]] = [
"html" => "",
"floating" => [],
"content" => $entry,
];
} } }
return $ret;
}
private function joinHtml($entries) {
$ret = "";
foreach ($entries as $entry) {
$ret.= $entry["html"];
}
return $ret;
}
private function printBlock($block, &$entries) {
// Initialize block
$div = "<div name='".$block["Bezeichnung"]."' style='".$block["Css"]
."width:".($block["Breite"]/10)."mm;height:".($block["Höhe"]/10)."mm;left:".($block["Links"]/10)."mm;top:".($block["Oben"]/10)."mm;'>";
foreach ($entries as &$entry) {
$entry["html"].= $div;
}
unset($entry); // $entry was used as a reference, so it needs to be unset
// Fetch sub-blocks
$qry = "SELECT spb.*,sph.Links,sph.Oben FROM sys_print_hierarchy sph LEFT JOIN sys_print_blocks spb ON spb.ID=sph.Kindblock WHERE sph.Block=".$block["ID"];
$res = Db::Get()->query($qry);
while ($subblock = $res->fetch_assoc()) {
$this->printBlock($subblock, $entries);
}
// Fetch block elements
$elements = [];
if ($res = Db::Get()->query("SELECT * FROM sys_print_elements WHERE Block=".$block["ID"]." ORDER BY Reihenfolge ASC")) {
while ($element = $res->fetch_assoc()) {
$this->printEntryElement($element, $entries);
//$elements[] = $row;
} }
// Finalize block
foreach ($entries as &$entry) {
$entry["html"].= "</div>";
}
unset($entry);
}
private function printEntryElement($element, &$entries) {
foreach ($entries as &$entry) {
$do_print = false;
$cond = explode("|", $element["Bedingung"]);
if ($element["Parameter"]=="") {
$element["Parameter"] = "{}";
}
$params = json_decode($element["Parameter"], true);
switch ($cond[0]) {
case null:
case "":
case "0": //Print always
$do_print = true;
//echo " - Print always";
break;
case "1": //Field has to be NOT null, not false and not empty text
//echo " - Print if NOT null/false/empty";
if (sizeof($cond)>1 && ($this->getContent($entry["content"], $cond[1])!==null && (
$this->getContent($entry["content"], $cond[1])!==false || $this->getContent($entry["content"], $cond[1], true)!=""
) ) ) {
$do_print = true;
}
break;
case "2": //Field has to be null, false or empty text
//echo " - Print if null/false/empty";
if (sizeof($cond)>1 && ($this->getContent($entry["content"], $cond[1])===null || $this->getContent($entry["content"], $cond[1])===false || $this->getContent($entry["content"], $cond[1], true)==""))
$do_print = true;
break;
case "3": //Field has to be value (second parameter) or include value if is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes value ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1]);
$arr = is_array($result);
$do_print = ($arr && in_array($cond[2], $result)) || (!$arr && $cond[2]==$result);
}
break;
case "4": //Field has to be NOT value (second parameter) or DONT include value if is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes NOT value ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1]);
$arr = is_array($result);
$do_print = ($arr && !in_array($cond[2], $result)) || (!$arr && $cond[2]!=$result);
}
break;
case "5": //Field has to be text (second parameter) or include text if it is link
if (sizeof($cond)>2) {
//echo " - Print if has/includes text ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1], 1);
$testtexts = explode(",", $cond[2]);
if (is_array($result)) {
$do_print = true;
foreach ($testtexts as $testtext) {
if (!in_array($testtext, $result)) {
$do_print = false;
break;
} }
} else {
$do_print = $cond[2]==$result;
} }
break;
case "6": //Field has to be NOT text (second parameter) or DONT include text if it is link
if (sizeof($cond)>2) {
// UNTESTED
//echo " - Print if has/includes NOT text ".$cond[2];
$result = $this->getContent($entry["content"], $cond[1], 1);
$testtexts = explode(",", $cond[2]);
if (is_array($result)) {
$do_print = true;
foreach ($testtexts as $testtext) {
if (in_array($testtext, $result)) {
$do_print = false;
break;
} }
} else {
$do_print = $cond[2]!=$result;
} }
break;
}
$print_count = 0;
if ($do_print) {
$print_count = $params["repeat"] ?? 1;
}
while ($print_count>0) {
//echo " - Printing";
$print_count--;
$border = array();
$border["top"] = isset($params["border-top"]) ? $params["border-top"] : (isset($params["border"]) ? $params["border"] : "0");
$border["bottom"] = isset($params["border-bottom"]) ? $params["border-bottom"] : (isset($params["border"]) ? $params["border"] : "0");
$border["left"] = isset($params["border-left"]) ? $params["border-left"] : (isset($params["border"]) ? $params["border"] : "0");
$border["right"] = isset($params["border-right"]) ? $params["border-right"] : (isset($params["border"]) ? $params["border"] : "0");
$border["color"] = isset($params["border-color"]) ? $params["border-color"] : "#000000";
$border["string"] = "";
if ($border["left"]!="0") {
$border["string"].= "border-left: 1px solid ".$border["color"]."; ";
}
if ($border["right"]!="0") {
$border["string"].= "border-right: 1px solid ".$border["color"]."; ";
}
if ($border["top"]!="0") {
$border["string"].= "border-top: 1px solid ".$border["color"]."; ";
}
if ($border["bottom"]!="0") {
$border["string"].= "border-bottom: 1px solid ".$border["color"]."; ";
}
/*if (isset($params["border"])) {
$border["string"] = "border: 1px solid ".$border["color"].";";
}*/
$cnt = $element["Inhalt"];
$fields = array();
if (preg_match_all("/#([^#\[\]]+)(?:\[(\d*):(\d*)\])?#/", $cnt, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$replace = "";
$fields[] = $match[1];
$replace = $this->getContent($entry["content"], $match[1], 1);
if (sizeof($match)>3) {
if ($match[3]!="") {
$replace = substr($replace, 0, intval($match[3]));
}
if ($match[2]!="") {
$replace = substr($replace, intval($match[2]));
}
}
$cnt = str_replace($match[0], $replace, $cnt);
}
}
$left = $element["Links"]/10;
$top = $element["Oben"]/10;
$width = $element["Breite"]/10;
if ($width == 0) {
$width = 10;
}
$height = $element["Höhe"]/10;
if ($height == 0) {
$height = 10;
}
$this->moveIfOverlap($entry["floating"], $left, $top, $width, $height, isset($params["float"]) ? $params["float"] : "none");
$position = "position:absolute;";
if (isset($params["position"])) {
$position = "position:".$params["position"].";";
}
$position.= "z-index:".$element["Reihenfolge"].";";
switch ($element["Art"]) {
case "img":
if (isset($params["aspect"]) && $params["aspect"]=="keep") {
$imgsize = "max-width: ".$width."mm; max-height: ".$height."mm; width: auto; height: auto; ";
}
if (isset($params["aspect"]) && $params["aspect"]=="height") {
$imgsize = "height: ".$height."mm; width: auto; ";
} else {
$imgsize = "width: ".$width."mm; height: ".$height."mm; ";
}
$path = $cnt;
if (count($fields)) {
$path = $cnt;
}
//if (file_exists($path)) {
$entry["html"].= "<img src='".$path."' style='".$position." left: ".$left."mm; top: ".$top."mm; ".$imgsize."' />";
//}
break;
case "text":
$rotate = "";
if (isset($params["rotate"]) && $params["rotate"]=="1") {
$rotate = "transform: rotate(-90deg); ";
$left = "calc(".($left + ($height - $width)/2)."mm - ".$border["left"]."px); ";
$top = "calc(".($top + ($width - $height)/2)."mm - ".$border["top"]."px); ";
} else {
$left.= "mm; ";
$top.= "mm; ";
}
$font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
$font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
if (isset($params["weight"])) {
$font.= "font-weight: ".$params["weight"]."; ";
}
if (isset($params["align"])) {
$font.= "text-align: ".$params["align"]."; ";
}
if (isset($params["color"])) {
$font.= "color: ".$params["color"]."; ";
}
$entry["html"].= "<span style='".$position."overflow:hidden;white-space:nowrap;left:".$left."top:".$top."width:".$width."mm;height:"
.$height."mm;".$font.$border["string"].$rotate."'>".$cnt."</span>";
break;
case "circle":
$left.= "mm; ";
$top.= "mm; ";
$display = "display: inline-flex; align-items: center; justify-content: space-around; ";
$font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
$font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
if (isset($params["weight"])) {
$font.= "font-weight: ".$params["weight"]."; ";
}
if (isset($params["color"])) {
$font.= "color: ".$params["color"]."; ";
}
$font.= "text-align: center; ";
$background = isset($params["background"]) ? "background-color: ".$params["background"].";" : "";
$entry["html"].= "<span style='".$position."left:".$left."top:".$top."width:".$width."mm;height:".$height."mm;".$display.$background.$font
.$border["string"]."border-radius:50%;'>".$cnt."</span>";
break;
case "quartercircle":
$left.= "mm; ";
$top.= "mm; ";
$background = isset($params["background"]) ? "background-color: ".$params["background"].";" : "";
$border_radius = isset($params["corner"]) ? "border-".$params["corner"]."-radius:100%;" : "border-top-left-radius:100%;";
$entry["html"].= "<span style='".$position."left:".$left."top:".$top."width:".$width."mm;height:".$height."mm;"/*.$display*/.$background/*.$font*/
.$border_radius."'>".$cnt."</span>";
break;
} } }
unset($entry);
}
private function getTextFromReference($ref, $getText) {
if (1==$getText && array_key_exists("KÜRZEL", $ref)) {
return $ref["KÜRZEL"];
} elseif (1<=$getText && array_key_exists("NAME", $ref)) {
return $ref["NAME"];
} elseif (array_key_exists("ID", $ref)) {
return $ref["ID"];
}
return "";
}
// $entry Content-array of entry ([ID, MAIN, SUB])
// $key Requested field
// $getText 0 = ID is sufficient, 1 = Kürzel (prio.) or Name desired, 2 = Name desired
private function getContent($entry, $key, $getText = 0) {
// Check if requested field are actually nested keys separated by dots
$dotPos = strpos($key, ".");
if ($dotPos!==false) {
$subkey = substr($key, 0, $dotPos);
if (array_key_exists($subkey, $entry)) {
return $this->getContent($entry[$subkey], substr($key, $dotPos+1, strlen($key)-$dotPos-1));
} }
// Check if requested field exists
if (array_key_exists($key, $entry)) {
// Check if requested field is an array (which means it was selected through a foreign reference or link table)
if (is_array($entry[$key])) {
// Check if array is size 0 -> This means an unset reference, i.e. NULL
if (0==sizeof($entry[$key])) {
return null;
} elseif (array_key_exists("ID", $entry[$key])) { // Check if requested field is a normal foreign reference -> It will have the ID field then.
// Foreign reference with ID, NAME and maybe KÜRZEL
return $this->getTextFromReference($entry[$key], $getText);
} else { // If not, we assume to have an sequential array which means a n:m-link-table
$ret = [];
foreach ($entry[$key] as $i => $ref) {
$ret[] = $this->getTextFromReference($ref, $getText);
}
return $ret;
}
} else { // Requested field is a normal value
return $entry[$key];
}
} elseif (array_key_exists("MAIN", $entry)) { // If we are in the root structure of a content object, we will have sections MAIN and SUB and check those recursively
// Search the MAIN-array for field
$ret = $this->getContent($entry["MAIN"], $key, $getText);
// If only the field name was returned check SUB-array if exists
if ($key==$ret && array_key_exists("SUB", $entry)) {
$ret = $this->getContent($entry["SUB"], $key, $getText);
}
return $ret;
} else {
// Nothing was found, we return the field name
return $key;
} }
private function moveIfOverlap(&$floating, &$left, &$top, $width, $height, $dir = "none", $lastdir = "none") {
$moved = false;
foreach ($floating as $key => $float) {
if (
$left < $float["right"] &&
$left+$width > $float["left"] &&
$top < $float["bottom"] &&
$top+$height > $float["top"]
) {
if ($float["dir"]=="left" && $lastdir!="right") {
$left = $float["right"];
} else if ($float["dir"]=="right" && $lastdir!="left") {
$left = $float["left"] - $width;
} else if ($float["dir"]=="up" && $lastdir!="down") {
$top = $float["bottom"];
} else if ($float["dir"]=="down" && $lastdir!="up") {
$top = $float["top"] - $height;
}
$lastdir = $float["dir"];
$moved = true;
}
}
if ($moved) {
$this->moveIfOverlap($floating, $left, $top, $width, $height, $dir, $lastdir);
} else if ($dir!="none") {
$floating[] = array(
"left" => $left,
"right" => $left + $width,
"top" => $top,
"bottom" => $top + $height,
"dir" => $dir
);
} }
}

62
lib_new/99_manager.php Normal file
View File

@ -0,0 +1,62 @@
<?php #lib/99_manager.php
// Missing:
// - Error handling
// - Proper response codes
class Manager
{
private static $routes = [
//"Termine",
//"Dienstpläne",
"Personal",
//"Prozesse",
//"Spinde",
//"Einsätze",
//"Anwesenheiten",
//"Fahrzeuge"
];
private function __construct() {}
private function __destruct() {}
public static function Answer() {
try {
Request::ParseInput();
Login::Login();
if (Request::IsLogout()) {
// Logout detected
Login::Logout();
Response::Get()->Code(200)->Message("Logout erkannt!");
} else if (!Login::LoggedIn()) {
// Login is necessary
Response::Get()->Code(401);
} else {
// Include routes. Needed also for /-Request
foreach (self::$routes as $route => $filename) {
$file = "routes/".str_replace(["ä", "ö", "ü", "ß"], ["ae", "oe", "ue", "ss"], $filename).".php";
if (file_exists($file)) {
include $file;
} }
if (Request::IsRoot()) {
// POST (Login) or GET/OPTIONS... i dont care, all will result in the same
$access = [];
foreach (self::$routes as $route => $filename) {
$class = "\\Routes\\".$route;
$access[$route] = $class::Rights();
}
Response::Get()->Code(200)->Content($access)->ETag(hash('sha256', json_encode($access)));
} else if (!in_array(Request::Route(), self::$routes)) {
Response::Get()->Code(404)->Message("Route not found");
} else {
// This is the good path. We can try to answer the request
$class = "\\Routes\\".Request::Route();
(new $class)->Answer();
} }
} catch (\Throwable $e) { // For PHP 7
Response::Get()->Code(500)->Message("<h5>Caught throwable:</h5>".$e);
} catch (\Exception $e) { // For PHP 5
Response::Get()->Code(500)->Message("<h5>Caught exception:</h5>".$e);
} } }

View File

@ -0,0 +1,337 @@
<?php #lib/20_routes.php
namespace Resources;
class Element {
protected $parent;
protected $id;
protected $chksum = "";
protected $fields = [];
protected $keys = [];
protected $links = [];
public function __construct($parent, $data) {
$this->parent = $parent;
$this->id = $data["ID"];
$definitions = $parent->Definitions();
$this->fields = array_combine(array_keys($definitions["fields"]), array_column($definitions["fields"], "value"));
$this->keys = array_combine(array_keys($definitions["keys"]), array_column($definitions["keys"], "reference"));
$this->links = array_combine(array_keys($definitions["links"]), array_column($definitions["links"], "referenceList"));
$this->Patch($data);
}
public function ID() {return $this->id;}
public function Checksum() {return $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_array($data[$key]) ? $class::Get()->RefByData($data[$key]) : $class::Get()->RefById($data[$key]);
} }
unset($reference); // If removed, $reference would still point to last element of keys
foreach ($this->links as $link => &$refList) {
if (isset($data[$link])) {
$class = "\\Links\\".$definitions["links"][$link]["linkClass"];
$refList = $class::Get()->RefList($this->id, $link, $data[$link]);
} }
unset($refList); // If removed, $reference would still point to last element of keys
if (isset($data["CHKSUM"]) && !is_null($data["CHKSUM"])) {
$this->chksum = $data["CHKSUM"];
} else {
$this->updateChecksum();
}
}
public function Store() {
$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();
}
$ret = [
"ID" => $this->id,
"CHKSUM" => $this->chksum
];
foreach ($this->fields as $field => $value) {
$ret[$field] = $value;
}
foreach ($this->keys as $key => $reference) {
$ret[$key] = is_null($reference) ? null : ($depth>0 ? $reference->Json($depth-1) : $reference->ID());
}
foreach ($this->links as $link => $referenceList) {
$ids = [];
foreach ($referenceList as $reference) {
if (!is_null($reference)) {
$ids[] = $depth>0 ? $reference->Json($depth-1) : $reference->ID();
}
}
$ret[$link] = $ids;
}
return $ret;
}
}
abstract class Handler {
protected static $instances = [];
protected $liste = [];
public static function Get() {
$class = get_called_class();
if (!isset($instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
private function __construct() {}
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 RefById($id) {
if (!isset($this->liste[$id]) ) {
$this->liste[$id] = $this->load($id); // Might be null, but we will buffer the result anyway
}
return $this->liste[$id];
}
public function RefByData($data) {
if (!isset($data["ID"])) {
return null;
}
if (!isset($this->liste[$data["ID"]])) {
$this->liste[$data["ID"]] = new Element($this, $data);
}
return $this->liste[$data["ID"]];
}
public function RefAll() {
$this->liste = $this->load();
return $this->liste;
}
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()) {
$chksum = $stmt->get_result()->fetch_assoc()["cs"];
return $chksum;
} }
\Response::Get()->DbError();
return null;
}
public function CalcChecksum($data) {
$concat = "";
foreach ($this->definitions as $definition) {
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->liste[$id])) {
return $this->liste[$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[] = "?";
}
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 load($id = null) {
$fields = [$this->Short().".*"];
$joins = [];
foreach ($this->definitions["keys"] as $key => $definition) {
$class = "\\Resources\\".$definition["resourceClass"];
$joins[] = \DB::LeftJoin($class::Get()->Table(), $class::Get()->Short(), "ID", $this->Short(), $key);
$fields[] = $key;
}
foreach ($this->definitions["links"] as $link => $definition) {
$resClass = "\\Resources\\".$definition["resourceClass"];
$lnkClass = "\\Links\\".$definition["linkClass"];
$joins[] = \DB::LeftJoin($lnkClass::Get()->Table(), $lnkClass::Get()->Short(), $this->Ident(), $this->Short(), "ID");
//$joins[] = \DB::LeftJoin($resClass::Get()->Table(), $resClass::Get()->Short(), "ID", $lnkClass::Get()->Short(), $link);
$fields[] = "CONCAT(',', GROUP_CONCAT(DISTINCT ".$lnkClass::Get()->Short().".".$link." SEPARATOR ','), ',') ".$link;
}
$qry = "SELECT ".implode(", ", $fields)." FROM ".$this->TableWithShort()." ".implode(" ", $joins);
if (!is_null($id)) {
$qry.= "WHERE ".$this->Short().".ID = ?";
}
if ($stmt = \DB::Get()->prepare($qry)) {
if (!is_null($id)) {
$stmt->bind_param("s", $id);
}
if ($stmt->execute()) {
$res = $stmt->get_result();
if (is_null($id)) {
$ret = [];
while ($row = $res->fetch_assoc()) {
$ret[] = new Element($this, $row);
}
return $ret;
} else {
if ($row = $res->fetch_assoc()) {
return new Element($this, $row);
}
return null;
}
} }
\Response::Get()->DbError();
return null;
}
}

226
lib_new_bak/61_links.php Normal file
View File

@ -0,0 +1,226 @@
<?php #lib/20_routes.php
namespace Links;
/*class Element {
protected $parent;
protected $left;
protected $right;
protected $fields = [];
protected $keys = [];
public function __construct($parent, $data) {
$this->parent = $parent;
$definitions = $parent->Definitions();
$classLeft = "\\Resources\\".$definitions["left"]["resourceClass"];
$this->left = $classLeft::;
foreach ($definitions["ids"] as $id => $className) {
$class = "\\Resources\\".$className;
$this->ids[$id] = isset($data[$id]) ? $class::Get()->RefByData($data[$id]) : null;
}
$this->fields = array_combine(array_keys($definitions["fields"]), array_column($definitions["fields"], "value"));
$this->keys = array_combine(array_keys($definitions["keys"]), array_column($definitions["keys"], "reference"));
$this->Patch($data);
}
public function ID($key) {return $this->ids[$key];}
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_array($data[$key]) ? $class::Get()->RefByData($data[$key]) : $class::Get()->RefById($data[$key]);
} }
unset($reference); // If removed, $reference would still point to last element of keys
}
public function Store() {
$ids = [];
$fields = [];
$values = [];
$types = "";
$definitions = $this->parent->Definitions();
if (!empty($definitions["fields"]) || !empty($definitions["keys"])) {
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"];
}
foreach ($this->ids as $id => $reference) {
$ids[] = $id." = ?";
$values[] = $reference->ID();
$types.= "s";
}
$qry = "UPDATE ".$this->parent->Table()." SET ".implode(", ", $fields)." WHERE ".implode(" AND ", $ids);
if ($stmt = \DB::Get()->prepare($qry)) {
$stmt->bind_param($types, ...$values);
if ($stmt->execute()) {
$this->parent->UpdateChecksum();
return true;
} }
\Response::Get()->DbError();
return false;
}
return true;
}
public function Json($depth = null) {
if (is_null($depth)) {
$depth = \Request::DetailDepth();
}
$ret = [];
foreach ($this->ids as $id => $reference) {
$ret[$key] = is_null($reference) ? null : ($depth>0 ? $reference->Json($depth-1) : $reference->ID());
}
foreach ($this->fields as $field => $value) {
$ret[$field] = $value;
}
foreach ($this->keys as $key => $reference) {
$ret[$key] = is_null($reference) ? null : ($depth>0 ? $reference->Json($depth-1) : $reference->ID());
}
return $ret;
}
}*/
abstract class Link {
protected static $instances = [];
protected $leftIndex = [];
protected $rightIndex = [];
public static function Get() {
$class = get_called_class();
if (!isset($instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
private function __construct() {}
public function Table() {return $this->names["table"];}
public function Short() {return $this->names["short"];}
public function TableWithShort() {return $this->names["table"]." ".$this->names["short"];}
public function RefList($id, $target, $dataOrIds = null) {
if ($target==$this->definitions["left"]["ident"]) {
// We come from right
if (!isset($this->rightIndex[$id])) {
$this->rightIndex[$id] = is_array($dataOrIds) ? $this->connect("left", $dataOrIds) : $this->load("left", $id);
}
return $this->rightIndex[$id];
} else {
// We come from left
if (!isset($this->leftIndex[$id])) {
$this->rightIndex[$id] = is_array($dataOrIds) ? $this->connect("right", $dataOrIds) : $this->load("right", $id);
}
return $this->leftIndex[$id];
}
}
public function Unset($id, $target, $targetId) {
$idIsRight = $target==$this->definitions["left"]["ident"];
$qry = "DELETE FROM ".$this->Table()." WHERE ".$this->definitions["left"]["ident"]." = ? AND ".$this->definitions["right"]["ident"]." = ?";
if ($stmt = \DB::Get()->prepare($qry)) {
if ($idIsRight) {
$stmt->bind_param("ss", $targetId, $id);
} else {
$stmt->bind_param("ss", $id, $targetId);
}
if ($stmt->execute()) {
if (1==$stmt->affected_rows) {
if ($idIsRight) {
$this->indexRight[$id] = $this->load("left", $id);
} else {
$this->indexLeft[$id] = $this->load("right", $id);
}
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 Set($id, $target, $targetId) {
$idIsRight = $target==$this->definitions["left"]["ident"];
$qry = "INSERT INTO ".$this->Table()." (".$this->definitions["left"]["ident"].",".$this->definitions["right"]["ident"].") VALUES (?, ?)";
if ($stmt = \DB::Get()->prepare($qry)) {
if ($idIsRight) {
$stmt->bind_param("ss", $targetId, $id);
} else {
$stmt->bind_param("ss", $id, $targetId);
}
if ($stmt->execute()) {
if ($idIsRight) {
$this->indexRight[$id] = $this->load("left", $id);
} else {
$this->indexLeft[$id] = $this->load("right", $id);
}
return true;
} }
\Response::Get()->DbError();
return false;
}
protected function connect($target, $dataOrIds) {
$targetClass = "\\Resources\\".$this->definitions[$target]["resourceClass"];
$ret = [];
foreach ($dataOrIds as $dataOrId) {
if (is_array($dataOrId)) {
$ret[] = $targetClass::Get()->RefByData($dataOrId);
} else {
$ret[] = $targetClass::Get()->RefById($dataOrId);
}
}
return $ret;
}
protected function load($target, $id) {
$source = "left" == $target ? "right" : "left";
$sourceField = $this->definitions[$source]["ident"];
$targetField = $this->definitions[$target]["ident"];
$targetClass = "\\Resources\\".$this->definitions[$target]["resourceClass"];
$qry = "SELECT ".$targetField." FROM ".$this->Table()." WHERE ".$sourceField." = ?";
if ($stmt = \DB::Get()->prepare($qry)) {
if (!is_null($id)) {
$stmt->bind_param("s", $id);
}
if ($stmt->execute()) {
$res = $stmt->get_result();
$ret = [];
while ($row = $res->fetch_assoc()) {
$ret[] = $targetClass::Get()->RefById($row[$targetField]);
}
return $ret;
} }
\Response::Get()->DbError();
return [];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Links;
require_once("resources/Anwesenheiten/Anwesenheit.php");
require_once("resources/Einsätze/Einsatz.php");
class Anwesenheiten_Einsätze extends \Links\Link
{
protected $names = ["table" => "Anwesenheiten_Einsätze", "short" => "ae"];
protected $definitions = [
"left" => ["ident" => "Anwesenheiten", "resourceClass" => "Anwesenheiten\\Anwesenheit"],
"right" => ["ident" => "Einsätze", "resourceClass" => "Einsätze\\Einsatz"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,20 @@
<?php
namespace Links;
require_once("resources/Anwesenheiten/Anwesenheit.php");
require_once("resources/Personal/Personal.php");
require_once("resources/Fahrzeuge/Fahrzeug.php");
class Anwesenheiten_Personal extends \Links\Link
{
protected $names = ["table" => "Anwesenheiten_Personal", "short" => "ap"];
protected $definitions = [
"left" => ["ident" => "Anwesenheiten", "resourceClass" => "Anwesenheiten\\Anwesenheit"],
"right" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"fields" => [],
"keys" => [
"Fahrzeuge" => ["resourceClass" => "Fahrzeuge\\Fahrzeug", "nullable" => true, "type" => "s"]
]
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace Links;
require_once("resources/Anwesenheiten/Anwesenheit.php");
require_once("resources/Einsätze/Einsatz.php");
class Anwesenheiten_Termine extends \Links\Link
{
protected $names = ["table" => "Anwesenheiten_Termine", "short" => "at"];
protected $definitions = [
"left" => ["ident" => "Anwesenheiten", "resourceClass" => "Anwesenheiten\\Anwesenheit"],
"right" => ["ident" => "Termine", "resourceClass" => "Termine\\Termin"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace Links;
require_once("resources/Fahrzeuge/Fahrzeug.php");
require_once("resources/Personal/Personal.php");
class Fahrzeuge_Einweisungen extends \Links\Link
{
protected $names = ["table" => "Fahrzeuge_Einweisungen", "short" => "fe"];
protected $definitions = [
"left" => ["ident" => "Fahrzeuge", "resourceClass" => "Fahrzeuge\\Fahrzeug"],
"right" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php #lnk/xx_personal_lehrgänge/class.php
namespace Links;
require_once("resources/Personal/Personal.php");
require_once("resources/Struktur/Abteilung.php");
class Personal_Abteilungen extends \Links\Link
{
protected $names = ["table" => "Personal_Abteilungen", "short" => "pa"];
protected $definitions = [
"left" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"right" => ["ident" => "Abteilungen", "resourceClass" => "Struktur\\Abteilung"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php #lnk/xx_personal_lehrgänge/class.php
namespace Links;
require_once("resources/Personal/Personal.php");
require_once("resources/Struktur/Gruppe.php");
class Personal_Gruppen extends \Links\Link
{
protected $names = ["table" => "Personal_Gruppen", "short" => "pg"];
protected $definitions = [
"left" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"right" => ["ident" => "Gruppen", "resourceClass" => "Struktur\\Gruppe"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace Links;
require_once("resources/Personal/Personal.php");
require_once("resources/Lehrgaenge/Lehrgang.php");
class Personal_Lehrgänge extends \Links\Link
{
protected $names = ["table" => "Personal_Lehrgänge", "short" => "pl"];
protected $definitions = [
"left" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"right" => ["ident" => "Lehrgänge", "resourceClass" => "Lehrgänge\\Lehrgang"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php #lnk/xx_personal_lehrgänge/class.php
namespace Links;
require_once("resources/Personal/Personal.php");
require_once("resources/Struktur/Verwalter.php");
class Personal_Verwalter extends \Links\Link
{
protected $names = ["table" => "Personal_Verwalter", "short" => "pv"];
protected $definitions = [
"left" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"right" => ["ident" => "Verwalter", "resourceClass" => "Struktur\\Verwalter"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace Links;
require_once("resources/Rechte/Recht.php");
require_once("resources/Struktur/Verwalter.php");
class Rechte_Verwalter extends \Links\Link
{
protected $names = ["table" => "Rechte_Verwalter", "short" => "rv"];
protected $definitions = [
"left" => ["ident" => "Rechte", "resourceClass" => "Rechte\\Recht"],
"right" => ["ident" => "Verwalter", "resourceClass" => "Struktur\\Verwalter"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php #lnk/xx_personal_lehrgänge/class.php
namespace Links;
require_once("resources/Spinde/Spind.php");
require_once("resources/Personal/Personal.php");
class Spinde_Zuordnung extends \Links\Link
{
protected $names = ["table" => "Spinde_Zuordnung", "short" => "sz"];
protected $definitions = [
"left" => ["ident" => "Spinde", "resourceClass" => "Spinde\\Spind"],
"right" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,17 @@
<?php #lnk/xx_personal_lehrgänge/class.php
namespace Links;
require_once("resources/Personal/Personal.php");
require_once("resources/Struktur/Verwalter.php");
class Personal_Verwalter extends \Links\Link
{
protected $names = ["table" => "Personal_Verwalter", "short" => "pv"];
protected $definitions = [
"left" => ["ident" => "Personal", "resourceClass" => "Personal\\Personal"],
"right" => ["ident" => "Verwalter", "resourceClass" => "Struktur\\Verwalter"],
"fields" => [],
"keys" => []
];
}

View File

@ -0,0 +1,49 @@
<?php #lnk/anwesenheiten_einsätze/class.php
class Anwesenheiten_EinsätzeLink extends BaseLink
{
protected function insert($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Einsätze"]==null) {
return 400;
}
$qry = "INSERT INTO Anwesenheiten_Einsätze (Anwesenheiten, Einsätze) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Einsätze"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Einsatz zu Anwesenheitsliste hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Einsätze"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Anwesenheiten_Einsätze WHERE Anwesenheiten = ? AND Einsätze = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Einsätze"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Einsatz aus Anwesenheitsliste entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Anwesenheiten_Einsätze",
"table" : "Anwesenheiten_Einsätze",
"useRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"adminRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"isPage": false
}

View File

@ -0,0 +1,52 @@
<?php #lnk/anwesenheiten_personal/class.php
class Anwesenheiten_PersonalLink extends BaseLink
{
protected function insert($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Anwesenheiten_Personal (Anwesenheiten, Personal) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Personal"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal zu Anwesenheitsliste hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
//$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Anwesenheiten_Personal WHERE Anwesenheiten = ? AND Personal = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Personal"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal aus Anwesenheitsliste entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Anwesenheiten_Personal",
"table": "Anwesenheiten_Personal",
"useRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"adminRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"isPage": false
}

View File

@ -0,0 +1,52 @@
<?php #lnk/anwesenheiten_termine/class.php
class Anwesenheiten_TermineLink extends BaseLink
{
protected function insert($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Termine"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Anwesenheiten_Termine (Anwesenheiten, Termine) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Termine"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Termin zu Anwesenheitsliste hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Anwesenheiten"]==null || $ids["Termine"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Anwesenheiten_Termine WHERE Anwesenheiten = ? AND Termine = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Anwesenheiten"], $ids["Termine"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Termin aus Anwesenheitsliste entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Anwesenheiten_Termine",
"table": "Anwesenheiten_Termine",
"useRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"adminRight": ["DARF_PERSONAL_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN"],
"isPage": false
}

88
lnk/dateien/link.php Executable file
View File

@ -0,0 +1,88 @@
<?php #lnk/xx_dateien/class.php
class DateienLink extends BaseLink
{
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info, $page);
}
protected function insert($ids) {
if (isset($_FILES["file"])) {
$source = $_FILES ["file"]["tmp_name"];
$dest_dir = "upl/";
$keys = [];
$vals = [];
// ids should be [%TABLENAME => %ENTRYID, %FIELDNAME => NULL]
foreach ($ids as $path => $id) {
$dest_dir.= $path."/";
$keys[] = $path;
$vals[] = $id;
}
if (!file_exists($dest_dir)) {
mkdir($dest_dir, 0770, true);
}
$mime = mime_content_type($source);
switch ($mime) {
case "image/gif": $filename = $vals[0].".gif"; break;
case "image/jpeg": $filename = $vals[0].".jpg"; break;
case "image/png": $filename = $vals[0].".png"; break;
case "image/svg": $filename = $vals[0].".svg"; break;
default: $this->man->AddMessage("Unbekannter Dateityp"); return 400;
}
// Delete existing files
foreach (glob($dest_dir.$vals[0].".*") as $existingfile) {
unlink($existingfile);
}
if (move_uploaded_file($source, $dest_dir.$filename)) {
if ($stmt = $this->db->prepare("UPDATE `".$keys[0]."` SET `".$keys[1]."` = ? WHERE ID = ?")) {
$stmt->bind_param("si", $filename, $vals[0]);
if ($stmt->execute()) {
return 201;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
} else {
$this->man->AddMessage("File could not be moved");
return 403;
}
}
return 400;
}
protected function remove($ids) {
$dest_dir = "upl/";
$keys = [];
$vals = [];
// ids should be [%TABLENAME => %ENTRYID, %FIELDNAME => NULL]
foreach ($ids as $path => $id) {
$dest_dir.= $path."/";
$keys[] = $path;
$vals[] = $id;
}
// Delete existing files
$files = glob($dest_dir.$vals[0].".*");
if (empty($files)) {
$this->man->AddMessage("No files were found!");
}
foreach ($files as $existingfile) {
unlink($existingfile);
}
if ($stmt = $this->db->prepare("UPDATE `".$keys[0]."` SET `".$keys[1]."` = NULL WHERE ID = ?")) {
$stmt->bind_param("i", $vals[0]);
if ($stmt->execute()) {
$this->man->AddMessage("Datei erfolgreich gelöscht!");
return 200;
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
}

6
lnk/dateien/module.json Executable file
View File

@ -0,0 +1,6 @@
{
"route": "Dateien",
"useRight": "DARF_DATEIEN_VERWALTEN",
"adminRight": "DARF_DATEIEN_VERWALTEN",
"isPage": false
}

View File

@ -0,0 +1,56 @@
<?php #lnk/xx_fahrzeuge_einweisungen/class.php
class Fahrzeuge_EinweisungenLink extends BaseLink
{
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info, $page);
}
protected function insert($ids) {
if ($ids["Fahrzeuge"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Fahrzeuge_Einweisungen (Personal, Fahrzeuge) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Personal"], $ids["Fahrzeuge"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Fahrzeugeinweisung hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Fahrzeuge"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Fahrzeuge_Einweisungen WHERE Fahrzeuge = ? AND Personal = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Fahrzeuge"], $ids["Personal"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Fahrzeugeinweisung entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Fahrzeuge_Einweisungen",
"table": "Fahrzeuge_Einweisungen",
"useRight": null,
"adminRight": "DARF_FAHRZEUG_EINWEISUNGEN_VERWALTEN",
"isPage": false
}

View File

@ -0,0 +1,56 @@
<?php #lnk/xx_personal_abteilungen/class.php
class Personal_AbteilungenLink extends BaseLink
{
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info, $page);
}
protected function insert($ids) {
if ($ids["Abteilungen"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Personal_Abteilungen (Personal, Abteilungen) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Personal"], $ids["Abteilungen"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal zu Abteilung hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Abteilungen"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Personal_Abteilungen WHERE Abteilungen = ? AND Personal = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Abteilungen"], $ids["Personal"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal aus Abteilung entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Personal_Abteilungen",
"table": "Personal_Abteilungen",
"useRight": null,
"adminRight": "DARF_PERSONAL_VERWALTEN",
"isPage": false
}

56
lnk/personal_gruppen/link.php Executable file
View File

@ -0,0 +1,56 @@
<?php #lnk/xx_personal_gruppen/class.php
class Personal_GruppenLink extends BaseLink
{
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info, $page);
}
protected function insert($ids) {
if ($ids["Gruppen"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Personal_Gruppen (Personal, Gruppen) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Personal"], $ids["Gruppen"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal zu Gruppe hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Gruppen"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Personal_Gruppen WHERE Personal = ? AND Gruppen = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Personal"], $ids["Gruppen"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personal aus Gruppe entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Personal_Gruppen",
"table": "Personal_Gruppen",
"useRight": null,
"adminRight": "DARF_PERSONAL_VERWALTEN",
"isPage": false
}

View File

@ -0,0 +1,56 @@
<?php #lnk/xx_personal_lehrgänge/class.php
class Personal_LehrgängeLink extends BaseLink
{
public function __construct($database, $keyvaluestore, $manager, $info, $page) {
parent::__construct($database, $keyvaluestore, $manager, $info, $page);
}
protected function insert($ids) {
if ($ids["Lehrgänge"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "INSERT INTO Personal_Lehrgänge (Personal, Lehrgänge) VALUES (?, ?)";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Personal"], $ids["Lehrgänge"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personalausbildung hinzugefügt!");
return 201;
} else {
return 200;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
}
return 500; // Should not reach this stage
}
protected function remove($ids) {
if ($ids["Lehrgänge"]==null || $ids["Personal"]==null) {
return 400;
}
if (!$this->man->user->HasRight($this->adminRight)) {
return 403; // You shall not pass!
}
$qry = "DELETE FROM Personal_Lehrgänge WHERE Lehrgänge = ? AND Personal = ?";
if ($stmt = $this->db->prepare($qry)) {
$stmt->bind_param("ii", $ids["Lehrgänge"], $ids["Personal"]);
$stmt->execute();
if (1==$stmt->affected_rows) {
$this->man->AddMessage("Personalausbildung entfernt!");
return 200;
} else {
return 404;
}
} else {
$this->man->AddMessage("Mysql error: ".$this->db->error);
return 500;
}
}
}

View File

@ -0,0 +1,7 @@
{
"route": "Personal_Lehrgänge",
"table": "Personal_Lehrgänge",
"useRight": null,
"adminRight": "DARF_PERSONAL_VERWALTEN",
"isPage": false
}

Some files were not shown because too many files have changed in this diff Show More