diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5b835a2 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..17df4ee --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +CGIPassAuth On diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a343ccd..0000000 --- a/LICENSE +++ /dev/null @@ -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. diff --git a/README.md b/README.md deleted file mode 100644 index 5d5681f..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# App - diff --git a/bin/errors.php b/bin/__errors.php old mode 100644 new mode 100755 similarity index 100% rename from bin/errors.php rename to bin/__errors.php diff --git a/bin/navi.php b/bin/__navi.php old mode 100644 new mode 100755 similarity index 100% rename from bin/navi.php rename to bin/__navi.php diff --git a/bin/__print.php b/bin/__print.php new file mode 100755 index 0000000..b03e95b --- /dev/null +++ b/bin/__print.php @@ -0,0 +1,63 @@ + $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"]; + + +?> \ No newline at end of file diff --git a/bin/exit.php b/bin/exit.php old mode 100644 new mode 100755 index ea56bed..4b7a14c --- a/bin/exit.php +++ b/bin/exit.php @@ -1,5 +1,4 @@ close(); - -?> \ No newline at end of file +$mysqli->close(); +$redis->close(); diff --git a/bin/init.php b/bin/init.php old mode 100644 new mode 100755 index ee8821f..6f12814 --- a/bin/init.php +++ b/bin/init.php @@ -1,70 +1,25 @@ $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(); -} - -?> \ No newline at end of file +// Authenticate user and prepare input/output +$man = new Manager($mysqli, $redis, JWT_KEY); diff --git a/bin/init_new.php b/bin/init_new.php new file mode 100644 index 0000000..89280c9 --- /dev/null +++ b/bin/init_new.php @@ -0,0 +1,22 @@ + $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 = " "; + 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 "
+ Willkommen! Bitte Anmeldedaten von IServ verwenden! (vorname.nachname)
+ Sollte der Login nicht klappen, bitte versuchen ob die Zugangsdaten auf
+ feuerwehr-bs.net funktionieren und danach bei Nils melden.
+
+
+
+
+
" : "")
+ + "
";
+ html+= "
";
+ } else {
+ html+= "
";
+ }
+ 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);});
+ } }
+}
diff --git a/client/pages/fahrzeuge.js b/client/pages/fahrzeuge.js
new file mode 100644
index 0000000..6f83ab8
--- /dev/null
+++ b/client/pages/fahrzeuge.js
@@ -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 = "





" + this.Name + "
" + this.Kürzel + "



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 diff --git a/client/qr-scanner-worker.min.js.map b/client/qr-scanner-worker.min.js.map new file mode 100644 index 0000000..618e36e --- /dev/null +++ b/client/qr-scanner-worker.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"qr-scanner-worker.min.js","sources":["node_modules/jsqr-es6/src/BitMatrix.ts","node_modules/jsqr-es6/src/binarizer/index.ts","node_modules/jsqr-es6/src/decoder/decodeData/BitStream.ts","node_modules/jsqr-es6/src/decoder/decodeData/index.ts","node_modules/jsqr-es6/src/decoder/reedsolomon/GenericGFPoly.ts","node_modules/jsqr-es6/src/decoder/reedsolomon/GenericGF.ts","node_modules/jsqr-es6/src/decoder/reedsolomon/index.ts","node_modules/jsqr-es6/src/decoder/version.ts","node_modules/jsqr-es6/src/decoder/decoder.ts","node_modules/jsqr-es6/src/extractor/index.ts","node_modules/jsqr-es6/src/locator/index.ts","node_modules/jsqr-es6/src/index.ts","src/worker.ts"],"sourcesContent":["export class BitMatrix {\n public static createEmpty(width: number, height: number) {\n return new BitMatrix(new Uint8ClampedArray(width * height), width);\n }\n\n public width: number;\n public height: number;\n private data: Uint8ClampedArray;\n\n constructor(data: Uint8ClampedArray, width: number) {\n this.width = width;\n this.height = data.length / width;\n this.data = data;\n }\n\n public get(x: number, y: number): boolean {\n if (x < 0 || x >= this.width || y < 0 || y >= this.height) {\n return false;\n }\n return !!this.data[y * this.width + x];\n }\n\n public set(x: number, y: number, v: boolean) {\n this.data[y * this.width + x] = v ? 1 : 0;\n }\n\n public setRegion(left: number, top: number, width: number, height: number, v: boolean) {\n for (let y = top; y < top + height; y++) {\n for (let x = left; x < left + width; x++) {\n this.set(x, y, !!v);\n }\n }\n }\n}\n","import {BitMatrix} from \"../BitMatrix\";\nimport {GreyscaleWeights} from \"../index\";\n\nconst REGION_SIZE = 8;\nconst MIN_DYNAMIC_RANGE = 24;\n\nfunction numBetween(value: number, min: number, max: number): number {\n return value < min ? min : value > max ? max : value;\n}\n\n// Like BitMatrix but accepts arbitry Uint8 values\nclass Matrix {\n private data: Uint8ClampedArray;\n private width: number;\n constructor(width: number, height: number, buffer?: Uint8ClampedArray) {\n this.width = width;\n const bufferSize = width * height;\n if (buffer && buffer.length !== bufferSize) {\n throw new Error(\"Wrong buffer size\");\n }\n this.data = buffer || new Uint8ClampedArray(bufferSize);\n }\n public get(x: number, y: number) {\n return this.data[y * this.width + x];\n }\n public set(x: number, y: number, value: number) {\n this.data[y * this.width + x] = value;\n }\n}\n\nexport function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean,\n greyscaleWeights: GreyscaleWeights, canOverwriteImage: boolean) {\n const pixelCount = width * height;\n if (data.length !== pixelCount * 4) {\n throw new Error(\"Malformed data passed to binarizer.\");\n }\n // assign the greyscale and binary image within the rgba buffer as the rgba image will not be needed after conversion\n let bufferOffset = 0;\n // Convert image to greyscale\n let greyscaleBuffer: Uint8ClampedArray;\n if (canOverwriteImage) {\n greyscaleBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);\n bufferOffset += pixelCount;\n }\n const greyscalePixels = new Matrix(width, height, greyscaleBuffer);\n if (greyscaleWeights.useIntegerApproximation) {\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const pixelPosition = (y * width + x) * 4;\n const r = data[pixelPosition];\n const g = data[pixelPosition + 1];\n const b = data[pixelPosition + 2];\n greyscalePixels.set(x, y,\n // tslint:disable-next-line no-bitwise\n (greyscaleWeights.red * r + greyscaleWeights.green * g + greyscaleWeights.blue * b + 128) >> 8);\n }\n }\n } else {\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const pixelPosition = (y * width + x) * 4;\n const r = data[pixelPosition];\n const g = data[pixelPosition + 1];\n const b = data[pixelPosition + 2];\n greyscalePixels.set(x, y,\n greyscaleWeights.red * r + greyscaleWeights.green * g + greyscaleWeights.blue * b);\n }\n }\n }\n const horizontalRegionCount = Math.ceil(width / REGION_SIZE);\n const verticalRegionCount = Math.ceil(height / REGION_SIZE);\n const blackPointsCount = horizontalRegionCount * verticalRegionCount;\n\n let blackPointsBuffer: Uint8ClampedArray;\n if (canOverwriteImage) {\n blackPointsBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, blackPointsCount);\n bufferOffset += blackPointsCount;\n }\n const blackPoints = new Matrix(horizontalRegionCount, verticalRegionCount, blackPointsBuffer);\n for (let verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {\n for (let hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {\n let min = Infinity;\n let max = 0;\n for (let y = 0; y < REGION_SIZE; y++) {\n for (let x = 0; x < REGION_SIZE; x++) {\n const pixelLumosity =\n greyscalePixels.get(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y);\n min = Math.min(min, pixelLumosity);\n max = Math.max(max, pixelLumosity);\n }\n }\n // We could also compute the real average of all pixels but following the assumption that the qr code consists\n // of bright and dark pixels and essentially not much in between, by (min + max)/2 we make the cut really between\n // those two classes. If using the average over all pixel in a block of mostly bright pixels and few dark pixels,\n // the avg would tend to the bright side and darker bright pixels could be interpreted as dark.\n let average = (min + max) / 2;\n // Small bias towards black by moving the threshold up. We do this, as in the finder patterns white holes tend\n // to appear which makes them undetectable.\n const blackBias = 1.11;\n average = Math.min(255, average * blackBias);\n if (max - min <= MIN_DYNAMIC_RANGE) {\n // If variation within the block is low, assume this is a block with only light or only\n // dark pixels. In that case we do not want to use the average, as it would divide this\n // low contrast area into black and white pixels, essentially creating data out of noise.\n //\n // Default the blackpoint for these blocks to be half the min - effectively white them out\n average = min / 2;\n\n if (verticalRegion > 0 && hortizontalRegion > 0) {\n // Correct the \"white background\" assumption for blocks that have neighbors by comparing\n // the pixels in this block to the previously calculated black points. This is based on\n // the fact that dark barcode symbology is always surrounded by some amount of light\n // background for which reasonable black point estimates were made. The bp estimated at\n // the boundaries is used for the interior.\n\n // The (min < bp) is arbitrary but works better than other heuristics that were tried.\n const averageNeighborBlackPoint = (\n blackPoints.get(hortizontalRegion, verticalRegion - 1) +\n (2 * blackPoints.get(hortizontalRegion - 1, verticalRegion)) +\n blackPoints.get(hortizontalRegion - 1, verticalRegion - 1)\n ) / 4;\n if (min < averageNeighborBlackPoint) {\n average = averageNeighborBlackPoint; // no need to apply black bias as already applied to neighbors\n }\n }\n }\n blackPoints.set(hortizontalRegion, verticalRegion, average);\n }\n }\n\n let binarized: BitMatrix;\n if (canOverwriteImage) {\n const binarizedBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);\n bufferOffset += pixelCount;\n binarized = new BitMatrix(binarizedBuffer, width);\n } else {\n binarized = BitMatrix.createEmpty(width, height);\n }\n\n let inverted: BitMatrix = null;\n if (returnInverted) {\n if (canOverwriteImage) {\n const invertedBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);\n inverted = new BitMatrix(invertedBuffer, width);\n } else {\n inverted = BitMatrix.createEmpty(width, height);\n }\n }\n\n for (let verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {\n for (let hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {\n const left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);\n const top = numBetween(verticalRegion, 2, verticalRegionCount - 3);\n let sum = 0;\n for (let xRegion = -2; xRegion <= 2; xRegion++) {\n for (let yRegion = -2; yRegion <= 2; yRegion++) {\n sum += blackPoints.get(left + xRegion, top + yRegion);\n }\n }\n const threshold = sum / 25;\n for (let xRegion = 0; xRegion < REGION_SIZE; xRegion++) {\n for (let yRegion = 0; yRegion < REGION_SIZE; yRegion++) {\n const x = hortizontalRegion * REGION_SIZE + xRegion;\n const y = verticalRegion * REGION_SIZE + yRegion;\n const lum = greyscalePixels.get(x, y);\n binarized.set(x, y, lum <= threshold);\n if (returnInverted) {\n inverted.set(x, y, !(lum <= threshold));\n }\n }\n }\n }\n }\n if (returnInverted) {\n return { binarized, inverted };\n }\n return { binarized };\n}\n","// tslint:disable:no-bitwise\n\nexport class BitStream {\n private bytes: Uint8ClampedArray;\n private byteOffset: number = 0;\n private bitOffset: number = 0;\n\n constructor(bytes: Uint8ClampedArray) {\n this.bytes = bytes;\n }\n\n public readBits(numBits: number): number {\n if (numBits < 1 || numBits > 32 || numBits > this.available()) {\n throw new Error(\"Cannot read \" + numBits.toString() + \" bits\");\n }\n\n let result = 0;\n // First, read remainder from current byte\n if (this.bitOffset > 0) {\n const bitsLeft = 8 - this.bitOffset;\n const toRead = numBits < bitsLeft ? numBits : bitsLeft;\n const bitsToNotRead = bitsLeft - toRead;\n const mask = (0xFF >> (8 - toRead)) << bitsToNotRead;\n result = (this.bytes[this.byteOffset] & mask) >> bitsToNotRead;\n numBits -= toRead;\n this.bitOffset += toRead;\n if (this.bitOffset === 8) {\n this.bitOffset = 0;\n this.byteOffset++;\n }\n }\n\n // Next read whole bytes\n if (numBits > 0) {\n while (numBits >= 8) {\n result = (result << 8) | (this.bytes[this.byteOffset] & 0xFF);\n this.byteOffset++;\n numBits -= 8;\n }\n\n // Finally read a partial byte\n if (numBits > 0) {\n const bitsToNotRead = 8 - numBits;\n const mask = (0xFF >> bitsToNotRead) << bitsToNotRead;\n result = (result << numBits) | ((this.bytes[this.byteOffset] & mask) >> bitsToNotRead);\n this.bitOffset += numBits;\n }\n }\n return result;\n }\n\n public available(): number {\n return 8 * (this.bytes.length - this.byteOffset) - this.bitOffset;\n }\n}\n","// tslint:disable:no-bitwise\nimport { BitStream } from \"./BitStream\";\n\nexport interface Chunk {\n type: Mode;\n text: string;\n}\n\nexport interface ByteChunk {\n type: Mode.Byte | Mode.Kanji;\n bytes: number[];\n}\n\nexport interface ECIChunk {\n type: Mode.ECI;\n assignmentNumber: number;\n}\n\nexport interface StructuredAppend {\n type: Mode.StructuredAppend;\n currentSequence: number;\n totalSequence: number;\n parity: number;\n}\n\nexport type Chunks = Array;\n\nexport interface DecodedQR {\n text: string;\n bytes: number[];\n chunks: Chunks;\n version: number;\n}\n\nexport enum Mode {\n Numeric = \"numeric\",\n Alphanumeric = \"alphanumeric\",\n Byte = \"byte\",\n Kanji = \"kanji\",\n ECI = \"eci\",\n StructuredAppend = \"structuredappend\",\n}\n\nenum ModeByte {\n Terminator = 0x0,\n Numeric = 0x1,\n Alphanumeric = 0x2,\n Byte = 0x4,\n Kanji = 0x8,\n ECI = 0x7,\n StructuredAppend = 0x3,\n // FNC1FirstPosition = 0x5,\n // FNC1SecondPosition = 0x9,\n}\n\nfunction decodeNumeric(stream: BitStream, size: number) {\n const bytes: number[] = [];\n let text = \"\";\n\n const characterCountSize = [10, 12, 14][size];\n let length = stream.readBits(characterCountSize);\n // Read digits in groups of 3\n while (length >= 3) {\n const num = stream.readBits(10);\n if (num >= 1000) {\n throw new Error(\"Invalid numeric value above 999\");\n }\n\n const a = Math.floor(num / 100);\n const b = Math.floor(num / 10) % 10;\n const c = num % 10;\n\n bytes.push(48 + a, 48 + b, 48 + c);\n text += a.toString() + b.toString() + c.toString();\n length -= 3;\n }\n\n // If the number of digits aren't a multiple of 3, the remaining digits are special cased.\n if (length === 2) {\n const num = stream.readBits(7);\n if (num >= 100) {\n throw new Error(\"Invalid numeric value above 99\");\n }\n\n const a = Math.floor(num / 10);\n const b = num % 10;\n\n bytes.push(48 + a, 48 + b);\n text += a.toString() + b.toString();\n } else if (length === 1) {\n const num = stream.readBits(4);\n if (num >= 10) {\n throw new Error(\"Invalid numeric value above 9\");\n }\n\n bytes.push(48 + num);\n text += num.toString();\n }\n\n return { bytes, text };\n}\n\nconst AlphanumericCharacterCodes = [\n \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\",\n \"9\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\",\n \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\", \"P\", \"Q\",\n \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\",\n \" \", \"$\", \"%\", \"*\", \"+\", \"-\", \".\", \"/\", \":\",\n];\n\nfunction decodeAlphanumeric(stream: BitStream, size: number) {\n const bytes: number[] = [];\n let text = \"\";\n\n const characterCountSize = [9, 11, 13][size];\n let length = stream.readBits(characterCountSize);\n while (length >= 2) {\n const v = stream.readBits(11);\n\n const a = Math.floor(v / 45);\n const b = v % 45;\n\n bytes.push(AlphanumericCharacterCodes[a].charCodeAt(0), AlphanumericCharacterCodes[b].charCodeAt(0));\n text += AlphanumericCharacterCodes[a] + AlphanumericCharacterCodes[b];\n length -= 2;\n }\n\n if (length === 1) {\n const a = stream.readBits(6);\n bytes.push(AlphanumericCharacterCodes[a].charCodeAt(0));\n text += AlphanumericCharacterCodes[a];\n }\n\n return { bytes, text };\n}\n\nfunction decodeByte(stream: BitStream, size: number) {\n const bytes: number[] = [];\n let text = \"\";\n\n const characterCountSize = [8, 16, 16][size];\n const length = stream.readBits(characterCountSize);\n for (let i = 0; i < length; i++) {\n const b = stream.readBits(8);\n bytes.push(b);\n }\n try {\n text += decodeURIComponent(bytes.map(b => `%${(\"0\" + b.toString(16)).substr(-2)}`).join(\"\"));\n } catch {\n // failed to decode\n }\n\n return { bytes, text };\n}\n\nfunction decodeKanji(stream: BitStream, size: number) {\n const bytes: number[] = [];\n\n const characterCountSize = [8, 10, 12][size];\n const length = stream.readBits(characterCountSize);\n for (let i = 0; i < length; i++) {\n const k = stream.readBits(13);\n\n let c = (Math.floor(k / 0xC0) << 8) | (k % 0xC0);\n if (c < 0x1F00) {\n c += 0x8140;\n } else {\n c += 0xC140;\n }\n\n bytes.push(c >> 8, c & 0xFF);\n }\n\n const text = new TextDecoder(\"shift-jis\").decode(Uint8Array.from(bytes));\n return { bytes, text };\n}\n\nexport function decode(data: Uint8ClampedArray, version: number): DecodedQR {\n const stream = new BitStream(data);\n\n // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2).\n const size = version <= 9 ? 0 : version <= 26 ? 1 : 2;\n\n const result: DecodedQR = {\n text: \"\",\n bytes: [],\n chunks: [],\n version,\n };\n\n while (stream.available() >= 4) {\n const mode = stream.readBits(4);\n if (mode === ModeByte.Terminator) {\n return result;\n } else if (mode === ModeByte.ECI) {\n if (stream.readBits(1) === 0) {\n result.chunks.push({\n type: Mode.ECI,\n assignmentNumber: stream.readBits(7),\n });\n } else if (stream.readBits(1) === 0) {\n result.chunks.push({\n type: Mode.ECI,\n assignmentNumber: stream.readBits(14),\n });\n } else if (stream.readBits(1) === 0) {\n result.chunks.push({\n type: Mode.ECI,\n assignmentNumber: stream.readBits(21),\n });\n } else {\n // ECI data seems corrupted\n result.chunks.push({\n type: Mode.ECI,\n assignmentNumber: -1,\n });\n }\n } else if (mode === ModeByte.Numeric) {\n const numericResult = decodeNumeric(stream, size);\n result.text += numericResult.text;\n result.bytes.push(...numericResult.bytes);\n result.chunks.push({\n type: Mode.Numeric,\n text: numericResult.text,\n });\n } else if (mode === ModeByte.Alphanumeric) {\n const alphanumericResult = decodeAlphanumeric(stream, size);\n result.text += alphanumericResult.text;\n result.bytes.push(...alphanumericResult.bytes);\n result.chunks.push({\n type: Mode.Alphanumeric,\n text: alphanumericResult.text,\n });\n } else if (mode === ModeByte.Byte) {\n const byteResult = decodeByte(stream, size);\n result.text += byteResult.text;\n result.bytes.push(...byteResult.bytes);\n result.chunks.push({\n type: Mode.Byte,\n bytes: byteResult.bytes,\n text: byteResult.text,\n });\n } else if (mode === ModeByte.Kanji) {\n const kanjiResult = decodeKanji(stream, size);\n result.text += kanjiResult.text;\n result.bytes.push(...kanjiResult.bytes);\n result.chunks.push({\n type: Mode.Kanji,\n bytes: kanjiResult.bytes,\n text: kanjiResult.text,\n });\n } else if (mode === ModeByte.StructuredAppend) {\n result.chunks.push({\n type: Mode.StructuredAppend,\n currentSequence: stream.readBits(4),\n totalSequence: stream.readBits(4),\n parity: stream.readBits(8),\n });\n }\n }\n\n // If there is no data left, or the remaining bits are all 0, then that counts as a termination marker\n if (stream.available() === 0 || stream.readBits(stream.available()) === 0) {\n return result;\n }\n}\n","import GenericGF, { addOrSubtractGF } from \"./GenericGF\";\n\nexport default class GenericGFPoly {\n private field: GenericGF;\n private coefficients: Uint8ClampedArray;\n\n constructor(field: GenericGF, coefficients: Uint8ClampedArray) {\n if (coefficients.length === 0) {\n throw new Error(\"No coefficients.\");\n }\n this.field = field;\n const coefficientsLength = coefficients.length;\n if (coefficientsLength > 1 && coefficients[0] === 0) {\n // Leading term must be non-zero for anything except the constant polynomial \"0\"\n let firstNonZero = 1;\n while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) {\n firstNonZero++;\n }\n if (firstNonZero === coefficientsLength) {\n this.coefficients = field.zero.coefficients;\n } else {\n this.coefficients = new Uint8ClampedArray(coefficientsLength - firstNonZero);\n for (let i = 0; i < this.coefficients.length; i++) {\n this.coefficients[i] = coefficients[firstNonZero + i];\n }\n }\n } else {\n this.coefficients = coefficients;\n }\n }\n\n public degree() {\n return this.coefficients.length - 1;\n }\n\n public isZero() {\n return this.coefficients[0] === 0;\n }\n\n public getCoefficient(degree: number) {\n return this.coefficients[this.coefficients.length - 1 - degree];\n }\n\n public addOrSubtract(other: GenericGFPoly) {\n if (this.isZero()) {\n return other;\n }\n if (other.isZero()) {\n return this;\n }\n\n let smallerCoefficients = this.coefficients;\n let largerCoefficients = other.coefficients;\n if (smallerCoefficients.length > largerCoefficients.length) {\n [smallerCoefficients, largerCoefficients] = [largerCoefficients, smallerCoefficients];\n }\n const sumDiff = new Uint8ClampedArray(largerCoefficients.length);\n const lengthDiff = largerCoefficients.length - smallerCoefficients.length;\n for (let i = 0; i < lengthDiff; i++) {\n sumDiff[i] = largerCoefficients[i];\n }\n\n for (let i = lengthDiff; i < largerCoefficients.length; i++) {\n sumDiff[i] = addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);\n }\n\n return new GenericGFPoly(this.field, sumDiff);\n }\n\n public multiply(scalar: number) {\n if (scalar === 0) {\n return this.field.zero;\n }\n if (scalar === 1) {\n return this;\n }\n const size = this.coefficients.length;\n const product = new Uint8ClampedArray(size);\n for (let i = 0; i < size; i++) {\n product[i] = this.field.multiply(this.coefficients[i], scalar);\n }\n\n return new GenericGFPoly(this.field, product);\n }\n\n public multiplyPoly(other: GenericGFPoly): GenericGFPoly {\n if (this.isZero() || other.isZero()) {\n return this.field.zero;\n }\n const aCoefficients = this.coefficients;\n const aLength = aCoefficients.length;\n const bCoefficients = other.coefficients;\n const bLength = bCoefficients.length;\n const product = new Uint8ClampedArray(aLength + bLength - 1);\n for (let i = 0; i < aLength; i++) {\n const aCoeff = aCoefficients[i];\n for (let j = 0; j < bLength; j++) {\n product[i + j] = addOrSubtractGF(product[i + j],\n this.field.multiply(aCoeff, bCoefficients[j]));\n }\n }\n return new GenericGFPoly(this.field, product);\n }\n\n public multiplyByMonomial(degree: number, coefficient: number) {\n if (degree < 0) {\n throw new Error(\"Invalid degree less than 0\");\n }\n if (coefficient === 0) {\n return this.field.zero;\n }\n const size = this.coefficients.length;\n const product = new Uint8ClampedArray(size + degree);\n for (let i = 0; i < size; i++) {\n product[i] = this.field.multiply(this.coefficients[i], coefficient);\n }\n return new GenericGFPoly(this.field, product);\n }\n\n public evaluateAt(a: number) {\n let result = 0;\n if (a === 0) {\n // Just return the x^0 coefficient\n return this.getCoefficient(0);\n }\n const size = this.coefficients.length;\n if (a === 1) {\n // Just the sum of the coefficients\n this.coefficients.forEach((coefficient) => {\n result = addOrSubtractGF(result, coefficient);\n });\n return result;\n }\n result = this.coefficients[0];\n for (let i = 1; i < size; i++) {\n result = addOrSubtractGF(this.field.multiply(a, result), this.coefficients[i]);\n }\n return result;\n }\n}\n","import GenericGFPoly from \"./GenericGFPoly\";\n\nexport function addOrSubtractGF(a: number, b: number) {\n return a ^ b; // tslint:disable-line:no-bitwise\n}\n\nexport default class GenericGF {\n public primitive: number;\n public size: number;\n public generatorBase: number;\n public zero: GenericGFPoly;\n public one: GenericGFPoly;\n\n private expTable: number[];\n private logTable: number[];\n\n constructor(primitive: number, size: number, genBase: number) {\n this.primitive = primitive;\n this.size = size;\n this.generatorBase = genBase;\n this.expTable = new Array(this.size);\n this.logTable = new Array(this.size);\n\n let x = 1;\n for (let i = 0; i < this.size; i++) {\n this.expTable[i] = x;\n x = x * 2;\n if (x >= this.size) {\n x = (x ^ this.primitive) & (this.size - 1); // tslint:disable-line:no-bitwise\n }\n }\n\n for (let i = 0; i < this.size - 1; i++) {\n this.logTable[this.expTable[i]] = i;\n }\n this.zero = new GenericGFPoly(this, Uint8ClampedArray.from([0]));\n this.one = new GenericGFPoly(this, Uint8ClampedArray.from([1]));\n }\n\n public multiply(a: number, b: number) {\n if (a === 0 || b === 0) {\n return 0;\n }\n return this.expTable[(this.logTable[a] + this.logTable[b]) % (this.size - 1)];\n }\n\n public inverse(a: number) {\n if (a === 0) {\n throw new Error(\"Can't invert 0\");\n }\n return this.expTable[this.size - this.logTable[a] - 1];\n }\n\n public buildMonomial(degree: number, coefficient: number): GenericGFPoly {\n if (degree < 0) {\n throw new Error(\"Invalid monomial degree less than 0\");\n }\n if (coefficient === 0) {\n return this.zero;\n }\n const coefficients = new Uint8ClampedArray(degree + 1);\n coefficients[0] = coefficient;\n return new GenericGFPoly(this, coefficients);\n }\n\n public log(a: number) {\n if (a === 0) {\n throw new Error(\"Can't take log(0)\");\n }\n return this.logTable[a];\n }\n\n public exp(a: number) {\n return this.expTable[a];\n }\n}\n","import GenericGF, { addOrSubtractGF } from \"./GenericGF\";\nimport GenericGFPoly from \"./GenericGFPoly\";\n\nfunction runEuclideanAlgorithm(field: GenericGF, a: GenericGFPoly, b: GenericGFPoly, R: number): GenericGFPoly[] {\n // Assume a's degree is >= b's\n if (a.degree() < b.degree()) {\n [a, b] = [b, a];\n }\n\n let rLast = a;\n let r = b;\n let tLast = field.zero;\n let t = field.one;\n\n // Run Euclidean algorithm until r's degree is less than R/2\n while (r.degree() >= R / 2) {\n const rLastLast = rLast;\n const tLastLast = tLast;\n rLast = r;\n tLast = t;\n\n // Divide rLastLast by rLast, with quotient in q and remainder in r\n if (rLast.isZero()) {\n // Euclidean algorithm already terminated?\n return null;\n }\n r = rLastLast;\n let q = field.zero;\n const denominatorLeadingTerm = rLast.getCoefficient(rLast.degree());\n const dltInverse = field.inverse(denominatorLeadingTerm);\n while (r.degree() >= rLast.degree() && !r.isZero()) {\n const degreeDiff = r.degree() - rLast.degree();\n const scale = field.multiply(r.getCoefficient(r.degree()), dltInverse);\n q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));\n r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));\n }\n\n t = q.multiplyPoly(tLast).addOrSubtract(tLastLast);\n\n if (r.degree() >= rLast.degree()) {\n return null;\n }\n }\n\n const sigmaTildeAtZero = t.getCoefficient(0);\n if (sigmaTildeAtZero === 0) {\n return null;\n }\n\n const inverse = field.inverse(sigmaTildeAtZero);\n return [t.multiply(inverse), r.multiply(inverse)];\n}\n\nfunction findErrorLocations(field: GenericGF, errorLocator: GenericGFPoly): number[] {\n // This is a direct application of Chien's search\n const numErrors = errorLocator.degree();\n if (numErrors === 1) {\n return [errorLocator.getCoefficient(1)];\n }\n const result: number[] = new Array(numErrors);\n let errorCount = 0;\n for (let i = 1; i < field.size && errorCount < numErrors; i++) {\n if (errorLocator.evaluateAt(i) === 0) {\n result[errorCount] = field.inverse(i);\n errorCount++;\n }\n }\n if (errorCount !== numErrors) {\n return null;\n }\n return result;\n}\n\nfunction findErrorMagnitudes(field: GenericGF, errorEvaluator: GenericGFPoly, errorLocations: number[]): number[] {\n // This is directly applying Forney's Formula\n const s = errorLocations.length;\n const result: number[] = new Array(s);\n for (let i = 0; i < s; i++) {\n const xiInverse = field.inverse(errorLocations[i]);\n let denominator = 1;\n for (let j = 0; j < s; j++) {\n if (i !== j) {\n denominator = field.multiply(denominator, addOrSubtractGF(1, field.multiply(errorLocations[j], xiInverse)));\n }\n }\n result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), field.inverse(denominator));\n if (field.generatorBase !== 0) {\n result[i] = field.multiply(result[i], xiInverse);\n }\n }\n return result;\n}\n\nexport function decode(bytes: number[], twoS: number) {\n const outputBytes = new Uint8ClampedArray(bytes.length);\n outputBytes.set(bytes);\n\n const field = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1\n const poly = new GenericGFPoly(field, outputBytes);\n\n const syndromeCoefficients = new Uint8ClampedArray(twoS);\n let error = false;\n for (let s = 0; s < twoS; s++) {\n const evaluation = poly.evaluateAt(field.exp(s + field.generatorBase));\n syndromeCoefficients[syndromeCoefficients.length - 1 - s] = evaluation;\n if (evaluation !== 0) {\n error = true;\n }\n }\n if (!error) {\n return outputBytes;\n }\n\n const syndrome = new GenericGFPoly(field, syndromeCoefficients);\n\n const sigmaOmega = runEuclideanAlgorithm(field, field.buildMonomial(twoS, 1), syndrome, twoS);\n if (sigmaOmega === null) {\n return null;\n }\n\n const errorLocations = findErrorLocations(field, sigmaOmega[0]);\n if (errorLocations == null) {\n return null;\n }\n\n const errorMagnitudes = findErrorMagnitudes(field, sigmaOmega[1], errorLocations);\n for (let i = 0; i < errorLocations.length; i++) {\n const position = outputBytes.length - 1 - field.log(errorLocations[i]);\n if (position < 0) {\n return null;\n }\n outputBytes[position] = addOrSubtractGF(outputBytes[position], errorMagnitudes[i]);\n }\n\n return outputBytes;\n}\n","export interface Version {\n infoBits: number;\n versionNumber: number;\n alignmentPatternCenters: number[];\n errorCorrectionLevels: Array<{\n ecCodewordsPerBlock: number;\n ecBlocks: Array<{\n numBlocks: number;\n dataCodewordsPerBlock: number;\n }>\n }>;\n}\n\nexport const VERSIONS: Version[] = [\n {\n infoBits: null,\n versionNumber: 1,\n alignmentPatternCenters: [],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 7,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 19 }],\n },\n {\n ecCodewordsPerBlock: 10,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 16 }],\n },\n {\n ecCodewordsPerBlock: 13,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 13 }],\n },\n {\n ecCodewordsPerBlock: 17,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 9 }],\n },\n ],\n },\n {\n infoBits: null,\n versionNumber: 2,\n alignmentPatternCenters: [6, 18],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 10,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 34 }],\n },\n {\n ecCodewordsPerBlock: 16,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 28 }],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 22 }],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 16 }],\n },\n ],\n },\n {\n infoBits: null,\n versionNumber: 3,\n alignmentPatternCenters: [6, 22],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 15,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 55 }],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 44 }],\n },\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 17 }],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 13 }],\n },\n ],\n },\n {\n infoBits: null,\n versionNumber: 4,\n alignmentPatternCenters: [6, 26],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 20,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 80 }],\n },\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 32 }],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 24 }],\n },\n {\n ecCodewordsPerBlock: 16,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 9 }],\n },\n ],\n },\n {\n infoBits: null,\n versionNumber: 5,\n alignmentPatternCenters: [6, 30],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 108 }],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 43 }],\n },\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 15 },\n { numBlocks: 2, dataCodewordsPerBlock: 16 },\n ],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 11 },\n { numBlocks: 2, dataCodewordsPerBlock: 12 },\n ],\n },\n ],\n },\n {\n infoBits: null,\n versionNumber: 6,\n alignmentPatternCenters: [6, 34],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 68 }],\n },\n {\n ecCodewordsPerBlock: 16,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 27 }],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 19 }],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 15 }],\n },\n ],\n },\n {\n infoBits: 0x07C94,\n versionNumber: 7,\n alignmentPatternCenters: [6, 22, 38],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 20,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 78 }],\n },\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 31 }],\n },\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 14 },\n { numBlocks: 4, dataCodewordsPerBlock: 15 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 13 },\n { numBlocks: 1, dataCodewordsPerBlock: 14 },\n ],\n },\n ],\n },\n {\n infoBits: 0x085BC,\n versionNumber: 8,\n alignmentPatternCenters: [6, 24, 42],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 97 }],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 38 },\n { numBlocks: 2, dataCodewordsPerBlock: 39 },\n ],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 18 },\n { numBlocks: 2, dataCodewordsPerBlock: 19 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 14 },\n { numBlocks: 2, dataCodewordsPerBlock: 15 },\n ],\n },\n ],\n },\n {\n infoBits: 0x09A99,\n versionNumber: 9,\n alignmentPatternCenters: [6, 26, 46],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 116 }],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 36 },\n { numBlocks: 2, dataCodewordsPerBlock: 37 },\n ],\n },\n {\n ecCodewordsPerBlock: 20,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 16 },\n { numBlocks: 4, dataCodewordsPerBlock: 17 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 12 },\n { numBlocks: 4, dataCodewordsPerBlock: 13 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0A4D3,\n versionNumber: 10,\n alignmentPatternCenters: [6, 28, 50],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 18,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 68 },\n { numBlocks: 2, dataCodewordsPerBlock: 69 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 43 },\n { numBlocks: 1, dataCodewordsPerBlock: 44 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 19 },\n { numBlocks: 2, dataCodewordsPerBlock: 20 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 15 },\n { numBlocks: 2, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0BBF6,\n versionNumber: 11,\n alignmentPatternCenters: [6, 30, 54],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 20,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 81 }],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 1, dataCodewordsPerBlock: 50 },\n { numBlocks: 4, dataCodewordsPerBlock: 51 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 22 },\n { numBlocks: 4, dataCodewordsPerBlock: 23 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 12 },\n { numBlocks: 8, dataCodewordsPerBlock: 13 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0C762,\n versionNumber: 12,\n alignmentPatternCenters: [6, 32, 58],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 92 },\n { numBlocks: 2, dataCodewordsPerBlock: 93 },\n ],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 36 },\n { numBlocks: 2, dataCodewordsPerBlock: 37 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 20 },\n { numBlocks: 6, dataCodewordsPerBlock: 21 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 7, dataCodewordsPerBlock: 14 },\n { numBlocks: 4, dataCodewordsPerBlock: 15 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0D847,\n versionNumber: 13,\n alignmentPatternCenters: [6, 34, 62],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 107 }],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 37 },\n { numBlocks: 1, dataCodewordsPerBlock: 38 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 20 },\n { numBlocks: 4, dataCodewordsPerBlock: 21 },\n ],\n },\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 12, dataCodewordsPerBlock: 11 },\n { numBlocks: 4, dataCodewordsPerBlock: 12 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0E60D,\n versionNumber: 14,\n alignmentPatternCenters: [6, 26, 46, 66],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 115 },\n { numBlocks: 1, dataCodewordsPerBlock: 116 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 40 },\n { numBlocks: 5, dataCodewordsPerBlock: 41 },\n ],\n },\n {\n ecCodewordsPerBlock: 20,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 16 },\n { numBlocks: 5, dataCodewordsPerBlock: 17 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 12 },\n { numBlocks: 5, dataCodewordsPerBlock: 13 },\n ],\n },\n ],\n },\n {\n infoBits: 0x0F928,\n versionNumber: 15,\n alignmentPatternCenters: [6, 26, 48, 70],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 22,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 87 },\n { numBlocks: 1, dataCodewordsPerBlock: 88 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 41 },\n { numBlocks: 5, dataCodewordsPerBlock: 42 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 24 },\n { numBlocks: 7, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 12 },\n { numBlocks: 7, dataCodewordsPerBlock: 13 },\n ],\n },\n ],\n },\n {\n infoBits: 0x10B78,\n versionNumber: 16,\n alignmentPatternCenters: [6, 26, 50, 74],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 98 },\n { numBlocks: 1, dataCodewordsPerBlock: 99 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 7, dataCodewordsPerBlock: 45 },\n { numBlocks: 3, dataCodewordsPerBlock: 46 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [\n { numBlocks: 15, dataCodewordsPerBlock: 19 },\n { numBlocks: 2, dataCodewordsPerBlock: 20 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 15 },\n { numBlocks: 13, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1145D,\n versionNumber: 17,\n alignmentPatternCenters: [6, 30, 54, 78],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 1, dataCodewordsPerBlock: 107 },\n { numBlocks: 5, dataCodewordsPerBlock: 108 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 10, dataCodewordsPerBlock: 46 },\n { numBlocks: 1, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 1, dataCodewordsPerBlock: 22 },\n { numBlocks: 15, dataCodewordsPerBlock: 23 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 14 },\n { numBlocks: 17, dataCodewordsPerBlock: 15 },\n ],\n },\n ],\n },\n {\n infoBits: 0x12A17,\n versionNumber: 18,\n alignmentPatternCenters: [6, 30, 56, 82],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 120 },\n { numBlocks: 1, dataCodewordsPerBlock: 121 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 9, dataCodewordsPerBlock: 43 },\n { numBlocks: 4, dataCodewordsPerBlock: 44 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 17, dataCodewordsPerBlock: 22 },\n { numBlocks: 1, dataCodewordsPerBlock: 23 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 14 },\n { numBlocks: 19, dataCodewordsPerBlock: 15 },\n ],\n },\n ],\n },\n {\n infoBits: 0x13532,\n versionNumber: 19,\n alignmentPatternCenters: [6, 30, 58, 86],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 113 },\n { numBlocks: 4, dataCodewordsPerBlock: 114 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 44 },\n { numBlocks: 11, dataCodewordsPerBlock: 45 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 17, dataCodewordsPerBlock: 21 },\n { numBlocks: 4, dataCodewordsPerBlock: 22 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 9, dataCodewordsPerBlock: 13 },\n { numBlocks: 16, dataCodewordsPerBlock: 14 },\n ],\n },\n ],\n },\n {\n infoBits: 0x149A6,\n versionNumber: 20,\n alignmentPatternCenters: [6, 34, 62, 90],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 107 },\n { numBlocks: 5, dataCodewordsPerBlock: 108 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 41 },\n { numBlocks: 13, dataCodewordsPerBlock: 42 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 15, dataCodewordsPerBlock: 24 },\n { numBlocks: 5, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 15, dataCodewordsPerBlock: 15 },\n { numBlocks: 10, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x15683,\n versionNumber: 21,\n alignmentPatternCenters: [6, 28, 50, 72, 94],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 116 },\n { numBlocks: 4, dataCodewordsPerBlock: 117 },\n ],\n },\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 42 }],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 17, dataCodewordsPerBlock: 22 },\n { numBlocks: 6, dataCodewordsPerBlock: 23 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 16 },\n { numBlocks: 6, dataCodewordsPerBlock: 17 },\n ],\n },\n ],\n },\n {\n infoBits: 0x168C9,\n versionNumber: 22,\n alignmentPatternCenters: [6, 26, 50, 74, 98],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 111 },\n { numBlocks: 7, dataCodewordsPerBlock: 112 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 46 }],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 7, dataCodewordsPerBlock: 24 },\n { numBlocks: 16, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 24,\n ecBlocks: [{ numBlocks: 34, dataCodewordsPerBlock: 13 }],\n },\n ],\n },\n {\n infoBits: 0x177EC,\n versionNumber: 23,\n alignmentPatternCenters: [6, 30, 54, 74, 102],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 121 },\n { numBlocks: 5, dataCodewordsPerBlock: 122 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 47 },\n { numBlocks: 14, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 24 },\n { numBlocks: 14, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 16, dataCodewordsPerBlock: 15 },\n { numBlocks: 14, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x18EC4,\n versionNumber: 24,\n alignmentPatternCenters: [6, 28, 54, 80, 106],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 117 },\n { numBlocks: 4, dataCodewordsPerBlock: 118 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 45 },\n { numBlocks: 14, dataCodewordsPerBlock: 46 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 24 },\n { numBlocks: 16, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 30, dataCodewordsPerBlock: 16 },\n { numBlocks: 2, dataCodewordsPerBlock: 17 },\n ],\n },\n ],\n },\n {\n infoBits: 0x191E1,\n versionNumber: 25,\n alignmentPatternCenters: [6, 32, 58, 84, 110],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 26,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 106 },\n { numBlocks: 4, dataCodewordsPerBlock: 107 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 47 },\n { numBlocks: 13, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 7, dataCodewordsPerBlock: 24 },\n { numBlocks: 22, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 22, dataCodewordsPerBlock: 15 },\n { numBlocks: 13, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1AFAB,\n versionNumber: 26,\n alignmentPatternCenters: [6, 30, 58, 86, 114],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 10, dataCodewordsPerBlock: 114 },\n { numBlocks: 2, dataCodewordsPerBlock: 115 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 46 },\n { numBlocks: 4, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 28, dataCodewordsPerBlock: 22 },\n { numBlocks: 6, dataCodewordsPerBlock: 23 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 33, dataCodewordsPerBlock: 16 },\n { numBlocks: 4, dataCodewordsPerBlock: 17 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1B08E,\n versionNumber: 27,\n alignmentPatternCenters: [6, 34, 62, 90, 118],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 122 },\n { numBlocks: 4, dataCodewordsPerBlock: 123 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 22, dataCodewordsPerBlock: 45 },\n { numBlocks: 3, dataCodewordsPerBlock: 46 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 8, dataCodewordsPerBlock: 23 },\n { numBlocks: 26, dataCodewordsPerBlock: 24 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 12, dataCodewordsPerBlock: 15 },\n { numBlocks: 28, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1CC1A,\n versionNumber: 28,\n alignmentPatternCenters: [6, 26, 50, 74, 98, 122],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 117 },\n { numBlocks: 10, dataCodewordsPerBlock: 118 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 3, dataCodewordsPerBlock: 45 },\n { numBlocks: 23, dataCodewordsPerBlock: 46 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 24 },\n { numBlocks: 31, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 15 },\n { numBlocks: 31, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1D33F,\n versionNumber: 29,\n alignmentPatternCenters: [6, 30, 54, 78, 102, 126],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 7, dataCodewordsPerBlock: 116 },\n { numBlocks: 7, dataCodewordsPerBlock: 117 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 21, dataCodewordsPerBlock: 45 },\n { numBlocks: 7, dataCodewordsPerBlock: 46 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 1, dataCodewordsPerBlock: 23 },\n { numBlocks: 37, dataCodewordsPerBlock: 24 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 15 },\n { numBlocks: 26, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1ED75,\n versionNumber: 30,\n alignmentPatternCenters: [6, 26, 52, 78, 104, 130],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 5, dataCodewordsPerBlock: 115 },\n { numBlocks: 10, dataCodewordsPerBlock: 116 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 47 },\n { numBlocks: 10, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 15, dataCodewordsPerBlock: 24 },\n { numBlocks: 25, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 23, dataCodewordsPerBlock: 15 },\n { numBlocks: 25, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x1F250,\n versionNumber: 31,\n alignmentPatternCenters: [6, 30, 56, 82, 108, 134],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 13, dataCodewordsPerBlock: 115 },\n { numBlocks: 3, dataCodewordsPerBlock: 116 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 46 },\n { numBlocks: 29, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 42, dataCodewordsPerBlock: 24 },\n { numBlocks: 1, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 23, dataCodewordsPerBlock: 15 },\n { numBlocks: 28, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x209D5,\n versionNumber: 32,\n alignmentPatternCenters: [6, 34, 60, 86, 112, 138],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 115 }],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 10, dataCodewordsPerBlock: 46 },\n { numBlocks: 23, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 10, dataCodewordsPerBlock: 24 },\n { numBlocks: 35, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 15 },\n { numBlocks: 35, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x216F0,\n versionNumber: 33,\n alignmentPatternCenters: [6, 30, 58, 86, 114, 142],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 17, dataCodewordsPerBlock: 115 },\n { numBlocks: 1, dataCodewordsPerBlock: 116 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 14, dataCodewordsPerBlock: 46 },\n { numBlocks: 21, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 29, dataCodewordsPerBlock: 24 },\n { numBlocks: 19, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 11, dataCodewordsPerBlock: 15 },\n { numBlocks: 46, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x228BA,\n versionNumber: 34,\n alignmentPatternCenters: [6, 34, 62, 90, 118, 146],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 13, dataCodewordsPerBlock: 115 },\n { numBlocks: 6, dataCodewordsPerBlock: 116 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 14, dataCodewordsPerBlock: 46 },\n { numBlocks: 23, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 44, dataCodewordsPerBlock: 24 },\n { numBlocks: 7, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 59, dataCodewordsPerBlock: 16 },\n { numBlocks: 1, dataCodewordsPerBlock: 17 },\n ],\n },\n ],\n },\n {\n infoBits: 0x2379F,\n versionNumber: 35,\n alignmentPatternCenters: [6, 30, 54, 78, 102, 126, 150],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 12, dataCodewordsPerBlock: 121 },\n { numBlocks: 7, dataCodewordsPerBlock: 122 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 12, dataCodewordsPerBlock: 47 },\n { numBlocks: 26, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 39, dataCodewordsPerBlock: 24 },\n { numBlocks: 14, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 22, dataCodewordsPerBlock: 15 },\n { numBlocks: 41, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x24B0B,\n versionNumber: 36,\n alignmentPatternCenters: [ 6, 24, 50, 76, 102, 128, 154 ],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 121 },\n { numBlocks: 14, dataCodewordsPerBlock: 122 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 6, dataCodewordsPerBlock: 47 },\n { numBlocks: 34, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 46, dataCodewordsPerBlock: 24 },\n { numBlocks: 10, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 2, dataCodewordsPerBlock: 15 },\n { numBlocks: 64, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x2542E,\n versionNumber: 37,\n alignmentPatternCenters: [ 6, 28, 54, 80, 106, 132, 158 ],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 17, dataCodewordsPerBlock: 122 },\n { numBlocks: 4, dataCodewordsPerBlock: 123 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 29, dataCodewordsPerBlock: 46 },\n { numBlocks: 14, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 49, dataCodewordsPerBlock: 24 },\n { numBlocks: 10, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 24, dataCodewordsPerBlock: 15 },\n { numBlocks: 46, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x26A64,\n versionNumber: 38,\n alignmentPatternCenters: [ 6, 32, 58, 84, 110, 136, 162 ],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 4, dataCodewordsPerBlock: 122 },\n { numBlocks: 18, dataCodewordsPerBlock: 123 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 13, dataCodewordsPerBlock: 46 },\n { numBlocks: 32, dataCodewordsPerBlock: 47 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 48, dataCodewordsPerBlock: 24 },\n { numBlocks: 14, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 42, dataCodewordsPerBlock: 15 },\n { numBlocks: 32, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x27541,\n versionNumber: 39,\n alignmentPatternCenters: [ 6, 26, 54, 82, 110, 138, 166 ],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 20, dataCodewordsPerBlock: 117 },\n { numBlocks: 4, dataCodewordsPerBlock: 118 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 40, dataCodewordsPerBlock: 47 },\n { numBlocks: 7, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 43, dataCodewordsPerBlock: 24 },\n { numBlocks: 22, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 10, dataCodewordsPerBlock: 15 },\n { numBlocks: 67, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n {\n infoBits: 0x28C69,\n versionNumber: 40,\n alignmentPatternCenters: [ 6, 30, 58, 86, 114, 142, 170 ],\n errorCorrectionLevels: [\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 19, dataCodewordsPerBlock: 118 },\n { numBlocks: 6, dataCodewordsPerBlock: 119 },\n ],\n },\n {\n ecCodewordsPerBlock: 28,\n ecBlocks: [\n { numBlocks: 18, dataCodewordsPerBlock: 47 },\n { numBlocks: 31, dataCodewordsPerBlock: 48 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 34, dataCodewordsPerBlock: 24 },\n { numBlocks: 34, dataCodewordsPerBlock: 25 },\n ],\n },\n {\n ecCodewordsPerBlock: 30,\n ecBlocks: [\n { numBlocks: 20, dataCodewordsPerBlock: 15 },\n { numBlocks: 61, dataCodewordsPerBlock: 16 },\n ],\n },\n ],\n },\n];\n","import { BitMatrix } from \"../BitMatrix\";\nimport { Point } from \"../Point\";\nimport { decode as decodeData, DecodedQR } from \"./decodeData\";\nimport { decode as rsDecode } from \"./reedsolomon\";\nimport { Version, VERSIONS } from \"./version\";\n\n// tslint:disable:no-bitwise\nfunction numBitsDiffering(x: number, y: number) {\n let z = x ^ y;\n let bitCount = 0;\n while (z) {\n bitCount++;\n z &= z - 1;\n }\n return bitCount;\n}\n\nfunction pushBit(bit: any, byte: number) {\n return (byte << 1) | bit;\n}\n// tslint:enable:no-bitwise\n\nconst FORMAT_INFO_TABLE = [\n { bits: 0x5412, formatInfo: { errorCorrectionLevel: 1, dataMask: 0 } },\n { bits: 0x5125, formatInfo: { errorCorrectionLevel: 1, dataMask: 1 } },\n { bits: 0x5E7C, formatInfo: { errorCorrectionLevel: 1, dataMask: 2 } },\n { bits: 0x5B4B, formatInfo: { errorCorrectionLevel: 1, dataMask: 3 } },\n { bits: 0x45F9, formatInfo: { errorCorrectionLevel: 1, dataMask: 4 } },\n { bits: 0x40CE, formatInfo: { errorCorrectionLevel: 1, dataMask: 5 } },\n { bits: 0x4F97, formatInfo: { errorCorrectionLevel: 1, dataMask: 6 } },\n { bits: 0x4AA0, formatInfo: { errorCorrectionLevel: 1, dataMask: 7 } },\n { bits: 0x77C4, formatInfo: { errorCorrectionLevel: 0, dataMask: 0 } },\n { bits: 0x72F3, formatInfo: { errorCorrectionLevel: 0, dataMask: 1 } },\n { bits: 0x7DAA, formatInfo: { errorCorrectionLevel: 0, dataMask: 2 } },\n { bits: 0x789D, formatInfo: { errorCorrectionLevel: 0, dataMask: 3 } },\n { bits: 0x662F, formatInfo: { errorCorrectionLevel: 0, dataMask: 4 } },\n { bits: 0x6318, formatInfo: { errorCorrectionLevel: 0, dataMask: 5 } },\n { bits: 0x6C41, formatInfo: { errorCorrectionLevel: 0, dataMask: 6 } },\n { bits: 0x6976, formatInfo: { errorCorrectionLevel: 0, dataMask: 7 } },\n { bits: 0x1689, formatInfo: { errorCorrectionLevel: 3, dataMask: 0 } },\n { bits: 0x13BE, formatInfo: { errorCorrectionLevel: 3, dataMask: 1 } },\n { bits: 0x1CE7, formatInfo: { errorCorrectionLevel: 3, dataMask: 2 } },\n { bits: 0x19D0, formatInfo: { errorCorrectionLevel: 3, dataMask: 3 } },\n { bits: 0x0762, formatInfo: { errorCorrectionLevel: 3, dataMask: 4 } },\n { bits: 0x0255, formatInfo: { errorCorrectionLevel: 3, dataMask: 5 } },\n { bits: 0x0D0C, formatInfo: { errorCorrectionLevel: 3, dataMask: 6 } },\n { bits: 0x083B, formatInfo: { errorCorrectionLevel: 3, dataMask: 7 } },\n { bits: 0x355F, formatInfo: { errorCorrectionLevel: 2, dataMask: 0 } },\n { bits: 0x3068, formatInfo: { errorCorrectionLevel: 2, dataMask: 1 } },\n { bits: 0x3F31, formatInfo: { errorCorrectionLevel: 2, dataMask: 2 } },\n { bits: 0x3A06, formatInfo: { errorCorrectionLevel: 2, dataMask: 3 } },\n { bits: 0x24B4, formatInfo: { errorCorrectionLevel: 2, dataMask: 4 } },\n { bits: 0x2183, formatInfo: { errorCorrectionLevel: 2, dataMask: 5 } },\n { bits: 0x2EDA, formatInfo: { errorCorrectionLevel: 2, dataMask: 6 } },\n { bits: 0x2BED, formatInfo: { errorCorrectionLevel: 2, dataMask: 7 } },\n];\n\nconst DATA_MASKS = [\n (p: Point) => ((p.y + p.x) % 2) === 0,\n (p: Point) => (p.y % 2) === 0,\n (p: Point) => p.x % 3 === 0,\n (p: Point) => (p.y + p.x) % 3 === 0,\n (p: Point) => (Math.floor(p.y / 2) + Math.floor(p.x / 3)) % 2 === 0,\n (p: Point) => ((p.x * p.y) % 2) + ((p.x * p.y) % 3) === 0,\n (p: Point) => ((((p.y * p.x) % 2) + (p.y * p.x) % 3) % 2) === 0,\n (p: Point) => ((((p.y + p.x) % 2) + (p.y * p.x) % 3) % 2) === 0,\n];\n\ninterface FormatInformation {\n errorCorrectionLevel: number;\n dataMask: number;\n}\n\nfunction buildFunctionPatternMask(version: Version): BitMatrix {\n const dimension = 17 + 4 * version.versionNumber;\n const matrix = BitMatrix.createEmpty(dimension, dimension);\n\n matrix.setRegion(0, 0, 9, 9, true); // Top left finder pattern + separator + format\n matrix.setRegion(dimension - 8, 0, 8, 9, true); // Top right finder pattern + separator + format\n matrix.setRegion(0, dimension - 8, 9, 8, true); // Bottom left finder pattern + separator + format\n\n // Alignment patterns\n for (const x of version.alignmentPatternCenters) {\n for (const y of version.alignmentPatternCenters) {\n if (!(x === 6 && y === 6 || x === 6 && y === dimension - 7 || x === dimension - 7 && y === 6)) {\n matrix.setRegion(x - 2, y - 2, 5, 5, true);\n }\n }\n }\n\n matrix.setRegion(6, 9, 1, dimension - 17, true); // Vertical timing pattern\n matrix.setRegion(9, 6, dimension - 17, 1, true); // Horizontal timing pattern\n\n if (version.versionNumber > 6) {\n matrix.setRegion(dimension - 11, 0, 3, 6, true); // Version info, top right\n matrix.setRegion(0, dimension - 11, 6, 3, true); // Version info, bottom left\n }\n\n return matrix;\n}\n\nfunction readCodewords(matrix: BitMatrix, version: Version, formatInfo: FormatInformation) {\n const dataMask = DATA_MASKS[formatInfo.dataMask];\n const dimension = matrix.height;\n\n const functionPatternMask = buildFunctionPatternMask(version);\n\n const codewords: number[] = [];\n let currentByte = 0;\n let bitsRead = 0;\n\n // Read columns in pairs, from right to left\n let readingUp = true;\n for (let columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) {\n if (columnIndex === 6) { // Skip whole column with vertical alignment pattern;\n columnIndex--;\n }\n for (let i = 0; i < dimension; i++) {\n const y = readingUp ? dimension - 1 - i : i;\n for (let columnOffset = 0; columnOffset < 2; columnOffset++) {\n const x = columnIndex - columnOffset;\n if (!functionPatternMask.get(x, y)) {\n bitsRead++;\n let bit = matrix.get(x, y);\n if (dataMask({y, x})) {\n bit = !bit;\n }\n currentByte = pushBit(bit, currentByte);\n if (bitsRead === 8) { // Whole bytes\n codewords.push(currentByte);\n bitsRead = 0;\n currentByte = 0;\n }\n }\n }\n }\n readingUp = !readingUp;\n }\n return codewords;\n}\n\nfunction readVersion(matrix: BitMatrix): Version {\n const dimension = matrix.height;\n\n const provisionalVersion = Math.floor((dimension - 17) / 4);\n if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code\n return VERSIONS[provisionalVersion - 1];\n }\n\n let topRightVersionBits = 0;\n for (let y = 5; y >= 0; y--) {\n for (let x = dimension - 9; x >= dimension - 11; x--) {\n topRightVersionBits = pushBit(matrix.get(x, y), topRightVersionBits);\n }\n }\n\n let bottomLeftVersionBits = 0;\n for (let x = 5; x >= 0; x--) {\n for (let y = dimension - 9; y >= dimension - 11; y--) {\n bottomLeftVersionBits = pushBit(matrix.get(x, y), bottomLeftVersionBits);\n }\n }\n\n let bestDifference = Infinity;\n let bestVersion: Version;\n for (const version of VERSIONS) {\n if (version.infoBits === topRightVersionBits || version.infoBits === bottomLeftVersionBits) {\n return version;\n }\n\n let difference = numBitsDiffering(topRightVersionBits, version.infoBits);\n if (difference < bestDifference) {\n bestVersion = version;\n bestDifference = difference;\n }\n\n difference = numBitsDiffering(bottomLeftVersionBits, version.infoBits);\n if (difference < bestDifference) {\n bestVersion = version;\n bestDifference = difference;\n }\n }\n // We can tolerate up to 3 bits of error since no two version info codewords will\n // differ in less than 8 bits.\n if (bestDifference <= 3) {\n return bestVersion;\n }\n}\n\nfunction readFormatInformation(matrix: BitMatrix) {\n let topLeftFormatInfoBits = 0;\n for (let x = 0; x <= 8; x++) {\n if (x !== 6) { // Skip timing pattern bit\n topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits);\n }\n }\n for (let y = 7; y >= 0; y--) {\n if (y !== 6) { // Skip timing pattern bit\n topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits);\n }\n }\n\n const dimension = matrix.height;\n let topRightBottomRightFormatInfoBits = 0;\n for (let y = dimension - 1; y >= dimension - 7; y--) { // bottom left\n topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits);\n }\n for (let x = dimension - 8; x < dimension; x++) { // top right\n topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits);\n }\n\n let bestDifference = Infinity;\n let bestFormatInfo = null;\n for (const {bits, formatInfo} of FORMAT_INFO_TABLE) {\n if (bits === topLeftFormatInfoBits || bits === topRightBottomRightFormatInfoBits) {\n return formatInfo;\n }\n let difference = numBitsDiffering(topLeftFormatInfoBits, bits);\n if (difference < bestDifference) {\n bestFormatInfo = formatInfo;\n bestDifference = difference;\n }\n if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option\n difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits);\n if (difference < bestDifference) {\n bestFormatInfo = formatInfo;\n bestDifference = difference;\n }\n }\n }\n // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match\n if (bestDifference <= 3) {\n return bestFormatInfo;\n }\n return null;\n}\n\nfunction getDataBlocks(codewords: number[], version: Version, ecLevel: number) {\n const ecInfo = version.errorCorrectionLevels[ecLevel];\n const dataBlocks: Array<{\n numDataCodewords: number;\n codewords: number[];\n }> = [];\n\n let totalCodewords = 0;\n ecInfo.ecBlocks.forEach(block => {\n for (let i = 0; i < block.numBlocks; i++) {\n dataBlocks.push({ numDataCodewords: block.dataCodewordsPerBlock, codewords: [] });\n totalCodewords += block.dataCodewordsPerBlock + ecInfo.ecCodewordsPerBlock;\n }\n });\n\n // In some cases the QR code will be malformed enough that we pull off more or less than we should.\n // If we pull off less there's nothing we can do.\n // If we pull off more we can safely truncate\n if (codewords.length < totalCodewords) {\n return null;\n }\n codewords = codewords.slice(0, totalCodewords);\n\n const shortBlockSize = ecInfo.ecBlocks[0].dataCodewordsPerBlock;\n // Pull codewords to fill the blocks up to the minimum size\n for (let i = 0; i < shortBlockSize; i++) {\n for (const dataBlock of dataBlocks) {\n dataBlock.codewords.push(codewords.shift());\n }\n }\n\n // If there are any large blocks, pull codewords to fill the last element of those\n if (ecInfo.ecBlocks.length > 1) {\n const smallBlockCount = ecInfo.ecBlocks[0].numBlocks;\n const largeBlockCount = ecInfo.ecBlocks[1].numBlocks;\n for (let i = 0; i < largeBlockCount; i++) {\n dataBlocks[smallBlockCount + i].codewords.push(codewords.shift());\n }\n }\n\n // Add the rest of the codewords to the blocks. These are the error correction codewords.\n while (codewords.length > 0) {\n for (const dataBlock of dataBlocks) {\n dataBlock.codewords.push(codewords.shift());\n }\n }\n\n return dataBlocks;\n}\n\nfunction decodeMatrix(matrix: BitMatrix) {\n const version = readVersion(matrix);\n if (!version) {\n return null;\n }\n\n const formatInfo = readFormatInformation(matrix);\n if (!formatInfo) {\n return null;\n }\n\n const codewords = readCodewords(matrix, version, formatInfo);\n const dataBlocks = getDataBlocks(codewords, version, formatInfo.errorCorrectionLevel);\n if (!dataBlocks) {\n return null;\n }\n\n // Count total number of data bytes\n const totalBytes = dataBlocks.reduce((a, b) => a + b.numDataCodewords, 0);\n const resultBytes = new Uint8ClampedArray(totalBytes);\n\n let resultIndex = 0;\n for (const dataBlock of dataBlocks) {\n const correctedBytes = rsDecode(dataBlock.codewords, dataBlock.codewords.length - dataBlock.numDataCodewords);\n if (!correctedBytes) {\n return null;\n }\n for (let i = 0; i < dataBlock.numDataCodewords; i++) {\n resultBytes[resultIndex++] = correctedBytes[i];\n }\n }\n\n try {\n return decodeData(resultBytes, version.versionNumber);\n } catch {\n return null;\n }\n}\n\nexport function decode(matrix: BitMatrix): DecodedQR {\n if (matrix == null) {\n return null;\n }\n const result = decodeMatrix(matrix);\n if (result) {\n return result;\n }\n // Decoding didn't work, try mirroring the QR across the topLeft -> bottomRight line.\n for (let x = 0; x < matrix.width; x++) {\n for (let y = x + 1; y < matrix.height; y++) {\n if (matrix.get(x, y) !== matrix.get(y, x)) {\n matrix.set(x, y, !matrix.get(x, y));\n matrix.set(y, x, !matrix.get(y, x));\n }\n }\n }\n return decodeMatrix(matrix);\n}\n","import {BitMatrix} from \"../BitMatrix\";\nimport {Point, QRLocation} from \"../locator\";\n\ninterface PerspectiveTransform {\n a11: number;\n a21: number;\n a31: number;\n a12: number;\n a22: number;\n a32: number;\n a13: number;\n a23: number;\n a33: number;\n}\n\nfunction squareToQuadrilateral(p1: Point, p2: Point, p3: Point, p4: Point): PerspectiveTransform {\n const dx3 = p1.x - p2.x + p3.x - p4.x;\n const dy3 = p1.y - p2.y + p3.y - p4.y;\n if (dx3 === 0 && dy3 === 0) { // Affine\n return {\n a11: p2.x - p1.x,\n a12: p2.y - p1.y,\n a13: 0,\n a21: p3.x - p2.x,\n a22: p3.y - p2.y,\n a23: 0,\n a31: p1.x,\n a32: p1.y,\n a33: 1,\n };\n } else {\n const dx1 = p2.x - p3.x;\n const dx2 = p4.x - p3.x;\n const dy1 = p2.y - p3.y;\n const dy2 = p4.y - p3.y;\n const denominator = dx1 * dy2 - dx2 * dy1;\n const a13 = (dx3 * dy2 - dx2 * dy3) / denominator;\n const a23 = (dx1 * dy3 - dx3 * dy1) / denominator;\n return {\n a11: p2.x - p1.x + a13 * p2.x,\n a12: p2.y - p1.y + a13 * p2.y,\n a13,\n a21: p4.x - p1.x + a23 * p4.x,\n a22: p4.y - p1.y + a23 * p4.y,\n a23,\n a31: p1.x,\n a32: p1.y,\n a33: 1,\n };\n }\n}\n\nfunction quadrilateralToSquare(p1: Point, p2: Point, p3: Point, p4: Point): PerspectiveTransform {\n // Here, the adjoint serves as the inverse:\n const sToQ = squareToQuadrilateral(p1, p2, p3, p4);\n return {\n a11: sToQ.a22 * sToQ.a33 - sToQ.a23 * sToQ.a32,\n a12: sToQ.a13 * sToQ.a32 - sToQ.a12 * sToQ.a33,\n a13: sToQ.a12 * sToQ.a23 - sToQ.a13 * sToQ.a22,\n a21: sToQ.a23 * sToQ.a31 - sToQ.a21 * sToQ.a33,\n a22: sToQ.a11 * sToQ.a33 - sToQ.a13 * sToQ.a31,\n a23: sToQ.a13 * sToQ.a21 - sToQ.a11 * sToQ.a23,\n a31: sToQ.a21 * sToQ.a32 - sToQ.a22 * sToQ.a31,\n a32: sToQ.a12 * sToQ.a31 - sToQ.a11 * sToQ.a32,\n a33: sToQ.a11 * sToQ.a22 - sToQ.a12 * sToQ.a21,\n };\n}\n\nfunction times(a: PerspectiveTransform, b: PerspectiveTransform): PerspectiveTransform {\n return {\n a11: a.a11 * b.a11 + a.a21 * b.a12 + a.a31 * b.a13,\n a12: a.a12 * b.a11 + a.a22 * b.a12 + a.a32 * b.a13,\n a13: a.a13 * b.a11 + a.a23 * b.a12 + a.a33 * b.a13,\n a21: a.a11 * b.a21 + a.a21 * b.a22 + a.a31 * b.a23,\n a22: a.a12 * b.a21 + a.a22 * b.a22 + a.a32 * b.a23,\n a23: a.a13 * b.a21 + a.a23 * b.a22 + a.a33 * b.a23,\n a31: a.a11 * b.a31 + a.a21 * b.a32 + a.a31 * b.a33,\n a32: a.a12 * b.a31 + a.a22 * b.a32 + a.a32 * b.a33,\n a33: a.a13 * b.a31 + a.a23 * b.a32 + a.a33 * b.a33,\n };\n}\n\nexport function extract(image: BitMatrix, location: QRLocation) {\n const qToS = quadrilateralToSquare(\n {x: 3.5, y: 3.5},\n {x: location.dimension - 3.5, y: 3.5},\n {x: location.dimension - 6.5, y: location.dimension - 6.5},\n {x: 3.5, y: location.dimension - 3.5},\n );\n const sToQ = squareToQuadrilateral(location.topLeft, location.topRight, location.alignmentPattern, location.bottomLeft);\n const transform = times(sToQ, qToS);\n\n const matrix = BitMatrix.createEmpty(location.dimension, location.dimension);\n const mappingFunction = (x: number, y: number) => {\n const denominator = transform.a13 * x + transform.a23 * y + transform.a33;\n return {\n x: (transform.a11 * x + transform.a21 * y + transform.a31) / denominator,\n y: (transform.a12 * x + transform.a22 * y + transform.a32) / denominator,\n };\n };\n\n for (let y = 0; y < location.dimension; y++) {\n for (let x = 0; x < location.dimension; x++) {\n const xValue = x + 0.5;\n const yValue = y + 0.5;\n const sourcePixel = mappingFunction(xValue, yValue);\n matrix.set(x, y, image.get(Math.floor(sourcePixel.x), Math.floor(sourcePixel.y)));\n }\n }\n\n return {\n matrix,\n mappingFunction,\n };\n}\n","import { BitMatrix } from \"../BitMatrix\";\n\nconst MAX_FINDERPATTERNS_TO_SEARCH = 5;\nconst MIN_QUAD_RATIO = 0.5;\nconst MAX_QUAD_RATIO = 1.5;\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface QRLocation {\n topRight: Point;\n bottomLeft: Point;\n topLeft: Point;\n alignmentPattern: Point;\n dimension: number;\n}\n\nconst distance = (a: Point, b: Point) => Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);\n\nfunction sum(values: number[]) {\n return values.reduce((a, b) => a + b);\n}\n\n// Takes three finder patterns and organizes them into topLeft, topRight, etc\nfunction reorderFinderPatterns(pattern1: Point, pattern2: Point, pattern3: Point) {\n // Find distances between pattern centers\n const oneTwoDistance = distance(pattern1, pattern2);\n const twoThreeDistance = distance(pattern2, pattern3);\n const oneThreeDistance = distance(pattern1, pattern3);\n\n let bottomLeft: Point;\n let topLeft: Point;\n let topRight: Point;\n\n // Assume one closest to other two is B; A and C will just be guesses at first\n if (twoThreeDistance >= oneTwoDistance && twoThreeDistance >= oneThreeDistance) {\n [bottomLeft, topLeft, topRight] = [pattern2, pattern1, pattern3];\n } else if (oneThreeDistance >= twoThreeDistance && oneThreeDistance >= oneTwoDistance) {\n [bottomLeft, topLeft, topRight] = [pattern1, pattern2, pattern3];\n } else {\n [bottomLeft, topLeft, topRight] = [pattern1, pattern3, pattern2];\n }\n\n // Use cross product to figure out whether bottomLeft (A) and topRight (C) are correct or flipped in relation to topLeft (B)\n // This asks whether BC x BA has a positive z component, which is the arrangement we want. If it's negative, then\n // we've got it flipped around and should swap topRight and bottomLeft.\n if (((topRight.x - topLeft.x) * (bottomLeft.y - topLeft.y)) - ((topRight.y - topLeft.y) * (bottomLeft.x - topLeft.x)) < 0) {\n [bottomLeft, topRight] = [topRight, bottomLeft];\n }\n\n return { bottomLeft, topLeft, topRight };\n}\n\n// Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns\nfunction computeDimension(topLeft: Point, topRight: Point, bottomLeft: Point, matrix: BitMatrix) {\n const moduleSize = (\n sum(countBlackWhiteRun(topLeft, bottomLeft, matrix, 5)) / 7 + // Divide by 7 since the ratio is 1:1:3:1:1\n sum(countBlackWhiteRun(topLeft, topRight, matrix, 5)) / 7 +\n sum(countBlackWhiteRun(bottomLeft, topLeft, matrix, 5)) / 7 +\n sum(countBlackWhiteRun(topRight, topLeft, matrix, 5)) / 7\n ) / 4;\n\n if (moduleSize < 1) {\n throw new Error(\"Invalid module size\");\n }\n\n const topDimension = Math.round(distance(topLeft, topRight) / moduleSize);\n const sideDimension = Math.round(distance(topLeft, bottomLeft) / moduleSize);\n let dimension = Math.floor((topDimension + sideDimension) / 2) + 7;\n switch (dimension % 4) {\n case 0:\n dimension++;\n break;\n case 2:\n dimension--;\n break;\n }\n return { dimension, moduleSize };\n}\n\n// Takes an origin point and an end point and counts the sizes of the black white run from the origin towards the end point.\n// Returns an array of elements, representing the pixel size of the black white run.\n// Uses a variant of http://en.wikipedia.org/wiki/Bresenham's_line_algorithm\nfunction countBlackWhiteRunTowardsPoint(origin: Point, end: Point, matrix: BitMatrix, length: number) {\n const switchPoints: Point[] = [{x: Math.floor(origin.x), y: Math.floor(origin.y)}];\n const steep = Math.abs(end.y - origin.y) > Math.abs(end.x - origin.x);\n\n let fromX: number;\n let fromY: number;\n let toX: number;\n let toY: number;\n if (steep) {\n fromX = Math.floor(origin.y);\n fromY = Math.floor(origin.x);\n toX = Math.floor(end.y);\n toY = Math.floor(end.x);\n } else {\n fromX = Math.floor(origin.x);\n fromY = Math.floor(origin.y);\n toX = Math.floor(end.x);\n toY = Math.floor(end.y);\n }\n\n const dx = Math.abs(toX - fromX);\n const dy = Math.abs(toY - fromY);\n let error = Math.floor(-dx / 2);\n const xStep = fromX < toX ? 1 : -1;\n const yStep = fromY < toY ? 1 : -1;\n\n let currentPixel = true;\n // Loop up until x == toX, but not beyond\n for (let x = fromX, y = fromY; x !== toX + xStep; x += xStep) {\n // Does current pixel mean we have moved white to black or vice versa?\n // Scanning black in state 0,2 and white in state 1, so if we find the wrong\n // color, advance to next state or end if we are in state 2 already\n const realX = steep ? y : x;\n const realY = steep ? x : y;\n if (matrix.get(realX, realY) !== currentPixel) {\n currentPixel = !currentPixel;\n switchPoints.push({x: realX, y: realY});\n if (switchPoints.length === length + 1) {\n break;\n }\n }\n error += dy;\n if (error > 0) {\n if (y === toY) {\n break;\n }\n y += yStep;\n error -= dx;\n }\n }\n const distances: number[] = [];\n for (let i = 0; i < length; i++) {\n if (switchPoints[i] && switchPoints[i + 1]) {\n distances.push(distance(switchPoints[i], switchPoints[i + 1]));\n } else {\n distances.push(0);\n }\n }\n return distances;\n}\n\n// Takes an origin point and an end point and counts the sizes of the black white run in the origin point\n// along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes\n// of the black white run. Takes a length which represents the number of switches from black to white to look for.\nfunction countBlackWhiteRun(origin: Point, end: Point, matrix: BitMatrix, length: number) {\n const rise = end.y - origin.y;\n const run = end.x - origin.x;\n\n const towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2));\n const awayFromEnd = countBlackWhiteRunTowardsPoint(origin, {x: origin.x - run, y: origin.y - rise}, matrix, Math.ceil(length / 2));\n\n const middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel\n return awayFromEnd.concat(middleValue).concat(...towardsEnd);\n}\n\n// Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the \"error\" -\n// that is the amount the run diverges from the expected ratio\nfunction scoreBlackWhiteRun(sequence: number[], ratios: number[]) {\n const averageSize = sum(sequence) / sum(ratios);\n let error = 0;\n ratios.forEach((ratio, i) => {\n error += (sequence[i] - ratio * averageSize) ** 2;\n });\n\n return { averageSize, error };\n}\n\n// Takes an X,Y point and an array of sizes and scores the point against those ratios.\n// For example for a finder pattern takes the ratio list of 1:1:3:1:1 and checks horizontal, vertical and diagonal ratios\n// against that.\nfunction scorePattern(point: Point, ratios: number[], matrix: BitMatrix) {\n try {\n const horizontalRun = countBlackWhiteRun(point, {x: -1, y: point.y}, matrix, ratios.length);\n const verticalRun = countBlackWhiteRun(point, {x: point.x, y: -1}, matrix, ratios.length);\n\n const topLeftPoint = {\n x: Math.max(0, point.x - point.y) - 1,\n y: Math.max(0, point.y - point.x) - 1,\n };\n const topLeftBottomRightRun = countBlackWhiteRun(point, topLeftPoint, matrix, ratios.length);\n\n const bottomLeftPoint = {\n x: Math.min(matrix.width, point.x + point.y) + 1,\n y: Math.min(matrix.height, point.y + point.x) + 1,\n };\n const bottomLeftTopRightRun = countBlackWhiteRun(point, bottomLeftPoint, matrix, ratios.length);\n\n const horzError = scoreBlackWhiteRun(horizontalRun, ratios);\n const vertError = scoreBlackWhiteRun(verticalRun, ratios);\n const diagDownError = scoreBlackWhiteRun(topLeftBottomRightRun, ratios);\n const diagUpError = scoreBlackWhiteRun(bottomLeftTopRightRun, ratios);\n\n const ratioError = Math.sqrt(horzError.error * horzError.error +\n vertError.error * vertError.error +\n diagDownError.error * diagDownError.error +\n diagUpError.error * diagUpError.error);\n\n const avgSize = (horzError.averageSize + vertError.averageSize + diagDownError.averageSize + diagUpError.averageSize) / 4;\n\n const sizeError = ((horzError.averageSize - avgSize) ** 2 +\n (vertError.averageSize - avgSize) ** 2 +\n (diagDownError.averageSize - avgSize) ** 2 +\n (diagUpError.averageSize - avgSize) ** 2) / avgSize;\n return ratioError + sizeError;\n } catch {\n return Infinity;\n }\n}\n\nfunction recenterLocation(matrix: BitMatrix, p: Point): Point {\n let leftX = Math.round(p.x);\n while (matrix.get(leftX, Math.round(p.y))) {\n leftX--;\n }\n let rightX = Math.round(p.x);\n while (matrix.get(rightX, Math.round(p.y))) {\n rightX++;\n }\n const x = (leftX + rightX) / 2;\n\n let topY = Math.round(p.y);\n while (matrix.get(Math.round(x), topY)) {\n topY--;\n }\n let bottomY = Math.round(p.y);\n while (matrix.get(Math.round(x), bottomY)) {\n bottomY++;\n }\n const y = (topY + bottomY) / 2;\n\n return { x, y };\n}\n\ninterface Quad {\n top: {\n startX: number;\n endX: number;\n y: number;\n };\n bottom: {\n startX: number;\n endX: number;\n y: number;\n };\n}\n\nexport function locate(matrix: BitMatrix): QRLocation[] {\n const finderPatternQuads: Quad[] = [];\n let activeFinderPatternQuads: Quad[] = [];\n const alignmentPatternQuads: Quad[] = [];\n let activeAlignmentPatternQuads: Quad[] = [];\n\n for (let y = 0; y <= matrix.height; y++) {\n let length = 0;\n let lastBit = false;\n let scans = [0, 0, 0, 0, 0];\n\n for (let x = -1; x <= matrix.width; x++) {\n const v = matrix.get(x, y);\n if (v === lastBit) {\n length++;\n } else {\n scans = [scans[1], scans[2], scans[3], scans[4], length];\n length = 1;\n lastBit = v;\n\n // Do the last 5 color changes ~ match the expected ratio for a finder pattern? 1:1:3:1:1 of b:w:b:w:b\n const averageFinderPatternBlocksize = sum(scans) / 7;\n const validFinderPattern =\n Math.abs(scans[0] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&\n Math.abs(scans[1] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&\n Math.abs(scans[2] - 3 * averageFinderPatternBlocksize) < 3 * averageFinderPatternBlocksize &&\n Math.abs(scans[3] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&\n Math.abs(scans[4] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&\n !v; // And make sure the current pixel is white since finder patterns are bordered in white\n\n // Do the last 3 color changes ~ match the expected ratio for an alignment pattern? 1:1:1 of w:b:w\n const averageAlignmentPatternBlocksize = sum(scans.slice(-3)) / 3;\n const validAlignmentPattern =\n Math.abs(scans[2] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&\n Math.abs(scans[3] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&\n Math.abs(scans[4] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&\n v; // Is the current pixel black since alignment patterns are bordered in black\n\n if (validFinderPattern) {\n // Compute the start and end x values of the large center black square\n const endX = x - scans[3] - scans[4];\n const startX = endX - scans[2];\n\n const line = { startX, endX, y };\n // Is there a quad directly above the current spot? If so, extend it with the new line. Otherwise, create a new quad with\n // that line as the starting point.\n const matchingQuads = activeFinderPatternQuads.filter(q =>\n (startX >= q.bottom.startX && startX <= q.bottom.endX) ||\n (endX >= q.bottom.startX && startX <= q.bottom.endX) ||\n (startX <= q.bottom.startX && endX >= q.bottom.endX && (\n (scans[2] / (q.bottom.endX - q.bottom.startX)) < MAX_QUAD_RATIO &&\n (scans[2] / (q.bottom.endX - q.bottom.startX)) > MIN_QUAD_RATIO\n )),\n );\n if (matchingQuads.length > 0) {\n matchingQuads[0].bottom = line;\n } else {\n activeFinderPatternQuads.push({ top: line, bottom: line });\n }\n }\n if (validAlignmentPattern) {\n // Compute the start and end x values of the center black square\n const endX = x - scans[4];\n const startX = endX - scans[3];\n\n const line = { startX, y, endX };\n // Is there a quad directly above the current spot? If so, extend it with the new line. Otherwise, create a new quad with\n // that line as the starting point.\n const matchingQuads = activeAlignmentPatternQuads.filter(q =>\n (startX >= q.bottom.startX && startX <= q.bottom.endX) ||\n (endX >= q.bottom.startX && startX <= q.bottom.endX) ||\n (startX <= q.bottom.startX && endX >= q.bottom.endX && (\n (scans[2] / (q.bottom.endX - q.bottom.startX)) < MAX_QUAD_RATIO &&\n (scans[2] / (q.bottom.endX - q.bottom.startX)) > MIN_QUAD_RATIO\n )),\n );\n if (matchingQuads.length > 0) {\n matchingQuads[0].bottom = line;\n } else {\n activeAlignmentPatternQuads.push({ top: line, bottom: line });\n }\n }\n }\n }\n finderPatternQuads.push(...activeFinderPatternQuads.filter(q => q.bottom.y !== y && q.bottom.y - q.top.y >= 2));\n activeFinderPatternQuads = activeFinderPatternQuads.filter(q => q.bottom.y === y);\n\n alignmentPatternQuads.push(...activeAlignmentPatternQuads.filter(q => q.bottom.y !== y));\n activeAlignmentPatternQuads = activeAlignmentPatternQuads.filter(q => q.bottom.y === y);\n\n }\n\n finderPatternQuads.push(...activeFinderPatternQuads.filter(q => q.bottom.y - q.top.y >= 2));\n alignmentPatternQuads.push(...activeAlignmentPatternQuads);\n\n // Refactored from cozmo/jsQR to (hopefully) circumvent an issue in Safari 13+ on both Mac and iOS (also including\n // iOS Chrome and other Safari iOS derivatives). Safari was very occasionally and apparently not deterministically\n // throwing a \"RangeError: Array size is not a small enough positive integer.\" exception seemingly within the second\n // .map of the original code (here the second for-loop). This second .map contained a nested .map call over the same\n // array instance which was the chained result from previous calls to .map, .filter and .sort which potentially caused\n // this bug in Safari?\n // Also see https://github.com/cozmo/jsQR/issues/157 and https://bugs.webkit.org/show_bug.cgi?id=211619#c3\n const scoredFinderPatternPositions: Array = [];\n for (const quad of finderPatternQuads) {\n if (quad.bottom.y - quad.top.y < 2) {\n // All quads must be at least 2px tall since the center square is larger than a block\n continue;\n }\n\n // calculate quad center\n const x = (quad.top.startX + quad.top.endX + quad.bottom.startX + quad.bottom.endX) / 4;\n const y = (quad.top.y + quad.bottom.y + 1) / 2;\n if (!matrix.get(Math.round(x), Math.round(y))) {\n continue;\n }\n\n const lengths = [quad.top.endX - quad.top.startX, quad.bottom.endX - quad.bottom.startX, quad.bottom.y - quad.top.y + 1];\n const size = sum(lengths) / lengths.length;\n // Initial scoring of finder pattern quads by looking at their ratios, not taking into account position\n const score = scorePattern({x: Math.round(x), y: Math.round(y)}, [1, 1, 3, 1, 1], matrix);\n scoredFinderPatternPositions.push({ score, x, y, size });\n }\n if (scoredFinderPatternPositions.length < 3) {\n // A QR code has 3 finder patterns, therefore we need at least 3 candidates.\n return null;\n }\n scoredFinderPatternPositions.sort((a, b) => a.score - b.score);\n\n // Now take the top finder pattern options and try to find 2 other options with a similar size.\n const finderPatternGroups: Array<{ points: [Point, Point, Point], score: number }> = [];\n for (let i = 0; i < Math.min(scoredFinderPatternPositions.length, MAX_FINDERPATTERNS_TO_SEARCH); ++i) {\n const point = scoredFinderPatternPositions[i];\n const otherPoints: typeof scoredFinderPatternPositions = [];\n\n for (const otherPoint of scoredFinderPatternPositions) {\n if (otherPoint === point) {\n continue;\n }\n otherPoints.push({\n ...otherPoint,\n score: otherPoint.score + ((otherPoint.size - point.size) ** 2) / point.size, // score similarity of sizes\n });\n }\n otherPoints.sort((a, b) => a.score - b.score);\n\n finderPatternGroups.push({\n points: [point, otherPoints[0], otherPoints[1]], // note that otherPoints.length >= 2 as scoredFinderPatternPositions.length >= 3\n score: point.score + otherPoints[0].score + otherPoints[1].score, // total combined score of the three points in the group\n });\n }\n finderPatternGroups.sort((a, b) => a.score - b.score);\n const bestFinderPatternGroup = finderPatternGroups[0];\n\n const { topRight, topLeft, bottomLeft } = reorderFinderPatterns(...bestFinderPatternGroup.points);\n const alignment = findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft);\n const result: QRLocation[] = [];\n if (alignment) {\n result.push({\n alignmentPattern: { x: alignment.alignmentPattern.x, y: alignment.alignmentPattern.y },\n bottomLeft: {x: bottomLeft.x, y: bottomLeft.y },\n dimension: alignment.dimension,\n topLeft: {x: topLeft.x, y: topLeft.y },\n topRight: {x: topRight.x, y: topRight.y },\n });\n }\n\n // We normally use the center of the quads as the location of the tracking points, which is optimal for most cases and will account\n // for a skew in the image. However, In some cases, a slight skew might not be real and instead be caused by image compression\n // errors and/or low resolution. For those cases, we'd be better off centering the point exactly in the middle of the black area. We\n // compute and return the location data for the naively centered points as it is little additional work and allows for multiple\n // attempts at decoding harder images.\n const midTopRight = recenterLocation(matrix, topRight);\n const midTopLeft = recenterLocation(matrix, topLeft);\n const midBottomLeft = recenterLocation(matrix, bottomLeft);\n const centeredAlignment = findAlignmentPattern(matrix, alignmentPatternQuads, midTopRight, midTopLeft, midBottomLeft);\n if (centeredAlignment) {\n result.push({\n alignmentPattern: { x: centeredAlignment.alignmentPattern.x, y: centeredAlignment.alignmentPattern.y },\n bottomLeft: { x: midBottomLeft.x, y: midBottomLeft. y },\n topLeft: { x: midTopLeft.x, y: midTopLeft. y },\n topRight: { x: midTopRight.x, y: midTopRight. y },\n dimension: centeredAlignment.dimension,\n });\n }\n\n if (result.length === 0) {\n return null;\n }\n\n return result;\n}\n\nfunction findAlignmentPattern(matrix: BitMatrix, alignmentPatternQuads: Quad[], topRight: Point, topLeft: Point, bottomLeft: Point) {\n // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code.\n // We'll use these to help find the alignment pattern but also later when we do the extraction.\n let dimension: number;\n let moduleSize: number;\n try {\n ({ dimension, moduleSize } = computeDimension(topLeft, topRight, bottomLeft, matrix));\n } catch (e) {\n return null;\n }\n\n // Now find the alignment pattern\n const bottomRightFinderPattern = { // Best guess at where a bottomRight finder pattern would be\n x: topRight.x - topLeft.x + bottomLeft.x,\n y: topRight.y - topLeft.y + bottomLeft.y,\n };\n const modulesBetweenFinderPatterns = ((distance(topLeft, bottomLeft) + distance(topLeft, topRight)) / 2 / moduleSize);\n const correctionToTopLeft = 1 - (3 / modulesBetweenFinderPatterns);\n const expectedAlignmentPattern = {\n x: topLeft.x + correctionToTopLeft * (bottomRightFinderPattern.x - topLeft.x),\n y: topLeft.y + correctionToTopLeft * (bottomRightFinderPattern.y - topLeft.y),\n };\n\n const alignmentPatterns = alignmentPatternQuads\n .map(q => {\n const x = (q.top.startX + q.top.endX + q.bottom.startX + q.bottom.endX) / 4;\n const y = (q.top.y + q.bottom.y + 1) / 2;\n if (!matrix.get(Math.floor(x), Math.floor(y))) {\n return;\n }\n\n const sizeScore = scorePattern({x: Math.floor(x), y: Math.floor(y)}, [1, 1, 1], matrix);\n const score = sizeScore + distance({x, y}, expectedAlignmentPattern);\n return { x, y, score };\n })\n .filter(v => !!v)\n .sort((a, b) => a.score - b.score);\n\n // If there are less than 15 modules between finder patterns it's a version 1 QR code and as such has no alignmemnt pattern\n // so we can only use our best guess.\n const alignmentPattern = modulesBetweenFinderPatterns >= 15 && alignmentPatterns.length ? alignmentPatterns[0] : expectedAlignmentPattern;\n\n return { alignmentPattern, dimension };\n}\n","import {binarize} from \"./binarizer\";\nimport {BitMatrix} from \"./BitMatrix\";\nimport {Chunks} from \"./decoder/decodeData\";\nimport {decode} from \"./decoder/decoder\";\nimport { Version } from \"./decoder/version\";\nimport {extract} from \"./extractor\";\nimport {locate, Point} from \"./locator\";\n\nexport interface QRCode {\n binaryData: number[];\n data: string;\n chunks: Chunks;\n version: number;\n location: {\n topRightCorner: Point;\n topLeftCorner: Point;\n bottomRightCorner: Point;\n bottomLeftCorner: Point;\n\n topRightFinderPattern: Point;\n topLeftFinderPattern: Point;\n bottomLeftFinderPattern: Point;\n\n bottomRightAlignmentPattern?: Point;\n };\n matrix: BitMatrix;\n}\n\nfunction scan(matrix: BitMatrix): QRCode | null {\n const locations = locate(matrix);\n if (!locations) {\n return null;\n }\n\n for (const location of locations) {\n const extracted = extract(matrix, location);\n const decoded = decode(extracted.matrix);\n if (decoded) {\n return {\n binaryData: decoded.bytes,\n data: decoded.text,\n chunks: decoded.chunks,\n version: decoded.version,\n location: {\n topRightCorner: extracted.mappingFunction(location.dimension, 0),\n topLeftCorner: extracted.mappingFunction(0, 0),\n bottomRightCorner: extracted.mappingFunction(location.dimension, location.dimension),\n bottomLeftCorner: extracted.mappingFunction(0, location.dimension),\n\n topRightFinderPattern: location.topRight,\n topLeftFinderPattern: location.topLeft,\n bottomLeftFinderPattern: location.bottomLeft,\n\n bottomRightAlignmentPattern: location.alignmentPattern,\n },\n matrix: extracted.matrix,\n };\n }\n }\n return null;\n}\n\nexport interface Options {\n inversionAttempts?: \"dontInvert\" | \"onlyInvert\" | \"attemptBoth\" | \"invertFirst\";\n greyScaleWeights?: GreyscaleWeights;\n canOverwriteImage?: boolean;\n}\n\nexport interface GreyscaleWeights {\n red: number;\n green: number;\n blue: number;\n useIntegerApproximation?: boolean;\n}\n\nconst defaultOptions: Options = {\n inversionAttempts: \"attemptBoth\",\n greyScaleWeights: {\n red: 0.2126,\n green: 0.7152,\n blue: 0.0722,\n useIntegerApproximation: false,\n },\n canOverwriteImage: true,\n};\n\nfunction mergeObject(target: any, src: any) {\n Object.keys(src).forEach(opt => { // Sad implementation of Object.assign since we target es5 not es6\n target[opt] = src[opt];\n });\n}\n\nfunction jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions: Options = {}): QRCode | null {\n const options = Object.create(null);\n mergeObject(options, defaultOptions);\n mergeObject(options, providedOptions);\n\n const tryInvertedFirst = options.inversionAttempts === \"onlyInvert\" || options.inversionAttempts === \"invertFirst\";\n const shouldInvert = options.inversionAttempts === \"attemptBoth\" || tryInvertedFirst;\n const {binarized, inverted} = binarize(data, width, height, shouldInvert, options.greyScaleWeights,\n options.canOverwriteImage);\n let result = scan(tryInvertedFirst ? inverted : binarized);\n if (!result && (options.inversionAttempts === \"attemptBoth\" || options.inversionAttempts === \"invertFirst\")) {\n result = scan(tryInvertedFirst ? binarized : inverted);\n }\n return result;\n}\n\n(jsQR as any).default = jsQR;\nexport default jsQR;\n","// @ts-ignore jsqr-es6 does not provide types currently\nimport jsQR from '../node_modules/jsqr-es6/dist/jsQR.js';\n\ntype GrayscaleWeights = {\n red: number,\n green: number,\n blue: number,\n useIntegerApproximation: boolean,\n};\n\nlet inversionAttempts: 'dontInvert' | 'onlyInvert' | 'attemptBoth' = 'dontInvert';\nlet grayscaleWeights: GrayscaleWeights = {\n // weights for quick luma integer approximation (https://en.wikipedia.org/wiki/YUV#Full_swing_for_BT.601)\n red: 77,\n green: 150,\n blue: 29,\n useIntegerApproximation: true,\n};\n\nself.onmessage = event => {\n const id = event['data']['id'];\n const type = event['data']['type'];\n const data = event['data']['data'];\n\n switch (type) {\n case 'decode':\n decode(data, id);\n break;\n case 'grayscaleWeights':\n setGrayscaleWeights(data);\n break;\n case 'inversionMode':\n setInversionMode(data);\n break;\n case 'close':\n // close after earlier messages in the event loop finished processing\n self.close();\n break;\n }\n};\n\nfunction decode(data: { data: Uint8ClampedArray, width: number, height: number }, requestId: number): void {\n const rgbaData = data['data'];\n const width = data['width'];\n const height = data['height'];\n const result = jsQR(rgbaData, width, height, {\n inversionAttempts: inversionAttempts,\n greyScaleWeights: grayscaleWeights,\n });\n if (!result) {\n (self as unknown as Worker).postMessage({\n id: requestId,\n type: 'qrResult',\n data: null,\n });\n return;\n }\n\n (self as unknown as Worker).postMessage({\n id: requestId,\n type: 'qrResult',\n data: result.data,\n // equivalent to cornerPoints of native BarcodeDetector\n cornerPoints: [\n result.location.topLeftCorner,\n result.location.topRightCorner,\n result.location.bottomRightCorner,\n result.location.bottomLeftCorner,\n ],\n });\n}\n\nfunction setGrayscaleWeights(data: GrayscaleWeights) {\n // update grayscaleWeights in a closure compiler compatible fashion\n grayscaleWeights.red = data['red'];\n grayscaleWeights.green = data['green'];\n grayscaleWeights.blue = data['blue'];\n grayscaleWeights.useIntegerApproximation = data['useIntegerApproximation'];\n}\n\nfunction setInversionMode(inversionMode: 'original' | 'invert' | 'both') {\n switch (inversionMode) {\n case 'original':\n inversionAttempts = 'dontInvert';\n break;\n case 'invert':\n inversionAttempts = 'onlyInvert';\n break;\n case 'both':\n inversionAttempts = 'attemptBoth';\n break;\n default:\n throw new Error('Invalid inversion mode');\n }\n}\n"],"names":["BitMatrix","constructor","data","width","height","length","createEmpty","Uint8ClampedArray","get","x","y","set","v","setRegion","left","top","Matrix","buffer","bufferSize","Error","value","BitStream","bytes","readBits","numBits","available","toString","result","bitOffset","byteOffset","bitsToNotRead","toRead","Mode","ModeByte","decodeByte","stream","size","text","i","push","b","decodeURIComponent","map","substr","join","decode","version","chunks","mode","Terminator","ECI","type","assignmentNumber","Numeric","num","a","c","Alphanumeric","AlphanumericCharacterCodes","charCodeAt","Byte","byteResult","Kanji","Math","floor","k","StructuredAppend","currentSequence","totalSequence","parity","GenericGFPoly","field","coefficients","coefficientsLength","firstNonZero","zero","degree","isZero","getCoefficient","addOrSubtract","other","smallerCoefficients","largerCoefficients","lengthDiff","sumDiff","multiply","scalar","product","multiplyPoly","aLength","j","bLength","addOrSubtractGF","aCoeff","bCoefficients","multiplyByMonomial","coefficient","evaluateAt","forEach","GenericGF","primitive","genBase","generatorBase","expTable","Array","logTable","from","one","inverse","buildMonomial","log","exp","runEuclideanAlgorithm","R","tLast","t","r","rLast","rLastLast","q","dltInverse","degreeDiff","scale","tLastLast","sigmaTildeAtZero","twoS","outputBytes","error","s","syndromeCoefficients","evaluation","syndrome","sigmaOmega","numErrors","errorLocator","errorCount","errorLocations","denominator","xiInverse","errorEvaluator","position","infoBits","versionNumber","alignmentPatternCenters","errorCorrectionLevels","ecCodewordsPerBlock","ecBlocks","numBlocks","dataCodewordsPerBlock","numBitsDiffering","z","bitCount","pushBit","bit","byte","bits","formatInfo","errorCorrectionLevel","dataMask","p","readCodewords","matrix","dimension","bitsRead","currentByte","readingUp","columnIndex","columnOffset","codewords","readVersion","provisionalVersion","VERSIONS","topRightVersionBits","bottomLeftVersionBits","bestDifference","Infinity","bestVersion","difference","readFormatInformation","topLeftFormatInfoBits","topRightBottomRightFormatInfoBits","bestFormatInfo","getDataBlocks","ecLevel","totalCodewords","ecInfo","block","dataBlocks","numDataCodewords","slice","shortBlockSize","dataBlock","shift","largeBlockCount","smallBlockCount","decodeMatrix","resultIndex","correctedBytes","resultBytes","decodeData","squareToQuadrilateral","p1","p2","p3","p4","dx3","dy3","a11","a12","a13","a21","a22","a23","a31","a32","a33","quadrilateralToSquare","sToQ","extract","image","location","qToS","sourcePixel","mappingFunction","sum","values","reduce","reorderFinderPatterns","pattern1","pattern2","pattern3","bottomLeft","topLeft","topRight","twoThreeDistance","oneTwoDistance","oneThreeDistance","computeDimension","countBlackWhiteRun","moduleSize","topDimension","sideDimension","countBlackWhiteRunTowardsPoint","origin","end","steep","fromX","fromY","toX","toY","dx","currentPixel","xStep","realX","realY","switchPoints","dy","yStep","distances","distance","awayFromEnd","concat","middleValue","towardsEnd","scoreBlackWhiteRun","sequence","ratios","ratio","averageSize","scorePattern","point","max","min","vertError","diagDownError","diagUpError","avgSize","recenterLocation","leftX","round","rightX","topY","bottomY","locate","activeFinderPatternQuads","activeAlignmentPatternQuads","lastBit","scans","abs","averageFinderPatternBlocksize","averageAlignmentPatternBlocksize","validFinderPattern","startX","endX","bottom","matchingQuads","line","validAlignmentPattern","finderPatternQuads","filter","alignmentPatternQuads","quad","scoredFinderPatternPositions","score","sort","otherPoint","otherPoints","finderPatternGroups","points","alignment","alignmentPattern","midTopRight","midTopLeft","midBottomLeft","centeredAlignment","findAlignmentPattern","e","correctionToTopLeft","sizeScore","expectedAlignmentPattern","scan","locations","decoded","binaryData","topRightCorner","extracted","topLeftCorner","bottomRightCorner","bottomLeftCorner","topRightFinderPattern","topLeftFinderPattern","bottomLeftFinderPattern","bottomRightAlignmentPattern","inversionAttempts","greyScaleWeights","red","green","blue","useIntegerApproximation","canOverwriteImage","mergeObject","target","src","Object","keys","opt","jsQR","providedOptions","options","defaultOptions","shouldInvert","pixelCount","bufferOffset","greyscaleBuffer","greyscaleWeights","greyscalePixels","blackPointsBuffer","blackPointsCount","verticalRegionCount","verticalRegion","hortizontalRegion","horizontalRegionCount","pixelLumosity","average","blackPoints","averageNeighborBlackPoint","binarized","binarizedBuffer","inverted","returnInverted","invertedBuffer","xRegion","yRegion","lum","threshold","tryInvertedFirst","default","grayscaleWeights","self","onmessage","event","self.onmessage","postMessage","id","cornerPoints","close"],"mappings":"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"} \ No newline at end of file diff --git a/client/qr-scanner.umd.min.js b/client/qr-scanner.umd.min.js new file mode 100644 index 0000000..667ac2f --- /dev/null +++ b/client/qr-scanner.umd.min.js @@ -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=''; +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",''), +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 {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;asetTimeout(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 diff --git a/client/qr-scanner.umd.min.js.map b/client/qr-scanner.umd.min.js.map new file mode 100644 index 0000000..924569c --- /dev/null +++ b/client/qr-scanner.umd.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"qr-scanner.umd.min.js","sources":["src/qr-scanner.ts"],"sourcesContent":["class QrScanner {\n static readonly DEFAULT_CANVAS_SIZE = 400;\n static readonly NO_QR_CODE_FOUND = 'No QR code found';\n private static _disableBarcodeDetector = false;\n private static _workerMessageId = 0;\n\n /** @deprecated */\n static set WORKER_PATH(workerPath: string) {\n console.warn('Setting QrScanner.WORKER_PATH is not required and not supported anymore. '\n + 'Have a look at the README for new setup instructions.');\n }\n\n static async hasCamera(): Promise{\n try {\n return !!(await QrScanner.listCameras(false)).length;\n } catch (e) {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise > {\n if (!navigator.mediaDevices) return [];\n\n const enumerateCameras = async (): Promise > =>\n (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === 'videoinput');\n\n // Note that enumerateDevices can always be called and does not prompt the user for permission.\n // However, enumerateDevices only includes device labels if served via https and an active media stream exists\n // or permission to access the camera was given. Therefore, if we're not getting labels but labels are requested\n // ask for camera permission by opening a stream.\n let openedStream: MediaStream | undefined;\n try {\n if (requestLabels && (await enumerateCameras()).every((camera) => !camera.label)) {\n openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });\n }\n } catch (e) {\n // Fail gracefully, especially if the device has no camera or on mobile when the camera is already in use\n // and some browsers disallow a second stream.\n }\n\n try {\n return (await enumerateCameras()).map((camera, i) => ({\n id: camera.deviceId,\n label: camera.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),\n }));\n } finally {\n // close the stream we just opened for getting camera access for listing the device labels\n if (openedStream) {\n console.warn('Call listCameras after successfully starting a QR scanner to avoid creating '\n + 'a temporary video stream');\n QrScanner._stopVideoStream(openedStream);\n }\n }\n }\n\n readonly $video: HTMLVideoElement;\n readonly $canvas: HTMLCanvasElement;\n readonly $overlay?: HTMLDivElement;\n private readonly $codeOutlineHighlight?: SVGSVGElement;\n private readonly _onDecode?: (result: QrScanner.ScanResult) => void;\n private readonly _legacyOnDecode?: (result: string) => void;\n private readonly _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;\n private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment';\n private readonly _maxScansPerSecond: number = 25;\n private _lastScanTimestamp: number = -1;\n private _scanRegion: QrScanner.ScanRegion;\n private _codeOutlineHighlightRemovalTimeout?: number;\n private _qrEnginePromise: Promise \n private _active: boolean = false;\n private _paused: boolean = false;\n private _flashOn: boolean = false;\n private _destroyed: boolean = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: QrScanner.ScanResult) => void,\n options: {\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n maxScansPerSecond?: number;\n highlightScanRegion?: boolean,\n highlightCodeOutline?: boolean,\n overlay?: HTMLDivElement,\n /** just a temporary flag until we switch entirely to the new api */\n returnDetailedScanResult?: true,\n },\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n canvasSize?: number,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);\n constructor(\n video: HTMLVideoElement,\n onDecode: ((result: QrScanner.ScanResult) => void) | ((result: string) => void),\n canvasSizeOrOnDecodeErrorOrOptions?: number | ((error: Error | string) => void) | {\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n maxScansPerSecond?: number;\n highlightScanRegion?: boolean,\n highlightCodeOutline?: boolean,\n overlay?: HTMLDivElement,\n /** just a temporary flag until we switch entirely to the new api */\n returnDetailedScanResult?: true,\n },\n canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n\n if (canvasSizeOrOnDecodeErrorOrOptions && typeof canvasSizeOrOnDecodeErrorOrOptions === 'object') {\n // we got an options object using the new api\n this._onDecode = onDecode as QrScanner['_onDecode'];\n } else {\n if (canvasSizeOrOnDecodeErrorOrOptions || canvasSizeOrCalculateScanRegion || preferredCamera) {\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n // Only video and onDecode were specified and we can't distinguish between new or old api usage. For\n // backwards compatibility we have to assume the old api for now. The options object is marked as non-\n // optional in the parameter list above to make clear that ScanResult instead of string is only passed\n // if an options object was provided. However, in the future once legacy support is removed, the options\n // object should become optional.\n console.warn('Note that the type of the scan result passed to onDecode will change in the future. '\n + 'To already switch to the new api today, you can pass returnDetailedScanResult: true.');\n }\n this._legacyOnDecode = onDecode as QrScanner['_legacyOnDecode'];\n }\n\n const options = typeof canvasSizeOrOnDecodeErrorOrOptions === 'object'\n ? canvasSizeOrOnDecodeErrorOrOptions\n : {};\n this._onDecodeError = options.onDecodeError || (typeof canvasSizeOrOnDecodeErrorOrOptions === 'function'\n ? canvasSizeOrOnDecodeErrorOrOptions\n : this._onDecodeError);\n this._calculateScanRegion = options.calculateScanRegion || (typeof canvasSizeOrCalculateScanRegion==='function'\n ? canvasSizeOrCalculateScanRegion\n : this._calculateScanRegion);\n this._preferredCamera = options.preferredCamera || preferredCamera || this._preferredCamera;\n this._legacyCanvasSize = typeof canvasSizeOrOnDecodeErrorOrOptions === 'number'\n ? canvasSizeOrOnDecodeErrorOrOptions\n : typeof canvasSizeOrCalculateScanRegion === 'number'\n ? canvasSizeOrCalculateScanRegion\n : this._legacyCanvasSize;\n this._maxScansPerSecond = options.maxScansPerSecond || this._maxScansPerSecond;\n\n this._onPlay = this._onPlay.bind(this);\n this._onLoadedMetaData = this._onLoadedMetaData.bind(this);\n this._onVisibilityChange = this._onVisibilityChange.bind(this);\n this._updateOverlay = this._updateOverlay.bind(this);\n\n // @ts-ignore\n video.disablePictureInPicture = true;\n // Allow inline playback on iPhone instead of requiring full screen playback,\n // see https://webkit.org/blog/6784/new-video-policies-for-ios/\n // @ts-ignore\n video.playsInline = true;\n // Allow play() on iPhone without requiring a user gesture. Should not really be needed as camera stream\n // includes no audio, but just to be safe.\n video.muted = true;\n\n // Avoid Safari stopping the video stream on a hidden video.\n // See https://github.com/cozmo/jsQR/issues/185\n let shouldHideVideo = false;\n if (video.hidden) {\n video.hidden = false;\n shouldHideVideo = true;\n }\n if (!document.body.contains(video)) {\n document.body.appendChild(video);\n shouldHideVideo = true;\n }\n const videoContainer = video.parentElement!;\n\n if (options.highlightScanRegion || options.highlightCodeOutline) {\n const gotExternalOverlay = !!options.overlay;\n this.$overlay = options.overlay || document.createElement('div');\n const overlayStyle = this.$overlay.style;\n overlayStyle.position = 'absolute';\n overlayStyle.display = 'none';\n overlayStyle.pointerEvents = 'none';\n this.$overlay.classList.add('scan-region-highlight');\n if (!gotExternalOverlay && options.highlightScanRegion) {\n // default style; can be overwritten via css, e.g. by changing the svg's stroke color, hiding the\n // .scan-region-highlight-svg, setting a border, outline, background, etc.\n this.$overlay.innerHTML = '';\n try {\n this.$overlay.firstElementChild!.animate({ transform: ['scale(.98)', 'scale(1.01)'] }, {\n duration: 400,\n iterations: Infinity,\n direction: 'alternate',\n easing: 'ease-in-out',\n });\n } catch (e) {}\n videoContainer.insertBefore(this.$overlay, this.$video.nextSibling);\n }\n if (options.highlightCodeOutline) {\n // default style; can be overwritten via css\n this.$overlay.insertAdjacentHTML(\n 'beforeend',\n '',\n );\n this.$codeOutlineHighlight = this.$overlay.lastElementChild as SVGSVGElement;\n }\n }\n this._scanRegion = this._calculateScanRegion(video);\n\n requestAnimationFrame(() => {\n // Checking in requestAnimationFrame which should avoid a potential additional re-flow for getComputedStyle.\n const videoStyle = window.getComputedStyle(video);\n if (videoStyle.display === 'none') {\n video.style.setProperty('display', 'block', 'important');\n shouldHideVideo = true;\n }\n if (videoStyle.visibility !== 'visible') {\n video.style.setProperty('visibility', 'visible', 'important');\n shouldHideVideo = true;\n }\n if (shouldHideVideo) {\n // Hide the video in a way that doesn't cause Safari to stop the playback.\n console.warn('QrScanner has overwritten the video hiding style to avoid Safari stopping the playback.');\n video.style.opacity = '0';\n video.style.width = '0';\n video.style.height = '0';\n if (this.$overlay && this.$overlay.parentElement) {\n this.$overlay.parentElement.removeChild(this.$overlay);\n }\n // @ts-ignore\n delete this.$overlay!;\n // @ts-ignore\n delete this.$codeOutlineHighlight!;\n }\n\n if (this.$overlay) {\n this._updateOverlay();\n }\n });\n\n video.addEventListener('play', this._onPlay);\n video.addEventListener('loadedmetadata', this._onLoadedMetaData);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n window.addEventListener('resize', this._updateOverlay);\n\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n async hasFlash(): Promise {\n let stream: MediaStream | undefined;\n try {\n if (this.$video.srcObject) {\n if (!(this.$video.srcObject instanceof MediaStream)) return false; // srcObject is not a camera stream\n stream = this.$video.srcObject;\n } else {\n stream = (await this._getCameraStream()).stream;\n }\n return 'torch' in stream.getVideoTracks()[0].getSettings();\n } catch (e) {\n return false;\n } finally {\n // close the stream we just opened for detecting whether it supports flash\n if (stream && stream !== this.$video.srcObject) {\n console.warn('Call hasFlash after successfully starting the scanner to avoid creating '\n + 'a temporary video stream');\n QrScanner._stopVideoStream(stream);\n }\n }\n }\n\n isFlashOn(): boolean {\n return this._flashOn;\n }\n\n async toggleFlash(): Promise {\n if (this._flashOn) {\n await this.turnFlashOff();\n } else {\n await this.turnFlashOn();\n }\n }\n\n async turnFlashOn(): Promise {\n if (this._flashOn || this._destroyed) return;\n this._flashOn = true;\n if (!this._active || this._paused) return; // flash will be turned on later on .start()\n try {\n if (!await this.hasFlash()) throw 'No flash available';\n // Note that the video track is guaranteed to exist and to be a MediaStream due to the check in hasFlash\n await (this.$video.srcObject as MediaStream).getVideoTracks()[0].applyConstraints({\n // @ts-ignore: constraint 'torch' is unknown to ts\n advanced: [{ torch: true }],\n });\n } catch (e) {\n this._flashOn = false;\n throw e;\n }\n }\n\n async turnFlashOff(): Promise {\n if (!this._flashOn) return;\n // applyConstraints with torch: false does not work to turn the flashlight off, as a stream's torch stays\n // continuously on, see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#torch. Therefore,\n // we have to stop the stream to turn the flashlight off.\n this._flashOn = false;\n await this._restartVideoStream();\n }\n\n destroy(): void {\n this.$video.removeEventListener('loadedmetadata', this._onLoadedMetaData);\n this.$video.removeEventListener('play', this._onPlay);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n window.removeEventListener('resize', this._updateOverlay);\n\n this._destroyed = true;\n this._flashOn = false;\n this.stop(); // sets this._paused = true and this._active = false\n QrScanner._postWorkerMessage(this._qrEnginePromise, 'close');\n }\n\n async start(): Promise {\n if (this._destroyed) throw new Error('The QR scanner can not be started as it had been destroyed.');\n if (this._active && !this._paused) return;\n\n if (window.location.protocol !== 'https:') {\n // warn but try starting the camera anyways\n console.warn('The camera stream is only accessible if the page is transferred via https.');\n }\n\n this._active = true;\n if (document.hidden) return; // camera will be started as soon as tab is in foreground\n this._paused = false;\n if (this.$video.srcObject) {\n // camera stream already/still set\n await this.$video.play();\n return;\n }\n\n try {\n const { stream, facingMode } = await this._getCameraStream();\n if (!this._active || this._paused) {\n // was stopped in the meantime\n QrScanner._stopVideoStream(stream);\n return;\n }\n this._setVideoMirror(facingMode);\n this.$video.srcObject = stream;\n await this.$video.play();\n\n // Restart the flash if it was previously on\n if (this._flashOn) {\n this._flashOn = false; // force turnFlashOn to restart the flash\n this.turnFlashOn().catch(() => {});\n }\n } catch (e) {\n if (this._paused) return;\n this._active = false;\n throw e;\n }\n }\n\n stop(): void {\n this.pause();\n this._active = false;\n }\n\n async pause(stopStreamImmediately = false): Promise {\n this._paused = true;\n if (!this._active) return true;\n this.$video.pause();\n\n if (this.$overlay) {\n this.$overlay.style.display = 'none';\n }\n\n const stopStream = () => {\n if (this.$video.srcObject instanceof MediaStream) {\n // revoke srcObject only if it's a stream which was likely set by us\n QrScanner._stopVideoStream(this.$video.srcObject);\n this.$video.srcObject = null;\n }\n };\n\n if (stopStreamImmediately) {\n stopStream();\n return true;\n }\n\n await new Promise((resolve) => setTimeout(resolve, 300));\n if (!this._paused) return false;\n stopStream();\n return true;\n }\n\n async setCamera(facingModeOrDeviceId: QrScanner.FacingMode | QrScanner.DeviceId): Promise {\n if (facingModeOrDeviceId === this._preferredCamera) return;\n this._preferredCamera = facingModeOrDeviceId;\n // Restart the scanner with the new camera which will also update the video mirror and the scan region.\n await this._restartVideoStream();\n }\n\n static async scanImage(\n imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement | File | Blob | URL | String,\n options: {\n scanRegion?: QrScanner.ScanRegion | null,\n qrEngine?: Worker | BarcodeDetector | Promise | null,\n canvas?: HTMLCanvasElement | null,\n disallowCanvasResizing?: boolean,\n alsoTryWithoutScanRegion?: boolean,\n /** just a temporary flag until we switch entirely to the new api */\n returnDetailedScanResult?: true,\n },\n ): Promise ;\n /** @deprecated */\n static async scanImage(\n imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement | File | Blob | URL | String,\n scanRegion?: QrScanner.ScanRegion | null,\n qrEngine?: Worker | BarcodeDetector | Promise | null,\n canvas?: HTMLCanvasElement | null,\n disallowCanvasResizing?: boolean,\n alsoTryWithoutScanRegion?: boolean,\n ): Promise ;\n static async scanImage(\n imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement | File | Blob | URL | String,\n scanRegionOrOptions?: QrScanner.ScanRegion | {\n scanRegion?: QrScanner.ScanRegion | null,\n qrEngine?: Worker | BarcodeDetector | Promise | null,\n canvas?: HTMLCanvasElement | null,\n disallowCanvasResizing?: boolean,\n alsoTryWithoutScanRegion?: boolean,\n /** just a temporary flag until we switch entirely to the new api */\n returnDetailedScanResult?: true,\n } | null,\n qrEngine?: Worker | BarcodeDetector | Promise | null,\n canvas?: HTMLCanvasElement | null,\n disallowCanvasResizing: boolean = false,\n alsoTryWithoutScanRegion: boolean = false,\n ): Promise {\n let scanRegion: QrScanner.ScanRegion | null | undefined;\n let returnDetailedScanResult = false;\n if (scanRegionOrOptions && (\n 'scanRegion' in scanRegionOrOptions\n || 'qrEngine' in scanRegionOrOptions\n || 'canvas' in scanRegionOrOptions\n || 'disallowCanvasResizing' in scanRegionOrOptions\n || 'alsoTryWithoutScanRegion' in scanRegionOrOptions\n || 'returnDetailedScanResult' in scanRegionOrOptions\n )) {\n // we got an options object using the new api\n scanRegion = scanRegionOrOptions.scanRegion;\n qrEngine = scanRegionOrOptions.qrEngine;\n canvas = scanRegionOrOptions.canvas;\n disallowCanvasResizing = scanRegionOrOptions.disallowCanvasResizing || false;\n alsoTryWithoutScanRegion = scanRegionOrOptions.alsoTryWithoutScanRegion || false;\n returnDetailedScanResult = true;\n } else if (scanRegionOrOptions || qrEngine || canvas || disallowCanvasResizing || alsoTryWithoutScanRegion) {\n console.warn('You\\'re using a deprecated api for scanImage which will be removed in the future.');\n } else {\n // Only imageOrFileOrBlobOrUrl was specified and we can't distinguish between new or old api usage. For\n // backwards compatibility we have to assume the old api for now. The options object is marked as non-\n // optional in the parameter list above to make clear that ScanResult instead of string is only returned if\n // an options object was provided. However, in the future once legacy support is removed, the options object\n // should become optional.\n console.warn('Note that the return type of scanImage will change in the future. To already switch to the '\n + 'new api today, you can pass returnDetailedScanResult: true.');\n }\n\n const gotExternalEngine = !!qrEngine;\n\n try {\n let image: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement;\n let canvasContext: CanvasRenderingContext2D;\n [qrEngine, image] = await Promise.all([\n qrEngine || QrScanner.createQrEngine(),\n QrScanner._loadImage(imageOrFileOrBlobOrUrl),\n ]);\n [canvas, canvasContext] = QrScanner._drawToCanvas(image, scanRegion, canvas, disallowCanvasResizing);\n let detailedScanResult: QrScanner.ScanResult;\n\n if (qrEngine instanceof Worker) {\n const qrEngineWorker = qrEngine; // for ts to know that it's still a worker later in the event listeners\n if (!gotExternalEngine) {\n // Enable scanning of inverted color qr codes.\n QrScanner._postWorkerMessageSync(qrEngineWorker, 'inversionMode', 'both');\n }\n detailedScanResult = await new Promise((resolve, reject) => {\n let timeout: number;\n let onMessage: (event: MessageEvent) => void;\n let onError: (error: ErrorEvent | string) => void;\n let expectedResponseId = -1;\n onMessage = (event: MessageEvent) => {\n if (event.data.id !== expectedResponseId) {\n return;\n }\n qrEngineWorker.removeEventListener('message', onMessage);\n qrEngineWorker.removeEventListener('error', onError);\n clearTimeout(timeout);\n if (event.data.data !== null) {\n resolve({\n data: event.data.data,\n cornerPoints: QrScanner._convertPoints(event.data.cornerPoints, scanRegion),\n });\n } else {\n reject(QrScanner.NO_QR_CODE_FOUND);\n }\n };\n onError = (error: ErrorEvent | string) => {\n qrEngineWorker.removeEventListener('message', onMessage);\n qrEngineWorker.removeEventListener('error', onError);\n clearTimeout(timeout);\n const errorMessage = !error ? 'Unknown Error' : ((error as ErrorEvent).message || error);\n reject('Scanner error: ' + errorMessage);\n };\n qrEngineWorker.addEventListener('message', onMessage);\n qrEngineWorker.addEventListener('error', onError);\n timeout = setTimeout(() => onError('timeout'), 10000);\n const imageData = canvasContext.getImageData(0, 0, canvas!.width, canvas!.height);\n expectedResponseId = QrScanner._postWorkerMessageSync(\n qrEngineWorker,\n 'decode',\n imageData,\n [imageData.data.buffer],\n );\n });\n } else {\n detailedScanResult = await Promise.race([\n new Promise ((resolve, reject) => window.setTimeout(\n () => reject('Scanner error: timeout'),\n 10000,\n )),\n (async (): Promise => {\n try {\n const [scanResult] = await qrEngine.detect(canvas!);\n if (!scanResult) throw QrScanner.NO_QR_CODE_FOUND;\n return {\n data: scanResult.rawValue,\n cornerPoints: QrScanner._convertPoints(scanResult.cornerPoints, scanRegion),\n };\n } catch (e) {\n const errorMessage = (e as Error).message || e as string;\n if (/not implemented|service unavailable/.test(errorMessage)) {\n // Not implemented can apparently for some reason happen even though getSupportedFormats\n // in createQrScanner reported that it's supported, see issue #98.\n // Service unavailable can happen after some time when the BarcodeDetector crashed and\n // can theoretically be recovered from by creating a new BarcodeDetector. However, in\n // newer browsers this issue does not seem to be present anymore and therefore we do not\n // apply this optimization anymore but just set _disableBarcodeDetector in both cases.\n // Also note that if we got an external qrEngine that crashed, we should possibly notify\n // the caller about it, but we also don't do this here, as it's such an unlikely case.\n QrScanner._disableBarcodeDetector = true;\n // retry without passing the broken BarcodeScanner instance\n return QrScanner.scanImage(imageOrFileOrBlobOrUrl, {\n scanRegion,\n canvas,\n disallowCanvasResizing,\n alsoTryWithoutScanRegion,\n });\n }\n throw `Scanner error: ${errorMessage}`;\n }\n })(),\n ]);\n }\n return returnDetailedScanResult ? detailedScanResult : detailedScanResult.data;\n } catch (e) {\n if (!scanRegion || !alsoTryWithoutScanRegion) throw e;\n const detailedScanResult = await QrScanner.scanImage(\n imageOrFileOrBlobOrUrl,\n { qrEngine, canvas, disallowCanvasResizing },\n );\n return returnDetailedScanResult ? detailedScanResult : detailedScanResult.data;\n } finally {\n if (!gotExternalEngine) {\n QrScanner._postWorkerMessage(qrEngine!, 'close');\n }\n }\n }\n\n setGrayscaleWeights(red: number, green: number, blue: number, useIntegerApproximation: boolean = true): void {\n // Note that for the native BarcodeDecoder or if the worker was destroyed, this is a no-op. However, the native\n // implementations work also well with colored qr codes.\n QrScanner._postWorkerMessage(\n this._qrEnginePromise,\n 'grayscaleWeights',\n { red, green, blue, useIntegerApproximation }\n );\n }\n\n setInversionMode(inversionMode: QrScanner.InversionMode): void {\n // Note that for the native BarcodeDecoder or if the worker was destroyed, this is a no-op. However, the native\n // implementations scan normal and inverted qr codes by default\n QrScanner._postWorkerMessage(this._qrEnginePromise, 'inversionMode', inversionMode);\n }\n\n static async createQrEngine(): Promise ;\n /** @deprecated */\n static async createQrEngine(workerPath: string): Promise ;\n static async createQrEngine(workerPath?: string): Promise {\n if (workerPath) {\n console.warn('Specifying a worker path is not required and not supported anymore.');\n }\n\n // @ts-ignore no types defined for import\n const createWorker = () => (import('./qr-scanner-worker.min.js') as Promise<{ createWorker: () => Worker }>)\n .then((module) => module.createWorker());\n\n const useBarcodeDetector = !QrScanner._disableBarcodeDetector\n && 'BarcodeDetector' in window\n && BarcodeDetector.getSupportedFormats\n && (await BarcodeDetector.getSupportedFormats()).includes('qr_code');\n\n if (!useBarcodeDetector) return createWorker();\n\n // On Macs with an M1/M2 processor and macOS Ventura (macOS version 13), the BarcodeDetector is broken in\n // Chromium based browsers, regardless of the version. For that constellation, the BarcodeDetector does not\n // error but does not detect QR codes. Macs without an M1/M2 or before Ventura are fine.\n // See issue #209 and https://bugs.chromium.org/p/chromium/issues/detail?id=1382442\n // TODO update this once the issue in macOS is fixed\n const userAgentData = navigator.userAgentData;\n const isChromiumOnMacWithArmVentura = userAgentData // all Chromium browsers support userAgentData\n && userAgentData.brands.some(({ brand }) => /Chromium/i.test(brand))\n && /mac ?OS/i.test(userAgentData.platform)\n // Does it have an ARM chip (e.g. M1/M2) and Ventura? Check this last as getHighEntropyValues can\n // theoretically trigger a browser prompt, although no browser currently does seem to show one.\n // If browser or user refused to return the requested values, assume broken ARM Ventura, to be safe.\n && await userAgentData.getHighEntropyValues(['architecture', 'platformVersion'])\n .then(({ architecture, platformVersion }) =>\n /arm/i.test(architecture || 'arm') && parseInt(platformVersion || '13') >= /* Ventura */ 13)\n .catch(() => true);\n if (isChromiumOnMacWithArmVentura) return createWorker();\n\n return new BarcodeDetector({ formats: ['qr_code'] });\n }\n\n private _onPlay(): void {\n this._scanRegion = this._calculateScanRegion(this.$video);\n this._updateOverlay();\n if (this.$overlay) {\n this.$overlay.style.display = '';\n }\n this._scanFrame();\n }\n\n private _onLoadedMetaData(): void {\n this._scanRegion = this._calculateScanRegion(this.$video);\n this._updateOverlay();\n }\n\n private _onVisibilityChange(): void {\n if (document.hidden) {\n this.pause();\n } else if (this._active) {\n this.start();\n }\n }\n\n private _calculateScanRegion(video: HTMLVideoElement): QrScanner.ScanRegion {\n // Default scan region calculation. Note that this can be overwritten in the constructor.\n const smallestDimension = Math.min(video.videoWidth, video.videoHeight);\n const scanRegionSize = Math.round(2 / 3 * smallestDimension);\n return {\n x: Math.round((video.videoWidth - scanRegionSize) / 2),\n y: Math.round((video.videoHeight - scanRegionSize) / 2),\n width: scanRegionSize,\n height: scanRegionSize,\n downScaledWidth: this._legacyCanvasSize,\n downScaledHeight: this._legacyCanvasSize,\n };\n }\n\n private _updateOverlay(): void {\n requestAnimationFrame(() => {\n // Running in requestAnimationFrame which should avoid a potential additional re-flow for getComputedStyle\n // and offsetWidth, offsetHeight, offsetLeft, offsetTop.\n if (!this.$overlay) return;\n const video = this.$video;\n const videoWidth = video.videoWidth;\n const videoHeight = video.videoHeight;\n const elementWidth = video.offsetWidth;\n const elementHeight = video.offsetHeight;\n const elementX = video.offsetLeft;\n const elementY = video.offsetTop;\n\n const videoStyle = window.getComputedStyle(video);\n const videoObjectFit = videoStyle.objectFit;\n const videoAspectRatio = videoWidth / videoHeight;\n const elementAspectRatio = elementWidth / elementHeight;\n let videoScaledWidth: number;\n let videoScaledHeight: number;\n switch (videoObjectFit) {\n case 'none':\n videoScaledWidth = videoWidth;\n videoScaledHeight = videoHeight;\n break;\n case 'fill':\n videoScaledWidth = elementWidth;\n videoScaledHeight = elementHeight;\n break;\n default: // 'cover', 'contains', 'scale-down'\n if (videoObjectFit === 'cover'\n ? videoAspectRatio > elementAspectRatio\n : videoAspectRatio < elementAspectRatio) {\n // The scaled height is the element height\n // - for 'cover' if the video aspect ratio is wider than the element aspect ratio\n // (scaled height matches element height and scaled width overflows element width)\n // - for 'contains'/'scale-down' if element aspect ratio is wider than the video aspect ratio\n // (scaled height matched element height and element width overflows scaled width)\n videoScaledHeight = elementHeight;\n videoScaledWidth = videoScaledHeight * videoAspectRatio;\n } else {\n videoScaledWidth = elementWidth;\n videoScaledHeight = videoScaledWidth / videoAspectRatio;\n }\n if (videoObjectFit === 'scale-down') {\n // for 'scale-down' the dimensions are the minimum of 'contains' and 'none'\n videoScaledWidth = Math.min(videoScaledWidth, videoWidth);\n videoScaledHeight = Math.min(videoScaledHeight, videoHeight);\n }\n }\n\n // getComputedStyle is so nice to convert keywords (left, center, right, top, bottom) to percent and makes\n // sure to set the default of 50% if only one or no component was provided, therefore we can be sure that\n // both components are set. Additionally, it converts units other than px (e.g. rem) to px.\n const [videoX, videoY] = videoStyle.objectPosition.split(' ').map((length, i) => {\n const lengthValue = parseFloat(length);\n return length.endsWith('%')\n ? (!i ? elementWidth - videoScaledWidth : elementHeight - videoScaledHeight) * lengthValue / 100\n : lengthValue;\n });\n\n const regionWidth = this._scanRegion.width || videoWidth;\n const regionHeight = this._scanRegion.height || videoHeight;\n const regionX = this._scanRegion.x || 0;\n const regionY = this._scanRegion.y || 0;\n\n const overlayStyle = this.$overlay.style;\n overlayStyle.width = `${regionWidth / videoWidth * videoScaledWidth}px`;\n overlayStyle.height = `${regionHeight / videoHeight * videoScaledHeight}px`;\n overlayStyle.top = `${elementY + videoY + regionY / videoHeight * videoScaledHeight}px`;\n const isVideoMirrored = /scaleX\\(-1\\)/.test(video.style.transform!);\n overlayStyle.left = `${elementX\n + (isVideoMirrored ? elementWidth - videoX - videoScaledWidth : videoX)\n + (isVideoMirrored ? videoWidth - regionX - regionWidth : regionX) / videoWidth * videoScaledWidth}px`;\n // apply same mirror as on video\n overlayStyle.transform = video.style.transform;\n });\n }\n\n private static _convertPoints(\n points: QrScanner.Point[],\n scanRegion?: QrScanner.ScanRegion | null,\n ): QrScanner.Point[] {\n if (!scanRegion) return points;\n const offsetX = scanRegion.x || 0;\n const offsetY = scanRegion.y || 0;\n const scaleFactorX = scanRegion.width && scanRegion.downScaledWidth\n ? scanRegion.width / scanRegion.downScaledWidth\n : 1;\n const scaleFactorY = scanRegion.height && scanRegion.downScaledHeight\n ? scanRegion.height / scanRegion.downScaledHeight\n : 1;\n for (const point of points) {\n point.x = point.x * scaleFactorX + offsetX;\n point.y = point.y * scaleFactorY + offsetY;\n }\n return points;\n }\n\n private _scanFrame(): void {\n if (!this._active || this.$video.paused || this.$video.ended) return;\n // If requestVideoFrameCallback is available use that to avoid unnecessary scans on the same frame as the\n // camera's framerate can be lower than the screen refresh rate and this._maxScansPerSecond, especially in dark\n // settings where the exposure time is longer. Both, requestVideoFrameCallback and requestAnimationFrame are not\n // being fired if the tab is in the background, which is what we want.\n const requestFrame = 'requestVideoFrameCallback' in this.$video\n // @ts-ignore\n ? this.$video.requestVideoFrameCallback.bind(this.$video)\n : requestAnimationFrame;\n requestFrame(async () => {\n if (this.$video.readyState <= 1) {\n // Skip scans until the video is ready as drawImage() only works correctly on a video with readyState\n // > 1, see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage#Notes.\n // This also avoids false positives for videos paused after a successful scan which remains visible on\n // the canvas until the video is started again and ready.\n this._scanFrame();\n return;\n }\n\n const timeSinceLastScan = Date.now() - this._lastScanTimestamp;\n const minimumTimeBetweenScans = 1000 / this._maxScansPerSecond;\n if (timeSinceLastScan < minimumTimeBetweenScans) {\n await new Promise((resolve) => setTimeout(resolve, minimumTimeBetweenScans - timeSinceLastScan));\n }\n // console.log('Scan rate:', Math.round(1000 / (Date.now() - this._lastScanTimestamp)));\n this._lastScanTimestamp = Date.now();\n\n let result: QrScanner.ScanResult | undefined;\n try {\n result = await QrScanner.scanImage(this.$video, {\n scanRegion: this._scanRegion,\n qrEngine: this._qrEnginePromise,\n canvas: this.$canvas,\n });\n } catch (error) {\n if (!this._active) return;\n this._onDecodeError(error as Error | string);\n }\n\n if (QrScanner._disableBarcodeDetector && !(await this._qrEnginePromise instanceof Worker)) {\n // replace the disabled BarcodeDetector\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n if (result) {\n if (this._onDecode) {\n this._onDecode(result);\n } else if (this._legacyOnDecode) {\n this._legacyOnDecode(result.data);\n }\n\n if (this.$codeOutlineHighlight) {\n clearTimeout(this._codeOutlineHighlightRemovalTimeout);\n this._codeOutlineHighlightRemovalTimeout = undefined;\n this.$codeOutlineHighlight.setAttribute(\n 'viewBox',\n `${this._scanRegion.x || 0} `\n + `${this._scanRegion.y || 0} `\n + `${this._scanRegion.width || this.$video.videoWidth} `\n + `${this._scanRegion.height || this.$video.videoHeight}`,\n );\n const polygon = this.$codeOutlineHighlight.firstElementChild!;\n polygon.setAttribute('points', result.cornerPoints.map(({x, y}) => `${x},${y}`).join(' '));\n this.$codeOutlineHighlight.style.display = '';\n }\n } else if (this.$codeOutlineHighlight && !this._codeOutlineHighlightRemovalTimeout) {\n // hide after timeout to make it flash less when on some frames the QR code is detected and on some not\n this._codeOutlineHighlightRemovalTimeout = setTimeout(\n () => this.$codeOutlineHighlight!.style.display = 'none',\n 100,\n );\n }\n\n this._scanFrame();\n });\n }\n\n private _onDecodeError(error: Error | string): void {\n // default error handler; can be overwritten in the constructor\n if (error === QrScanner.NO_QR_CODE_FOUND) return;\n console.log(error);\n }\n\n private async _getCameraStream(): Promise<{ stream: MediaStream, facingMode: QrScanner.FacingMode }> {\n if (!navigator.mediaDevices) throw 'Camera not found.';\n\n const preferenceType = /^(environment|user)$/.test(this._preferredCamera)\n ? 'facingMode'\n : 'deviceId';\n const constraintsWithoutCamera: Array = [{\n width: { min: 1024 }\n }, {\n width: { min: 768 }\n }, {}];\n const constraintsWithCamera = constraintsWithoutCamera.map((constraint) => Object.assign({}, constraint, {\n [preferenceType]: { exact: this._preferredCamera },\n }));\n\n for (const constraints of [...constraintsWithCamera, ...constraintsWithoutCamera]) {\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video: constraints, audio: false });\n // Try to determine the facing mode from the stream, otherwise use a guess or 'environment' as\n // default. Note that the guess is not always accurate as Safari returns cameras of different facing\n // mode, even for exact facingMode constraints.\n const facingMode = this._getFacingMode(stream)\n || (constraints.facingMode\n ? this._preferredCamera as QrScanner.FacingMode // a facing mode we were able to fulfill\n : (this._preferredCamera === 'environment'\n ? 'user' // switch as _preferredCamera was environment but we are not able to fulfill it\n : 'environment' // switch from unfulfilled user facingMode or default to environment\n )\n );\n return { stream, facingMode };\n } catch (e) {}\n }\n\n throw 'Camera not found.';\n }\n\n private async _restartVideoStream(): Promise {\n // Note that we always pause the stream and not only if !this._paused as even if this._paused === true, the\n // stream might still be running, as it's by default only stopped after a delay of 300ms.\n const wasPaused = this._paused;\n const paused = await this.pause(true);\n if (!paused || wasPaused || !this._active) return;\n await this.start();\n }\n\n private static _stopVideoStream(stream : MediaStream): void {\n for (const track of stream.getTracks()) {\n track.stop(); // note that this will also automatically turn the flashlight off\n stream.removeTrack(track);\n }\n }\n\n private _setVideoMirror(facingMode: QrScanner.FacingMode): void {\n // in user facing mode mirror the video to make it easier for the user to position the QR code\n const scaleFactor = facingMode === 'user'? -1 : 1;\n this.$video.style.transform = 'scaleX(' + scaleFactor + ')';\n }\n\n private _getFacingMode(videoStream: MediaStream): QrScanner.FacingMode | null {\n const videoTrack = videoStream.getVideoTracks()[0];\n if (!videoTrack) return null; // unknown\n // inspired by https://github.com/JodusNodus/react-qr-reader/blob/master/src/getDeviceId.js#L13\n return /rear|back|environment/i.test(videoTrack.label)\n ? 'environment'\n : /front|user|face/i.test(videoTrack.label)\n ? 'user'\n : null; // unknown\n }\n\n private static _drawToCanvas(\n image: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement,\n scanRegion?: QrScanner.ScanRegion | null,\n canvas?: HTMLCanvasElement | null,\n disallowCanvasResizing= false,\n ): [HTMLCanvasElement, CanvasRenderingContext2D] {\n canvas = canvas || document.createElement('canvas');\n const scanRegionX = scanRegion && scanRegion.x ? scanRegion.x : 0;\n const scanRegionY = scanRegion && scanRegion.y ? scanRegion.y : 0;\n const scanRegionWidth = scanRegion && scanRegion.width\n ? scanRegion.width\n : (image as HTMLVideoElement).videoWidth || image.width as number;\n const scanRegionHeight = scanRegion && scanRegion.height\n ? scanRegion.height\n : (image as HTMLVideoElement).videoHeight || image.height as number;\n\n if (!disallowCanvasResizing) {\n const canvasWidth = scanRegion && scanRegion.downScaledWidth\n ? scanRegion.downScaledWidth\n : scanRegionWidth;\n const canvasHeight = scanRegion && scanRegion.downScaledHeight\n ? scanRegion.downScaledHeight\n : scanRegionHeight;\n // Setting the canvas width or height clears the canvas, even if the values didn't change, therefore only\n // set them if they actually changed.\n if (canvas.width !== canvasWidth) {\n canvas.width = canvasWidth;\n }\n if (canvas.height !== canvasHeight) {\n canvas.height = canvasHeight;\n }\n }\n\n const context = canvas.getContext('2d', { alpha: false })!;\n context.imageSmoothingEnabled = false; // gives less blurry images\n context.drawImage(\n image,\n scanRegionX, scanRegionY, scanRegionWidth, scanRegionHeight,\n 0, 0, canvas.width, canvas.height,\n );\n return [canvas, context];\n }\n\n private static async _loadImage(\n imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap\n | SVGImageElement | File | Blob | URL | String,\n ): Promise {\n if (imageOrFileOrBlobOrUrl instanceof Image) {\n await QrScanner._awaitImageLoad(imageOrFileOrBlobOrUrl);\n return imageOrFileOrBlobOrUrl;\n } else if (imageOrFileOrBlobOrUrl instanceof HTMLVideoElement\n || imageOrFileOrBlobOrUrl instanceof HTMLCanvasElement\n || imageOrFileOrBlobOrUrl instanceof SVGImageElement\n || 'OffscreenCanvas' in window && imageOrFileOrBlobOrUrl instanceof OffscreenCanvas\n || 'ImageBitmap' in window && imageOrFileOrBlobOrUrl instanceof ImageBitmap) {\n return imageOrFileOrBlobOrUrl;\n } else if (imageOrFileOrBlobOrUrl instanceof File || imageOrFileOrBlobOrUrl instanceof Blob\n || imageOrFileOrBlobOrUrl instanceof URL || typeof imageOrFileOrBlobOrUrl === 'string') {\n const image = new Image();\n if (imageOrFileOrBlobOrUrl instanceof File || imageOrFileOrBlobOrUrl instanceof Blob) {\n image.src = URL.createObjectURL(imageOrFileOrBlobOrUrl);\n } else {\n image.src = imageOrFileOrBlobOrUrl.toString();\n }\n try {\n await QrScanner._awaitImageLoad(image);\n return image;\n } finally {\n if (imageOrFileOrBlobOrUrl instanceof File || imageOrFileOrBlobOrUrl instanceof Blob) {\n URL.revokeObjectURL(image.src);\n }\n }\n } else {\n throw 'Unsupported image type.';\n }\n }\n\n private static async _awaitImageLoad(image: HTMLImageElement): Promise {\n if (image.complete && image.naturalWidth !== 0) return; // already loaded\n await new Promise ((resolve, reject) => {\n const listener = (event: ErrorEvent | Event) => {\n image.removeEventListener('load', listener);\n image.removeEventListener('error', listener);\n if (event instanceof ErrorEvent) {\n reject('Image load error');\n } else {\n resolve();\n }\n };\n image.addEventListener('load', listener);\n image.addEventListener('error', listener);\n });\n }\n\n private static async _postWorkerMessage(\n qrEngineOrQrEnginePromise: Worker | BarcodeDetector | Promise ,\n type: string,\n data?: any,\n transfer?: Transferable[],\n ): Promise {\n return QrScanner._postWorkerMessageSync(await qrEngineOrQrEnginePromise, type, data, transfer);\n }\n\n // sync version of _postWorkerMessage without performance overhead of async functions\n private static _postWorkerMessageSync(\n qrEngine: Worker | BarcodeDetector,\n type: string,\n data?: any,\n transfer?: Transferable[],\n ): number {\n if (!(qrEngine instanceof Worker)) return -1;\n const id = QrScanner._workerMessageId++;\n qrEngine.postMessage({\n id,\n type,\n data,\n }, transfer);\n return id;\n }\n}\n\ndeclare namespace QrScanner {\n export interface ScanRegion {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n downScaledWidth?: number;\n downScaledHeight?: number;\n }\n\n export type FacingMode = 'environment' | 'user';\n export type DeviceId = string;\n\n export interface Camera {\n id: DeviceId;\n label: string;\n }\n\n export type InversionMode = 'original' | 'invert' | 'both';\n\n export interface Point {\n x: number;\n y: number;\n }\n\n export interface ScanResult {\n data: string;\n // In clockwise order, starting at top left, but this might not be guaranteed in the future.\n cornerPoints: QrScanner.Point[];\n }\n}\n\n// simplified from https://wicg.github.io/shape-detection-api/#barcode-detection-api\ndeclare class BarcodeDetector {\n constructor(options?: { formats: string[] });\n static getSupportedFormats(): Promise ;\n detect(image: ImageBitmapSource): Promise >;\n}\n\n// simplified from https://github.com/lukewarlow/user-agent-data-types/blob/master/index.d.ts\ndeclare global {\n interface Navigator {\n readonly userAgentData?: {\n readonly platform: string;\n readonly brands: Array<{\n readonly brand: string;\n readonly version: string;\n }>;\n getHighEntropyValues(hints: string[]): Promise<{\n readonly architecture?: string;\n readonly platformVersion?: string;\n }>;\n };\n }\n}\n\nexport default QrScanner;\n"],"names":["QrScanner","constructor","video","onDecode","canvasSizeOrOnDecodeErrorOrOptions","canvasSizeOrCalculateScanRegion","preferredCamera","DEFAULT_CANVAS_SIZE","$video","$canvas","document","createElement","_onDecode","console","warn","_legacyOnDecode","_onDecodeError","options","onDecodeError","_calculateScanRegion","calculateScanRegion","_preferredCamera","_legacyCanvasSize","_maxScansPerSecond","maxScansPerSecond","_onPlay","bind","_onLoadedMetaData","_onVisibilityChange","_updateOverlay","disablePictureInPicture","playsInline","muted","shouldHideVideo","hidden","body","contains","appendChild","highlightScanRegion","highlightCodeOutline","$overlay","overlay","overlayStyle","position","display","pointerEvents","classList","add","gotExternalOverlay","innerHTML","firstElementChild","animate","transform","duration","iterations","Infinity","direction","easing","e","videoContainer","insertBefore","nextSibling","insertAdjacentHTML","$codeOutlineHighlight","lastElementChild","_scanRegion","requestAnimationFrame","videoStyle","style","setProperty","visibility","opacity","width","height","parentElement","removeChild","addEventListener","window","_qrEnginePromise","createQrEngine","workerPath","hasCamera","length","listCameras","requestLabels","navigator","mediaDevices","filter","device","openedStream","every","enumerateCameras","camera","label","getUserMedia","audio","map","i","id","deviceId","_stopVideoStream","hasFlash","stream","srcObject","MediaStream","_getCameraStream","getVideoTracks","getSettings","isFlashOn","_flashOn","toggleFlash","turnFlashOff","turnFlashOn","_destroyed","_active","_paused","applyConstraints","advanced","torch","_restartVideoStream","destroy","removeEventListener","stop","_postWorkerMessage","start","Error","location","protocol","play","facingMode","_setVideoMirror","catch","pause","stopStreamImmediately","stopStream","Promise","resolve","setTimeout","setCamera","facingModeOrDeviceId","scanImage","imageOrFileOrBlobOrUrl","scanRegionOrOptions","qrEngine","canvas","disallowCanvasResizing","alsoTryWithoutScanRegion","scanRegion","returnDetailedScanResult","image","canvasContext","all","_loadImage","_drawToCanvas","detailedScanResult","Worker","gotExternalEngine","_postWorkerMessageSync","qrEngineWorker","reject","timeout","onMessage","onError","expectedResponseId","event","data","clearTimeout","cornerPoints","_convertPoints","NO_QR_CODE_FOUND","error","imageData","buffer","race","scanResult","rawValue","message","test","errorMessage","_disableBarcodeDetector","setGrayscaleWeights","red","green","blue","useIntegerApproximation","setInversionMode","inversionMode","then","module","createWorker","BarcodeDetector","getSupportedFormats","includes","userAgentData","brands","some","brand","platform","getHighEntropyValues","architecture","platformVersion","parseInt","formats","_scanFrame","x","Math","round","videoWidth","scanRegionSize","y","videoHeight","downScaledWidth","downScaledHeight","videoObjectFit","videoScaledWidth","videoScaledHeight","elementWidth","elementHeight","videoAspectRatio","elementAspectRatio","min","videoY","lengthValue","parseFloat","endsWith","regionWidth","regionHeight","top","elementY","regionY","left","elementX","isVideoMirrored","videoX","regionX","points","point","scaleFactorX","offsetX","scaleFactorY","offsetY","paused","ended","requestVideoFrameCallback","readyState","timeSinceLastScan","minimumTimeBetweenScans","_lastScanTimestamp","Date","now","result","_codeOutlineHighlightRemovalTimeout","undefined","setAttribute","join","log","constraint","preferenceType","exact","constraints","wasPaused","track","removeTrack","_getFacingMode","videoStream","videoTrack","scanRegionWidth","scanRegionHeight","canvasWidth","canvasHeight","alpha","context","imageSmoothingEnabled","drawImage","scanRegionX","scanRegionY","Image","_awaitImageLoad","HTMLVideoElement","HTMLCanvasElement","SVGImageElement","OffscreenCanvas","ImageBitmap","File","Blob","URL","src","createObjectURL","toString","revokeObjectURL","complete","naturalWidth","listener","ErrorEvent","qrEngineOrQrEnginePromise","type","transfer","postMessage"],"mappings":"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"} \ No newline at end of file diff --git a/client/root.js b/client/root.js new file mode 100644 index 0000000..4751c26 --- /dev/null +++ b/client/root.js @@ -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.
(" + 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" + (this.viewFrom>0 ? "" : "") + (this.viewTo "; + for (var v=this.viewFrom; v<=this.viewTo; v++) { + html+= ">" : "") + " " + + (v==this.viewFrom && this.viewFrom>0 ? "" : "") + + (v==this.viewTo && this.viewTo>this.viewFrom ? "" : "") + + this.view[v].title + + (v==this.viewFrom && this.viewFrom "; + } + html+= ""; + + for (let a in Account.byOrdering) { + html+= Account.byOrdering[a].Render(); + } + + this.contentContainer.innerHTML = html+""; + + 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(); +}); diff --git a/client/routes/fahrzeuge.js b/client/routes/fahrzeuge.js new file mode 100644 index 0000000..b221875 --- /dev/null +++ b/client/routes/fahrzeuge.js @@ -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 = ">" : "") + + (v==this.viewTo && this.viewTo >" : "") + + " "; + if (this.Bild.EXISTIERT) { + html+= ""; + if (drawEdit) { + html+= ""; + if (drawEdit) { + html+= "
"; + } + } else { + html+= ""; + } + if (drawEdit) { + html+= ""; + } + html += "
" + + "
"; + } else { + html+= "- " + + "
- " + + "
- " + + "
- " + + "
- " + + "
"; + } + document.getElementById(this.marker("Main")).innerHTML = html; + } + + static RenderAdd() { + let html = "
- " + "
" + this.Name + "
- "; + if (this.admin) { + html += "
" + this.Kürzel + "
- "; + } + html+= "
Fahrzeug hinzufügen
" + + ""; + return html; + } + + renderGroup() { + return Fahrzeuge.RenderGroup(this.groupName); + } + + static RenderGroup(groupName) { + return { + begin: "
- Name:
" + + "- Kürzel:
" + + "- Hinzufügen
" + }; + } + + 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 = ""; + return html; + } +} diff --git a/client/routes/personal.js b/client/routes/personal.js new file mode 100644 index 0000000..a6604f5 --- /dev/null +++ b/client/routes/personal.js @@ -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 = " ", + end: "" + groupName + "
"; + if (this.Bild.EXISTIERT) { + html+= ""; + if (drawEdit) { + html+= "
"; + } + } else { + html+= ""; + } + if (drawEdit) { + html+= ""; + } + html += "
"; + + // 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", "" + this.Nachnamen + ", " + this.Vornamen + "") + + (this.Login=="" || this.Login===null ? this.renderField("note.png", "Kein Login!") : "") + + this.renderField("star.png", this.Funktion!==null ? this.Funktion.KÜRZEL : "-"); + if (this.admin) { + html += this.renderButton("edit.png", "Edit", "Bearbeiten"); + } + } + html+= "
"; + document.getElementById(this.marker("Main")).innerHTML = html; + } + + static RenderAdd() { + let html = "Fahrzeug hinzufügen
" + + ""; + return html; + } + + renderGroup() { + return Personal.RenderGroup(this.groupName); + } + + static RenderGroup(groupName) { + return { + begin: "
- Name:
" + + "- Kürzel:
" + + "- Hinzufügen
" + }; + } + + 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 = ""; + return html; + } +} diff --git a/client/scanner.js b/client/scanner.js new file mode 100644 index 0000000..7fbc235 --- /dev/null +++ b/client/scanner.js @@ -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 + " ", + end: "" + groupName + "
"; + 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; + } } } } +} + diff --git a/client/template.js b/client/template.js new file mode 100644 index 0000000..f17a2d1 --- /dev/null +++ b/client/template.js @@ -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 ""; + } + + findMarkerModes(tpl, section) { + var ret = []; + var sectionMarker = this.makeMarker(section+":"); + var markerLength = 5 + sectionMarker.length - 3; // 5 = " + // True text + // + // False text + // + 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+ // Default text + // + // Text A + // + // Text B + // + 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 = " + + + Anleitung
+++ + + ++ Hier werden die kommenden Termine aufgelistet. Zu einigen davon kannst bzw. musst Du dich anmelden und zu einigen bist Du nicht zugelassen. + Lies daher bitte die folgenden Hinweise: +
++
+- Du darfst zu einem Termin nur kommen, wenn dies auch so in der Liste unten steht: "Du darfst kommen!"
+- Bei Terminen mit mehr Interessenten als Plätzen wird gelost, zur Zeit eine Woche vor Dienstbeginn.
+- Corona-Dienste werden zwei Tage vor Beginn für alle Gruppen freigegeben, falls noch Plätze frei sind.
+- Erscheine pünktlich zum Dienstbeginn! Bring eine Maske mit!
+- +
+= Zugelassen zum Dienst
= Nicht zugelassen
+= Du bist bereits zu einem anderen Dienst 7 Tage vorher oder nachher angemeldet. + Denke daran, dass wir die Gruppeneinteilung auch als Infektionsschutz eingerichtet haben. +
+ ++ + + + + + +++ + + + ++
+- ↳ ###LIST.SUB.ANMELDUNG.RESTRICTIONS###
+ + +- Du darfst kommen
+ +- Auf Warteliste
+ +- Zur Auslosung angemeldet
+ +- Bei Interesse bitte anmelden
+ +- Du leitest diesen Dienst
+ + +- Noch ###LIST.SUB.ANMELDUNG.FREE.NUM### Plätze frei
+ + +- ###LIST.SUB.ANMELDUNG.ACTION.NAME###
+ +++ ++
+- ↳ ###LIST.SUB.ANMELDUNG.RESTRICTIONS###
+ + +- Du darfst kommen
+ +- Auf Warteliste
+ +- Zur Auslosung angemeldet
+ +- Bei Interesse bitte anmelden
+ +- Du leitest diesen Dienst
+ + +- Noch ###LIST.SUB.ANMELDUNG.FREE.NUM### Plätze frei
+ + +- ###LIST.SUB.ANMELDUNG.ACTION.NAME###
+ ++ ++ + + + +Termin hinzufügen
+++ + + + ++
- Beginn:
+- Ende:
+- Thema:
+- Ort:
+- Dienstplan: + +
+- Kategorie: + +
+- Platzvergabe: + +
+- Ausgelost:
+- Gruppe: + +
+- Hinzufügen
++ + + diff --git a/pgs/11_dienstplaene/module.json b/pgs/11_dienstplaene/module.json new file mode 100644 index 0000000..31aca9f --- /dev/null +++ b/pgs/11_dienstplaene/module.json @@ -0,0 +1,19 @@ +{ + "route" : "Dienstpläne", + "title" : "Dienstpläne", + "table" : "Dienstpläne", + "marker": "DIENSTPLÄNE", + "dependencies": [], + "show" : 1, + "useRight": "DARF_DIENSTE_VERWALTEN", + "adminRight": "DARF_DIENSTE_VERWALTEN", + "template": "template.html", + "isPage": true, + "links": { + "Termine": {"route":"Termine_Dienstpläne", "mainName":"Dienstplan", "subName":"Termine"} + }, + "mainFields": { + "ABTEILUNG": {"label": "Abteilung", "type": "Select", "filter": true, "add": true, "edit": true}, + "JAHR": {"label": "Jahr", "type": "Integer", "filter": true, "add": true, "edit": true}, + "NAME": {"label": "Name", "type": "String", "filter": true, "add": true, "edit": true} +} } \ No newline at end of file diff --git a/pgs/11_dienstplaene/page.php b/pgs/11_dienstplaene/page.php new file mode 100644 index 0000000..1b6aec0 --- /dev/null +++ b/pgs/11_dienstplaene/page.php @@ -0,0 +1,149 @@ +get($id); + $qry = "UPDATE Termine_Dienstpläne SET Name = ?, Jahr = ?, Abteilung = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $Name = $this->man->input["NAME"] ?? $this->output["MAIN"]["NAME"]; + $Jahr = $this->man->input["JAHR"] ?? $this->output["MAIN"]["JAHR"]; + $Abteilung = $this->man->input["ABTEILUNG"] ?? $this->output["MAIN"]["ABTEILUNG"]["ID"]; + $stmt->bind_param( + "siii", + $Name, + $Jahr, + $Abteilung, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Dienstplan wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function insert() { + $qry = "INSERT INTO Termine_Dienstpläne (Abteilung, Jahr, Name) VALUES (?,?,?)"; + if ($stmt = $this->db->prepare($qry)) { + $Abteilung = $this->man->input["ABTEILUNG"] ?? null; + $Jahr = $this->man->input["JAHR"] ?? 0; + $Name = $this->man->input["NAME"] ?? ""; + $stmt->bind_param( + "iis", + $Abteilung, + $Jahr, + $Name + ); + $stmt->execute(); + if ($stmt->affected_rows==1) { + $this->man->AddMessage("Dienstplan wurde hinzugefügt!"); + $this->get($this->db->insert_id); + return 201; + } else { + $this->man->AddMessage("Dienstplan konnte nicht hinzugefügt werden! (".$Abteilung." / ".$Jahr." / ".$Name.")"); + return 400; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function get($id = null) { + $this->output = []; + $filterID = false; + if ($id!=null) { + $filterID = true; + } + $qry = "SELECT td.ID, td.Abteilung, td.Jahr, td.Name, sa.Kürzel saKürzel, sa.Name saName " + ."FROM Termine_Dienstpläne td LEFT JOIN Struktur_Abteilungen sa ON sa.ID=td.Abteilung " + .($filterID ? "WHERE td.ID = ? " : "") + ."ORDER BY td.Jahr DESC, td.Name DESC, sa.ID ASC"; + if ($stmt = $this->db->prepare($qry)) { + if ($filterID) { + $stmt->bind_param("i", $id); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $termine = $this->termineGetAll($row["ID"]); + + $entry = [ + "ID" => $row["ID"], + "MAIN" => [ + "FULLNAME" => $row["Jahr"]."-".$row["Name"], + "ABTEILUNG" => ["ID" => $row["Abteilung"], "KÜRZEL" => $row["saKürzel"], "NAME" => $row["saName"]], + "JAHR" => $row["Jahr"], + "NAME" => $row["Name"], + ], + "SUB" => [ + "TERMINE" => $termine, + ], + ]; + $this->addEntryToOutput("DEFAULT", $entry, $filterID); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + //$ret["ABTEILUNG"][] = ["ID" => "__NULL__", "NAME" => "Ohne"]; + $res = $this->db->query("SELECT ID, Name FROM Struktur_Abteilungen"); + while ($row = $res->fetch_assoc()) { + $ret["ABTEILUNG"][] = ["ID" => $row["ID"], "NAME" => $row["Name"]]; + } + $ret["TERMINE"] = $this->termineGetAll(null); + return $ret; + } + + private function termineGetAll($dienstplanID) { + $ret = []; + $qry = "SELECT t.ID, t.Beginn, t.Ende, t.Thema, t.Ort, t.Verantwortliche, t.Kategorie, tk.Name KategorieName, tk.Farbe KategorieFarbe " + ."FROM Termine t LEFT JOIN Termine_Kategorien tk ON tk.ID=t.Kategorie WHERE t.Dienstplan ".($dienstplanID===null ? "IS NULL " : "= ".$dienstplanID." ") + ."ORDER BY t.Beginn ASC "; + if ($res = $this->db->query($qry)) { + while ($row = $res->fetch_assoc()) { + $beginn_time = date("H:i", strtotime($row["Beginn"])); + $ende_time = date("H:i", strtotime($row["Ende"])); + $beginn_date = date("D, d.m.Y", strtotime($row["Beginn"])); + $ende_date = "00:00"==$ende_time ? date("D, d.m.Y", strtotime($row["Ende"]) - 1) : date("D, d.m.Y", strtotime($row["Ende"])); + + $entry = [ + "ID" => $row["ID"], + "BEGINN" => $row["Beginn"], + "BEGINN.DATUM" => date("D, d.m.Y", strtotime($row["Beginn"])), + "BEGINN.ZEIT" => date("H:i", strtotime($row["Beginn"])), + "ENDE" => $row["Ende"], + "ENDE.DATUM" => date("d.m.Y", strtotime($row["Ende"])), + "ENDE.ZEIT" => date("H:i", strtotime($row["Ende"])), + "DATUM" => $beginn_date.($beginn_date!=$ende_date ? " - ".$ende_date : ""), + "ZEIT" => ("00:00"==$ende_time && "00:00"==$beginn_time ? "Ganztägig" : $beginn_time." - ".$ende_time), + "ENDE" => $row["Ende"], + "THEMA" => $row["Thema"], + "ORT" => $row["Ort"], + "KATEGORIE" => $row["Kategorie"], + "KATNAME" => $row["KategorieName"], + "KATFARBE" => $row["KategorieFarbe"], + "VERANTWORTLICHE" => $row["Verantwortliche"] ?? "Keine Verantwortliche", + ]; + $ret[] = $entry; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return $ret; + } +} diff --git a/pgs/11_dienstplaene/template.html b/pgs/11_dienstplaene/template.html new file mode 100644 index 0000000..75dd664 --- /dev/null +++ b/pgs/11_dienstplaene/template.html @@ -0,0 +1,93 @@ + + + + + + +
++ + + + + ++ +
+- + +
###LIST.MAIN.ABTEILUNG.NAME###
- +
###LIST.MAIN.FULLNAME###
- Verstecken
+Anzeigen
- +
+ + + + ++ + + + +Dienstplan hinzufügen
+++ + + + ++
+- Abteilung:
+- Jahr:
+- Name:
+- Hinzufügen
++ + + diff --git a/pgs/20_personal/module.json b/pgs/20_personal/module.json new file mode 100755 index 0000000..79a4792 --- /dev/null +++ b/pgs/20_personal/module.json @@ -0,0 +1,34 @@ +{ + "route" : "Personal", + "title" : "Personal", + "table" : "Personal", + "marker": "PERSONAL", + "dependencies": [], + "show" : true, + "useRight": "DARF_PERSONAL_VERWALTEN", + "adminRight": "DARF_PERSONAL_VERWALTEN", + "template": "template.html", + "isPage": true, + "links": { + "Lehrgänge": {"route":"Personal_Lehrgänge", "mainName":"Personal", "subName":"Lehrgänge"}, + "Einweisungen": {"route":"Fahrzeuge_Einweisungen", "mainName":"Personal", "subName":"Fahrzeuge"}, + "Abteilungen": {"route":"Personal_Abteilungen", "mainName":"Personal", "subName":"Abteilungen"}, + "Gruppen": {"route":"Personal_Gruppen", "mainName":"Personal", "subName":"Gruppen"}, + "Bilder": {"route":"Dateien", "mainName":"Personal", "subName":"Bildadresse"} + }, + "mainFields": { + "OFNR": {"label": "OFNr", "type": "Integer", "filter": true, "add": true, "edit": true}, + "PNR": {"label": "PNr", "type": "Integer", "filter": true, "add": true, "edit": true}, + "AUTOPNR": {"label": "Auto-Pnr", "type": "Checkbox", "filter": false, "add": true, "edit": false}, + "NACHNAMEN": {"label": "Nachnamen", "type": "String", "filter": true, "add": true, "edit": true}, + "VORNAMEN": {"label": "Vornamen", "type": "String", "filter": true, "add": true, "edit": true}, + "KATEGORIE": {"label": "Kategorie", "type": "Select", "filter": true, "add": true, "edit": true}, + "LOGIN": {"label": "Login", "type": "String", "filter": true, "add": true, "edit": true}, + "FUNKTION": {"label": "Funktion", "type": "Select", "filter": true, "add": true, "edit": true}, + "AUTOPROZESS": {"label": "Prozess starten", "type": "Select", "filter": false, "add": true, "edit": false}, + "ABTEILUNGEN": {"label": "Abteilungen", "type": "Multiple", "filter": true, "add": false, "edit": false}, + "GRUPPEN": {"label": "Gruppen", "type": "Multiple", "filter": true, "add": false, "edit": false}, + "LEHRGÄNGE": {"label": "Lehrgänge", "type": "Multiple", "filter": true, "add": false, "edit": false}, + "EINWEISUNGEN": {"label": "Einweisungen", "type": "Multiple", "filter": true, "add": false, "edit": false} + } +} diff --git a/pgs/20_personal/page.php b/pgs/20_personal/page.php new file mode 100755 index 0000000..5c95ec7 --- /dev/null +++ b/pgs/20_personal/page.php @@ -0,0 +1,283 @@ +get($id); + + $qry = "UPDATE Personal SET OFnr = ?, Pnr = ?, Login = ?, Nachnamen = ?, Vornamen = ?, Kategorie = ?, Funktion = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $OFnr = $this->man->input["OFNR"] ?? $this->output["MAIN"]["OFNR"]; + $Pnr = $this->man->input["PNR"] ?? $this->output["MAIN"]["PNR"]; + $Login = $this->man->input["LOGIN"] ?? $this->output["MAIN"]["LOGIN"]; + $Nachnamen = $this->man->input["NACHNAMEN"] ?? $this->output["MAIN"]["NACHNAMEN"]; + $Vornamen = $this->man->input["VORNAMEN"] ?? $this->output["MAIN"]["VORNAMEN"]; + $Kategorie = $this->man->input["KATEGORIE"] ?? $this->output["MAIN"]["KATEGORIE"]["ID"]; + $Funktion = array_key_exists("FUNKTION", $this->man->input) + ? $this->man->input["FUNKTION"] + : (is_null($this->output["MAIN"]["FUNKTION"]) ? null : $this->output["MAIN"]["FUNKTION"]["ID"]); + $stmt->bind_param( + "iisssiii", + $OFnr, + $Pnr, + $Login, + $Nachnamen, + $Vornamen, + $Kategorie, + $Funktion, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Personal wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function insert() { + $Pnr = $this->man->input["PNR"] ?? null; + if ($this->man->input["AUTOPNR"] ?? false) { + $res = $this->db->query("SELECT Pnr FROM Personal WHERE Pnr BETWEEN 500 AND 899 ORDER BY Pnr ASC"); + $Pnr = 500; + while ($row = $res->fetch_assoc()) { + if ($row["Pnr"]==$Pnr) { + $Pnr++; + } else { + break; + } } } + $qry = "INSERT INTO Personal (OFnr, Pnr, Login, Nachnamen, Vornamen, Bildadresse, Kategorie, Funktion) VALUES (?,?,?,?,?,NULL,?,?)"; + if ($stmt = $this->db->prepare($qry)) { + $OFnr = $this->man->input["OFNR"] ?? "56"; + $Login = $this->man->input["LOGIN"] ?? ""; + $Nachnamen = $this->man->input["NACHNAMEN"] ?? ""; + $Vornamen = $this->man->input["VORNAMEN"] ?? ""; + $Kategorie = $this->man->input["KATEGORIE"] ?? 0; // Insert will fail if not existing + $Funktion = array_key_exists("FUNKTION", $this->man->input) + ? $this->man->input["FUNKTION"] + : null; + $stmt->bind_param( + "iisssii", + $OFnr, + $Pnr, + $Login, + $Nachnamen, + $Vornamen, + $Kategorie, + $Funktion + ); + $stmt->execute(); + if ($stmt->affected_rows==1) { + $this->man->AddMessage("Personal wurde hinzugefügt!"); + $pid = $this->db->insert_id; + + $autoprozess = $this->man->input["AUTOPROZESS"] ?? ""; + if ($autoprozess!="") { + $stmt2 = $this->db->prepare("SELECT Ziel FROM Prozesse WHERE Name = ?"); + $stmt2->bind_param("s", $autoprozess); + $stmt2->execute(); + $res = $stmt2->get_result(); + while ($row = $res->fetch_assoc()) { + $conditions = json_decode($row["Ziel"], true); + foreach ($conditions as $cond) { + switch ($cond["condition"]) { + case "group_member": + $this->db->query("INSERT INTO Personal_Gruppen (Personal, Gruppen) VALUES (".$pid.", ".$cond["group"].")"); + break; + } } } } + + $this->get($pid); + return 201; + } else { + $this->man->AddMessage("Personal konnte nicht hinzugefügt werden!"); + return 400; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + return 500; + } + } + + protected function get($id = null) { + $this->output = []; + if ($id===null && $this->man->Main()!=null) { + $id = $this->man->Main(); + } + + $where = ""; + $having = ""; + if ($id) { + $where = "AND p.ID = ? "; + } else if (sizeof($this->man->Filter())>0) { + /*$where = "WHERE 1 ";*/ + foreach ($this->man->Filter() as $filter) { + $and = "or"==$filter["and"] ? "OR " : "AND "; + if ("none"==$filter["field"]) { + $where.= $and."1 "; + } else { + switch ($filter["field"]) { + case "OFNR": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>", "<", ">"], "p", "OFnr", $filter["value"]); + break; + case "PNR": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>", "<", ">"], "p", "Pnr", $filter["value"]); + break; + case "NACHNAMEN": + $where.= $and.$this->getFilterString($filter["op"], ["like", "not like"], "p", "Nachnamen", $filter["value"]); + break; + case "VORNAMEN": + $where.= $and.$this->getFilterString($filter["op"], ["like", "not like"], "p", "Vornamen", $filter["value"]); + break; + case "KATEGORIE": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "p", "Kategorie", $filter["value"]); + break; + case "FUNKTION": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "p", "Funktion", $filter["value"]); + break; + case "ABTEILUNGEN": + $having.= $and."(1 "; + foreach ($filter["value"] as $value) { + $having.= "and ".$this->getFilterString($filter["op"], ["like", "not like"], "", "Abteilungen", ",".$value.","); + } + $having.= ") "; + break; + case "GRUPPEN": + $having.= $and."(1 "; + foreach ($filter["value"] as $value) { + $having.= $and.$this->getFilterString($filter["op"], ["like", "not like"], "", "Gruppen", ",".$value.","); + } + $having.= ") "; + break; + case "LEHRGÄNGE": + $having.= $and."(1 "; + foreach ($filter["value"] as $value) { + $having.= $and.$this->getFilterString($filter["op"], ["like", "not like"], "", "Lehrgänge", ",".$value.","); + } + $having.= ") "; + break; + case "EINWEISUNGEN": + $having.= $and."(1 "; + foreach ($filter["value"] as $value) { + $having.= $and.$this->getFilterString($filter["op"], ["like", "not like"], "", "Einweisungen", ",".$value.","); + } + $having.= ") "; + break; + default: + $where.= $and."1 "; + } } } } + + + $qry = "SELECT p.ID, p.OFnr, p.Pnr, p.Login, p.Vornamen, p.Nachnamen, p.Bildadresse, p.Kategorie, pk.Name KategorieName, " + ."p.Funktion, pf.Name FunktionName, pf.Kürzel FunktionKürzel, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT pa.Abteilungen SEPARATOR ','), ',') Abteilungen, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT pg.Gruppen SEPARATOR ','), ',') Gruppen, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT pl.Lehrgänge SEPARATOR ','), ',') Lehrgänge, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT fe.Fahrzeuge SEPARATOR ','), ',') Einweisungen " + ."FROM Personal p " + ."LEFT JOIN Personal_Kategorien pk ON pk.ID=p.Kategorie " + ."LEFT JOIN Personal_Funktionen pf ON pf.ID=p.Funktion " + ."LEFT JOIN Personal_Abteilungen pa ON pa.Personal=p.ID " + ."LEFT JOIN Struktur_Abteilungen sa ON sa.ID=pa.Abteilungen " + ."LEFT JOIN Personal_Gruppen pg ON pg.Personal=p.ID " + ."LEFT JOIN Struktur_Gruppen sg ON sg.ID=pg.Gruppen " + ."LEFT JOIN Personal_Lehrgänge pl ON pl.Personal=p.ID " + ."LEFT JOIN Lehrgänge l ON l.ID = pl.Lehrgänge " + ."LEFT JOIN Fahrzeuge_Einweisungen fe ON fe.Personal=p.ID " + ."LEFT JOIN Fahrzeuge f ON f.ID=fe.Fahrzeuge " + ."WHERE 1 ".$where + ."GROUP BY p.ID " + ."HAVING 1 ".$having + ."ORDER BY p.Nachnamen ASC, p.Vornamen ASC "; + if ($stmt = $this->db->prepare($qry)) { + if ($id) { + $stmt->bind_param("i", $id); + } else { + $this->registerGroup("Führung"); + $this->registerGroup("Kraftfahrer"); + $this->registerGroup("Mannschaft"); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $entry = [ + "ID" => $row["ID"], + "MAIN" => [ + "OFNR" => $row["OFnr"], + "PNR" => $row["Pnr"], + "NR" => $row["OFnr"]."-".$row["Pnr"], + "LOGIN" => $row["Login"], + "EMAIL" => $row["Login"]."@feuerwehr-bs.net", + "VORNAMEN" => $row["Vornamen"], + "NACHNAMEN" => $row["Nachnamen"], + "NAME" => substr($row["Vornamen"], 0, 1).". ".$row["Nachnamen"], + "BILD" => [ + "EXISTIERT" => file_exists($this->prefixImage.$row["Bildadresse"]) && null!==$row["Bildadresse"], + "ADRESSE" => $row["Bildadresse"], + "PFAD" => "/".$this->prefixImage, + ], + "KATEGORIE" => ["ID" => $row["Kategorie"], "NAME" => $row["KategorieName"]], + "FUNKTION" => isset($row["Funktion"]) ? ["ID" => $row["Funktion"], "NAME" => $row["FunktionName"], "KÜRZEL" => $row["FunktionKürzel"]] : null, + "ADMIN" => $this->man->user->HasRight($this->adminRight), + ], + "SUB" => [ + "ABTEILUNGEN" => $this->getSub("ABTEILUNGEN", $row["Abteilungen"]), + "GRUPPEN" => $this->getSub("GRUPPEN", $row["Gruppen"]), + "LEHRGÄNGE" => $this->getSub("LEHRGÄNGE", $row["Lehrgänge"]), + "EINWEISUNGEN" => $this->getSub("EINWEISUNGEN", $row["Einweisungen"]), + ], + ]; + $this->addEntryToOutput($row["KategorieName"], $entry, $id); + } + if ($id && $res->num_rows==0) { + $this->man->AddMessage("Couldn't find requested resource!"); + return 404; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error." / Qry: ".$qry); + return 500; + } + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + $ret["FUNKTION"][] = ["ID" => "__NULL__", "KÜRZEL" => "-", "NAME" => "Ohne"]; + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Personal_Funktionen"); + while ($row = $res->fetch_assoc()) { + $ret["FUNKTION"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Lehrgänge"); + while ($row = $res->fetch_assoc()) { + $ret["LEHRGÄNGE"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Fahrzeuge"); + while ($row = $res->fetch_assoc()) { + $ret["EINWEISUNGEN"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Struktur_Abteilungen"); + while ($row = $res->fetch_assoc()) { + $ret["ABTEILUNGEN"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Struktur_Gruppen"); + while ($row = $res->fetch_assoc()) { + $ret["GRUPPEN"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Name FROM Personal_Kategorien"); + while ($row = $res->fetch_assoc()) { + $ret["KATEGORIE"][] = ["ID" => $row["ID"], "NAME" => $row["Name"]]; + } + return $ret; + } +} diff --git a/pgs/20_personal/template.html b/pgs/20_personal/template.html new file mode 100755 index 0000000..3874b81 --- /dev/null +++ b/pgs/20_personal/template.html @@ -0,0 +1,266 @@ + + + + + +
+ + + + +
###GROUP_BEGIN.GROUP###
+ +++ + ++ + + + + + ++ +
+ + +
++ + + ++ + +
+- + + +
###LIST.SUB.ABTEILUNGEN.KÜRZEL###
- + + +
###LIST.SUB.GRUPPEN.KÜRZEL###
- +
+ + +###LIST.SUB.LEHRGÄNGE.KÜRZEL### +
- +
+ + + + +###LIST.SUB.EINWEISUNGEN.KÜRZEL### +
- + +
###LIST.SUB.ABTEILUNGEN.KÜRZEL###
+
- + +
+
- + +
###LIST.SUB.GRUPPEN.KÜRZEL###
+
- + +
+
- + +
###LIST.SUB.LEHRGÄNGE.KÜRZEL###
+
- + +
+
- + +
###LIST.SUB.EINWEISUNGEN.KÜRZEL###
+
- + +
+
Personal hinzufügen
++ + + ++
- OF-Nr:
+- + P-Nr: + + Niedrigste freie Zahl >= 500, ansonsten
+- Nachnamen:
+- Vornamen:
+- Kategorie: + +
+- Login: @feuerwehr-bs.net
+- Funktion: + +
+- Prozess starten: + +
+- Hinzufügen
++ + +
+ + +
+ + +
+ + + diff --git a/pgs/21_prozesse/module.json b/pgs/21_prozesse/module.json new file mode 100755 index 0000000..9ba5852 --- /dev/null +++ b/pgs/21_prozesse/module.json @@ -0,0 +1,23 @@ +{ + "route" : "Prozesse", + "title" : "Prozesse", + "table" : "Prozesse", + "marker" : "PROZESSE", + "dependencies": ["Prozesse"], + "show" : true, + "useRight": "DARF_PERSONAL_VERWALTEN", + "adminRight": "DARF_PERSONAL_VERWALTEN", + "template": "template.html", + "isPage": true, + "links": { + "Fortschritte": {"route":"Prozesse_Fortschritte", "mainName":"Prozess_Personal", "subName":"Schritt"} + }, + "mainFields": { + "OFNR": {"label": "OFNr", "type": "Integer", "filter": false, "add": false, "edit": true}, + "PNR": {"label": "PNr", "type": "Integer", "filter": false, "add": false, "edit": true}, + "NACHNAMEN": {"label": "Nachnamen", "type": "String", "filter": false, "add": false, "edit": true}, + "VORNAMEN": {"label": "Vornamen", "type": "String", "filter": false, "add": false, "edit": true}, + "KATEGORIE": {"label": "Kategorie", "type": "Select", "filter": false, "add": false, "edit": true}, + "LOGIN": {"label": "Login", "type": "String", "filter": false, "add": false, "edit": true}, + "FUNKTION": {"label": "Funktion", "type": "String", "filter": false, "add": false, "edit": true} +} } diff --git a/pgs/21_prozesse/page.php b/pgs/21_prozesse/page.php new file mode 100755 index 0000000..4afff1a --- /dev/null +++ b/pgs/21_prozesse/page.php @@ -0,0 +1,30 @@ +prozessListe = $this->man->objects["Prozesse"]; + } + + protected function remove($id) { + if (isset($this->man->input["Finish"])) { + return $this->prozessListe->Finish($id, $this->man->input["Finish"]); + } + return 400; + } + + protected function get($id = null) { + $this->output = $this->prozessListe->Get(); + if (null!==$id && $this->output==[]) { + return 404; + } + return 200; + } +} + +?> \ No newline at end of file diff --git a/pgs/21_prozesse/template.html b/pgs/21_prozesse/template.html new file mode 100755 index 0000000..8626cf6 --- /dev/null +++ b/pgs/21_prozesse/template.html @@ -0,0 +1,130 @@ + + + + + + +
+ + + + + +
###GROUP_BEGIN.GROUP###
++ + ++
+- +
###LIST.MAIN.NACHNAMEN###, ###LIST.MAIN.VORNAMEN###
- + +
###LIST.MAIN.FORTSCHRITT###
- + +
Unerledigte Aufgaben!
- Verstecken
+Anzeigen
+ ++ + diff --git a/pgs/22_spinde/module.json b/pgs/22_spinde/module.json new file mode 100644 index 0000000..03fecc7 --- /dev/null +++ b/pgs/22_spinde/module.json @@ -0,0 +1,19 @@ +{ + "route" : "Spinde", + "title" : "Spinde", + "table" : "Spinde", + "marker" : "SPINDE", + "dependencies": [], + "show" : true, + "useRight": "DARF_PERSONAL_VERWALTEN", + "adminRight": "DARF_PERSONAL_VERWALTEN", + "template": "template.html", + "isPage": true, + "mainFields": { + "RAUM": {"label": "Raum", "type": "Select", "filter": true, "add": false, "edit": false}, + "BESITZER": {"label": "PNr", "type": "Select", "filter": true, "add": false, "edit": true}, + "LINKS": {"label": "Links", "type": "Integer", "filter": false, "add": false, "edit": false}, + "Oben": {"label": "Oben", "type": "Integer", "filter": false, "add": false, "edit": false}, + "HÖHE": {"label": "Höhe", "type": "Integer", "filter": false, "add": false, "edit": false}, + "BREITE": {"label": "Breite", "type": "Integer", "filter": false, "add": false, "edit": false} +} } diff --git a/pgs/22_spinde/page.php b/pgs/22_spinde/page.php new file mode 100644 index 0000000..d119725 --- /dev/null +++ b/pgs/22_spinde/page.php @@ -0,0 +1,123 @@ +get($id); + + $qry = "UPDATE Spinde SET Besitzer = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $Besitzer = array_key_exists("BESITZER", $this->man->input) + ? $this->man->input["BESITZER"] + : (is_null($this->output["MAIN"]["BESITZER"]) ? null : $this->output["MAIN"]["BESITZER"]["ID"]); + $stmt->bind_param( + "ii", + $Besitzer, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Spindbesitzer wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function get($id = null) { + $this->output = []; + if ($id===null && $this->man->Main()!=null) { + $id = $this->man->Main(); + } + + $where = ""; + $having = ""; + if ($id) { + $where = "AND s.ID = ? "; + } else if (sizeof($this->man->Filter())>0) { + /*$where = "WHERE 1 ";*/ + foreach ($this->man->Filter() as $filter) { + $and = "or"==$filter["and"] ? "OR " : "AND "; + if ("none"==$filter["field"]) { + $where.= $and."1 "; + } else { + switch ($filter["field"]) { + case "RAUM": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "s", "Raum", $filter["value"]); + break; + case "BESITZER": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "s", "Besitzer", $filter["value"]); + break; + default: + $where.= $and."1 "; + } } } } + + $qry = "SELECT s.ID, s.Raum, s.Besitzer, s.Bezeichnung, s.Links, s.Oben, s.Höhe, s.Breite, s.Orientation, p.Pnr, p.Nachnamen, p.Vornamen, sr.Name srName " + ."FROM Spinde s " + ."LEFT JOIN Spinde_Räume sr ON sr.ID=s.Raum " + ."LEFT JOIN Personal p ON p.ID=s.Besitzer " + ."WHERE 1 ".$where + ."ORDER BY s.Raum ASC "; + if ($stmt = $this->db->prepare($qry)) { + if ($id) { + $stmt->bind_param("i", $id); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $entry = [ + "ID" => strval($row["ID"]), + "GROUP" => $row["srName"], + /*"POS" => [ + "LEFT" => $row["Links"], + "TOP"=> $row["Oben"], + "WIDTH" => $row["Breite"], + "HEIGHT" => $row["Höhe"], + "ROT" => $row["Orientation"], + ],*/ + "MAIN" => [ + "FREI" => is_null($row["Besitzer"]), + "RAUM" => $row["srName"], + "BEZEICHNUNG" => $row["Bezeichnung"], + "BESITZER" => [ + "ID" => $row["Besitzer"], + "PNR" => $row["Pnr"], + "VORNAMEN" => $row["Vornamen"], + "NACHNAMEN" => $row["Nachnamen"], + "NAME" => (is_null($row["Vornamen"]) ? "" : preg_replace("/(? [], + ]; + $this->addEntryToOutput($row["srName"], $entry, $id); + } + if ($id && $res->num_rows==0) { + $this->man->AddMessage("Couldn't find requested resource!"); + return 404; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error." / Qry: ".$qry); + return 500; + } + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + $ret["BESITZER"][] = ["ID" => "__NULL__", "NAME" => "Frei"]; + $res = $this->db->query("SELECT ID, Pnr, Vornamen, Nachnamen FROM Personal ORDER BY Nachnamen ASC, Vornamen ASC"); + while ($row = $res->fetch_assoc()) { + $ret["BESITZER"][] = ["ID" => $row["ID"], "NAME" => preg_replace("/(? + + + + ++ + + +
- ###LIST.SUB.SEKTION.NAME###
+
+ +- +
- +
###LIST.SUB.SEKTION.SCHRITT.NAME###
- +
###LIST.SUB.SEKTION.SCHRITT.VON###
- +
###LIST.SUB.SEKTION.SCHRITT.WANN###
- + +
###LIST.SUB.SEKTION.SCHRITT.NOTIZ###
- Zurücksetzen
+ ++
+ +- +
- +
###LIST.SUB.SEKTION.SCHRITT.NAME###
- +
###LIST.SUB.SEKTION.SCHRITT.VON###
- +
###LIST.SUB.SEKTION.SCHRITT.WANN###
- + +
Nicht zutreffend
- Zurücksetzen
+ ++ +
+ +- + +
- + +
- + + +
###LIST.SUB.SEKTION.SCHRITT.NAME###
- + +
###LIST.SUB.SEKTION.SCHRITT.INDIKATOREN.NAME###
- + + + +
###LIST.SUB.SEKTION.SCHRITT.INDIKATOREN.NAME###
- + + + + ###LIST.SUB.SEKTION.SCHRITT.HELFER.NAME### +
+ +- Abhaken
+ +- Nicht zutreffend
+ + + ++
+ + + +- +
++
- +
###LIST.SUB.SEKTION.SCHRITT.NAME###
+
+- Prozess beenden:
+ +- Erfolg
+ +- Erfolg
+ + +- Fehlschlag
+ +- Fehlschlag
+ + ++ + + +###GROUP_BEGIN.GROUP###
+
+ + +- ###LIST.MAIN.RAUM###
+- ###LIST.MAIN.BEZEICHNUNG###
+ +- + +
Frei
- + +
###LIST.MAIN.BESITZER.NAME###
- +
+
+ + + + diff --git a/pgs/30_einsaetze/module.json b/pgs/30_einsaetze/module.json new file mode 100644 index 0000000..a1eb9b4 --- /dev/null +++ b/pgs/30_einsaetze/module.json @@ -0,0 +1,46 @@ +{ + "route" : "Einsätze", + "title" : "(Einsätze)", + "table" : "Einsätze", + "marker": "EINSÄTZE", + "dependencies": [], + "show" : 1, + "rights": { + "admin": ["DARF_DIENSTE_VERWALTEN"], + "see": [], + "edit": { + "DARF_EA_DIENSTE_VERWALTEN": { + "GRUPPE":[1] + }, + "DARF_JF_DIENSTE_VERWALTEN": { + "GRUPPE":[2] + }, + "DARF_AE_DIENSTE_VERWALTEN": { + "GRUPPE":[3] + }, + "DARF_KF_DIENSTE_VERWALTEN": { + "KATEGORIE":[4], + "GRUPPE":[1,6] + }, + "DARF_MB_DIENSTE_VERWALTEN": { + "KATEGORIE":[13], + "GRUPPE":[null] + } } }, + "useRight": ["DARF_DIENSTE_VERWALTEN"], + "adminRight": ["DARF_DIENSTE_VERWALTEN", "DARF_KF_DIENSTE_VERWALTEN", "DARF_MB_DIENSTE_VERWALTEN"], + "template": "template.html", + "isPage": true, + "links": { + "Teilnahme": {"route":"Einsätze_Teilnahmen", "mainName":"Einsätze", "subName":"Personal"} + }, + "mainFields": { + "BEGINN": {"label": "Beginn", "type": "DateTime", "filter": true, "add": true, "edit": true}, + "ENDE": {"label": "Ende", "type": "DateTime", "filter": true, "add": true, "edit": true}, + "THEMA": {"label": "Thema", "type": "String", "filter": true, "add": true, "edit": true}, + "ORT": {"label": "Ort", "type": "String", "filter": false, "add": true, "edit": true}, + "DIENSTPLAN": {"label": "Dienstplan", "type": "Select", "filter": true, "add": true, "edit": true}, + "KATEGORIE": {"label": "Kategorie", "type": "Select", "filter": true, "add": true, "edit": true}, + "PLATZVERGABE": {"label": "Platzvergabe", "type": "Select", "filter": false, "add": true, "edit": true}, + "AUSGELOST": {"label": "Ausgelost", "type": "Checkbox", "filter": false, "add": true, "edit": true}, + "GRUPPE": {"label": "Gruppe", "type": "Select", "filter": true, "add": true, "edit": true} +} } \ No newline at end of file diff --git a/pgs/30_einsaetze/page.php b/pgs/30_einsaetze/page.php new file mode 100644 index 0000000..ec62d15 --- /dev/null +++ b/pgs/30_einsaetze/page.php @@ -0,0 +1,270 @@ +get($id); + $qry = "UPDATE Einsätze SET Beginn = ?, Ende = ?, Thema = ?, Ort = ?, Dienstplan = ?, " + ."Kategorie = ?, Platzvergabe = ?, Ausgelost = ?, Gruppe = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $Beginn = $this->man->input["ALARMIERUNGSZEIT"] ?? $this->output["MAIN"]["ALARMIERUNGSZEIT"]; + $Ende = $this->man->input["EINSATZENDE"] ?? $this->output["MAIN"]["EINSATZENDE"]; + $Thema = $this->man->input["THEMA"] ?? $this->output["MAIN"]["THEMA"]; + $Ort = $this->man->input["ORT"] ?? $this->output["MAIN"]["ORT"]; + $Dienstplan = $this->man->input["DIENSTPLAN"] ?? $this->output["MAIN"]["DIENSTPLAN"]["ID"]; + $Kategorie = $this->man->input["KATEGORIE"] ?? $this->output["MAIN"]["KATEGORIE"]["ID"]; + $Platzvergabe = array_key_exists("PLATZVERGABE", $this->man->input) + ? $this->man->input["PLATZVERGABE"] + : (is_null($this->output["MAIN"]["PLATZVERGABE"]) ? null : $this->output["MAIN"]["PLATZVERGABE"]["ID"]); + $Ausgelost = $this->man->input["AUSGELOST"] ?? $this->output["MAIN"]["AUSGELOST"]; + $Gruppe = array_key_exists("GRUPPE", $this->man->input) + ? $this->man->input["GRUPPE"] + : (is_null($this->output["MAIN"]["GRUPPE"]) ? null : $this->output["MAIN"]["GRUPPE"]["ID"]); + $stmt->bind_param( + "ssssiiiiii", + $Beginn, + $Ende, + $Thema, + $Ort, + $Dienstplan, + $Kategorie, + $Platzvergabe, + $Ausgelost, + $Gruppe, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Einsatz wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function insert() { + $qry = "INSERT INTO Einsätze (Beginn, Ende, Thema, Ort, Dienstplan, Kategorie, Platzvergabe, Gruppe) VALUES (?,?,?,?,?,?,?,?)"; + if ($stmt = $this->db->prepare($qry)) { + $Beginn = $this->man->input["ALARMIERUNGSZEIT"] ?? ""; + $Ende = $this->man->input["EINSATZENDE"] ?? ""; + $Thema = $this->man->input["THEMA"] ?? ""; + $Ort = $this->man->input["ORT"] ??""; + $Dienstplan = $this->man->input["DIENSTPLAN"] ?? null; + $Kategorie = $this->man->input["KATEGORIE"] ?? null; + $Platzvergabe = array_key_exists("PLATZVERGABE", $this->man->input) + ? $this->man->input["PLATZVERGABE"] + : null; + $Gruppe = array_key_exists("GRUPPE", $this->man->input) + ? $this->man->input["GRUPPE"] + : null; + $stmt->bind_param( + "ssssiiii", + $Beginn, + $Ende, + $Thema, + $Ort, + $Dienstplan, + $Kategorie, + $Platzvergabe, + $Gruppe + ); + $stmt->execute(); + if ($stmt->affected_rows==1) { + $this->man->AddMessage("Einsatz wurde hinzugefügt!"); + $this->get($this->db->insert_id); + return 201; + } else { + $this->man->AddMessage("Einsatz konnte nicht hinzugefügt werden! (" + .$Beginn." / ".$Ende." / ".$Thema." / ".$Ort." / " + .$Dienstplan." / ".$Kategorie." / ".$Platzvergabe." / ".$Gruppe.")" + ); + return 400; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + return 500; + } + } + + protected function get($id = null) { + $this->output = []; + $where = ""; + if ($id) { + $where = "WHERE e.ID = ? "; + } else if (sizeof($this->man->Filter())>0) { + $where = "WHERE 1 "; + foreach ($this->man->Filter() as $filter) { + $where.= "or"==$filter["and"] ? "OR " : "AND "; + if ("none"==$filter["field"]) { + $where.= "1 "; + } else { + switch ($filter["field"]) { + case "ALARMIERUNGSZEIT": + $where.= $this->getFilterString($filter["op"], ["<=", ">="], "t", "Beginn", $filter["value"]); + break; + case "EINSATZENDE": + $where.= $this->getFilterString($filter["op"], ["<=", ">="], "t", "Ende", $filter["value"]); + break; + case "THEMA": + $where.= $this->getFilterString($filter["op"], ["like", "not like"], "t", "Thema", $filter["value"]); + break; + case "DIENSTPLAN": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Dienstplan", $filter["value"]); + break; + case "KATEGORIE": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Kategorie", $filter["value"]); + break; + case "GRUPPE": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Gruppe", $filter["value"]); + break; + default: + $where.= "1 "; + } } } + } else { + /*$datefrom = date("Y-m-d H:i:s"); + $this->man->filter[] = ["and" => "and", "field" => "EINSATZENDE", "op" => ">=", "value" => $datefrom]; + $where = "WHERE ".$this->getFilterString(">=", [">="], "t", "Ende", $datefrom);*/ + } + + $qry = "SELECT e.ID, e.Alarmierungszeit, e.Einsatzende, e.Adresse, e.Art, e.Zusammenfassung, e.Einsatzleiter, e.Überprüft " + .", ea.Kategorie, ea.Name eaName, ea.Stichworte " + .", ek.Name ekName, ek.Farbe " + ."FROM Einsätze e " + ."LEFT JOIN Einsätze_Arten ea ON ea.ID=e.Art " + ."LEFT JOIN Einsätze_Kategorien ek ON ek.ID=ea.Kategorie " + .$where + ."ORDER BY e.Alarmierungszeit DESC "; + if ($stmt = $this->db->prepare($qry)) { + $userID = $this->man->user->ID(); + if ($id) { + $stmt->bind_param("i", $id); + } else { + //$stmt->bind_param("i", $userID, $userID); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $beginn_time = date("H:i", strtotime($row["Alarmierungszeit"])); + $ende_time = date("H:i", strtotime($row["Einsatzende"])); + $beginn_date = date("D, d.m.Y", strtotime($row["Alarmierungszeit"])); + $ende_date = "0000-00-00 00:00:00"== $row["Einsatzende"] ? + "?" : ( + "00:00"==$ende_time ? date("D, d.m.Y", strtotime($row["Einsatzende"]) - 1) : date("D, d.m.Y", strtotime($row["Einsatzende"])) + ); + + $entry = [ + "ID" => $row["ID"], + "MAIN" => [ + "ALARMIERUNGSZEIT" => $row["Alarmierungszeit"], + "ALARMIERUNGSZEIT.DATUM" => date("D, d.m.Y", strtotime($row["Alarmierungszeit"])), + "ALARMIERUNGSZEIT.DATE" => date("Y-m-d", strtotime($row["Alarmierungszeit"])), + "ALARMIERUNGSZEIT.ZEIT" => date("H:i", strtotime($row["Alarmierungszeit"])), + "ALARMIERUNGSZEIT.TIME" => date("H:i", strtotime($row["Alarmierungszeit"])), + "EINSATZENDE" => $row["Einsatzende"], + "EINSATZENDE.DATUM" => date("d.m.Y", strtotime($row["Einsatzende"])), + "EINSATZENDE.DATE" => date("Y-m-d", strtotime($row["Einsatzende"])), + "EINSATZENDE.ZEIT" => date("H:i", strtotime($row["Einsatzende"])), + "EINSATZENDE.TIME" => date("H:i", strtotime($row["Einsatzende"])), + "DATUM" => $beginn_date.($beginn_date!=$ende_date && "?"!=$ende_date ? " - ".$ende_date : ""), + "ZEIT" => $beginn_time." - ".("?"==$ende_date ? "?" : $ende_time), + "ADRESSE" => $row["Adresse"], + "ART" => [ + "ID" => $row["Art"], + "KATEGORIE" => ["ID" => $row["Kategorie"], "NAME" => $row["ekName"], "FARBE" => $row["Farbe"]], + "NAME" => is_null($row["Art"]) ? "Unbekannt" : $row["eaName"], + "STICHWORTE" => $row["Stichworte"] + ], + "ZUSAMMENFASSUNG" => $row["Zusammenfassung"], + "EINSATZLEITER" => $row["Einsatzleiter"], + "ÜBERPRÜFT" => $row["Überprüft"], + "ADMIN" => true, + ], + "SUB" => [ + /*"ANMELDUNG" => $registration, + "TEILNAHME" => $isPrivileged ? $teilnahme : []*/ + ], + ]; + $group = date("Y", strtotime($row["Alarmierungszeit"])); + $this->addEntryToOutput($group, $entry, $id); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + $res = $this->db->query("SELECT ID, Jahr, Name FROM Termine_Dienstpläne ORDER BY Jahr DESC, Name DESC"); + while ($row = $res->fetch_assoc()) { + $ret["DIENSTPLAN"][] = ["ID" => $row["ID"], "JAHR" => $row["Jahr"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Name FROM Einsätze_Kategorien"); + while ($row = $res->fetch_assoc()) { + $ret["KATEGORIE"][] = ["ID" => $row["ID"], "NAME" => $row["Name"]]; + } + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Struktur_Abteilungen"); + while ($row = $res->fetch_assoc()) { + $ret["ABTEILUNG"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + $ret["PLATZVERGABE"][] = ["ID" => "__NULL__", "NAME" => "Ohne"]; + $res = $this->db->query("SELECT ID, Name FROM Termine_Platzvergaben ORDER BY Name ASC"); + while ($row = $res->fetch_assoc()) { + $ret["PLATZVERGABE"][] = ["ID" => $row["ID"], "NAME" => $row["Name"]]; + } + $ret["GRUPPE"][] = ["ID" => "__NULL__", "KÜRZEL" => "Ohne", "NAME" => "Ohne"]; + $res = $this->db->query("SELECT ID, Kürzel, Name FROM Struktur_Gruppen ORDER BY Name ASC"); + while ($row = $res->fetch_assoc()) { + $ret["GRUPPE"][] = ["ID" => $row["ID"], "KÜRZEL" => $row["Kürzel"], "NAME" => $row["Name"]]; + } + if ($admin) { + $res = $this->db->query("SELECT p.ID, Nachnamen, Vornamen, pk.Name pkName FROM Personal p LEFT JOIN Personal_Kategorien pk ON pk.ID=p.Kategorie " + ."ORDER BY pk.Reihenfolge ASC, Nachnamen ASC, Vornamen ASC"); + $lastKategorie = ""; + while ($row = $res->fetch_assoc()) { + if ($row["pkName"]!=$lastKategorie) { + $ret["TEILNAHME"][] = ["ID" => 0, "NAME" => " === ".$row["pkName"]." === "]; + $lastKategorie = $row["pkName"]; + } + $ret["TEILNAHME"][] = ["ID" => $row["ID"], "NAME" => preg_replace("/(?db->prepare($qry)) { + $stmt->bind_param("i", $terminID); + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $status = $row["Status"]==2 ? "LEAD" : ($row["Status"]==1 ? "CAN" : "WANT"); + $entry = [ + "ID" => $row["ID"], + "NAME" => preg_replace("/(? $row["Nachnamen"], + "VORNAMEN" => $row["Vornamen"], + "STATUS" => "__CASE__:".$status, + "LEHRGÄNGE" => $row["lehrgänge"] ?? "Keine", + ]; + $ret[] = $entry; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return $ret; +} } diff --git a/pgs/30_einsaetze/template.html b/pgs/30_einsaetze/template.html new file mode 100644 index 0000000..ea6465f --- /dev/null +++ b/pgs/30_einsaetze/template.html @@ -0,0 +1,260 @@ + + + + +- +
- +
- +
+ + + +###GROUP_BEGIN.GROUP###
+ ++ + + + + + +++ + + + ++
+- ↳ ###LIST.SUB.ANMELDUNG.RESTRICTIONS###
+ + +- Du darfst kommen
+ +- Auf Warteliste
+ +- Zur Auslosung angemeldet
+ +- Bei Interesse bitte anmelden
+ +- Du leitest diesen Dienst
+ + +- Noch ###LIST.SUB.ANMELDUNG.FREE.NUM### Plätze frei
+ + +- ###LIST.SUB.ANMELDUNG.ACTION.NAME###
+ +++ ++
+- ↳ ###LIST.SUB.ANMELDUNG.RESTRICTIONS###
+ + +- Du darfst kommen
+ +- Auf Warteliste
+ +- Zur Auslosung angemeldet
+ +- Bei Interesse bitte anmelden
+ +- Du leitest diesen Dienst
+ + +- Noch ###LIST.SUB.ANMELDUNG.FREE.NUM### Plätze frei
+ + +- ###LIST.SUB.ANMELDUNG.ACTION.NAME###
+ ++ ++ + + + + + + +Termin hinzufügen
+++ + + + ++
- Beginn:
+- Ende:
+- Thema:
+- Ort:
+- Dienstplan: + +
+- Kategorie: + +
+- Platzvergabe: + +
+- Ausgelost:
+- Gruppe: + +
+- Hinzufügen
++ + + diff --git a/pgs/40_anwesenheit/module.json b/pgs/40_anwesenheit/module.json new file mode 100644 index 0000000..0cd12db --- /dev/null +++ b/pgs/40_anwesenheit/module.json @@ -0,0 +1,24 @@ +{ + "route" : "Anwesenheiten", + "title" : "(Anwesenheiten)", + "table" : "Anwesenheiten", + "marker": "ANWESENHEITEN", + "dependencies": [], + "show" : 1, + "rights": { + "admin": ["DARF_DIENSTE_VERWALTEN"], + "see": [], + "edit": { + } }, + "useRight": ["DARF_DIENSTE_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN", "DARF_ANWESENHEITEN_VERWALTEN"], + "adminRight": ["DARF_DIENSTE_VERWALTEN", "DARF_EINSÄTZE_VERWALTEN", "DARF_ANWESENHEITEN_VERWALTEN"], + "template": "template.html", + "isPage": true, + "links": { + "Termine": {"route":"Anwesenheiten_Termine", "mainName":"Anwesenheiten", "subName":"Termine"}, + "Einsätze": {"route":"Anwesenheiten_Einsätze", "mainName":"Anwesenheiten", "subName":"Einsätze"}, + "Personal": {"route":"Anwesenheiten_Personal", "mainName":"Anwesenheiten", "subName":"Personal"} + }, + "mainFields": { + "BEZEICHNUNG": {"label": "Bezeichnung", "type": "String", "filter": true, "add": true, "edit": true} +} } \ No newline at end of file diff --git a/pgs/40_anwesenheit/page.php b/pgs/40_anwesenheit/page.php new file mode 100644 index 0000000..995b02f --- /dev/null +++ b/pgs/40_anwesenheit/page.php @@ -0,0 +1,172 @@ +man->AddMessage($id); + $this->get($id); + $qry = "UPDATE Anwesenheiten SET Bezeichnung = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $Bezeichnung = $this->man->input["BEZEICHNUNG"] ?? $this->output["MAIN"]["BEZEICHNUNG"]; + $stmt->bind_param( + "si", + $Bezeichnung, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Anwesenheitsliste wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function insert() { + $qry = "INSERT INTO Anwesenheiten (Bezeichnung) VALUES (?)"; + if ($stmt = $this->db->prepare($qry)) { + $Bezeichnung = $this->man->input["BEZEICHNUNG"] ?? ""; + $stmt->bind_param("s", $Bezeichnung); + $stmt->execute(); + if ($stmt->affected_rows==1) { + $this->man->AddMessage("Anwesenheitsliste wurde hinzugefügt!"); + $this->get($this->db->insert_id); + return 201; + } else { + $this->man->AddMessage("Anwesenheitsliste konnte nicht hinzugefügt werden! (".$Bezeichnung.")" + ); + return 400; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + return 500; + } + } + + protected function get($id = null) { + $this->output = []; + $where = ""; + if ($id) { + $where = "WHERE a.ID = ? "; + } else if (sizeof($this->man->Filter())>0) { + $where = "WHERE 1 "; + foreach ($this->man->Filter() as $filter) { + $where.= "or"==$filter["and"] ? "OR " : "AND "; + if ("none"==$filter["field"]) { + $where.= "1 "; + } else { + switch ($filter["field"]) { + /*case "BEGINN": + $where.= $this->getFilterString($filter["op"], ["<=", ">="], "t", "Beginn", $filter["value"]); + break; + case "ENDE": + $where.= $this->getFilterString($filter["op"], ["<=", ">="], "t", "Ende", $filter["value"]); + break; + case "THEMA": + $where.= $this->getFilterString($filter["op"], ["like", "not like"], "t", "Thema", $filter["value"]); + break; + case "DIENSTPLAN": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Dienstplan", $filter["value"]); + break; + case "KATEGORIE": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Kategorie", $filter["value"]); + break; + case "GRUPPE": + $where.= $this->getFilterString($filter["op"], ["=", "<>"], "t", "Gruppe", $filter["value"]); + break;*/ + default: + $where.= "1 "; + } } } + } else { + $where = "WHERE 1 "; + } + + $qry = "SELECT a.*, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT at.Termine SEPARATOR ','), ',') Termine, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT ae.Einsätze SEPARATOR ','), ',') Einsätze, " + ."CONCAT(',', GROUP_CONCAT(DISTINCT ap.Personal SEPARATOR ','), ',') Personal " + ."FROM Anwesenheiten a " + ."LEFT JOIN Anwesenheiten_Termine at ON a.ID=at.Anwesenheiten " + ."LEFT JOIN Anwesenheiten_Einsätze ae ON a.ID=ae.Anwesenheiten " + ."LEFT JOIN Anwesenheiten_Personal ap ON a.ID=ap.Anwesenheiten " + .$where + ."GROUP BY a.ID "; + if ($stmt = $this->db->prepare($qry)) { + $userID = $this->man->user->ID(); + if ($id) { + $stmt->bind_param("i", $id); + } else { + + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $entry = [ + "ID" => strval($row["ID"]), + "MAIN" => [ + "BEZEICHNUNG" => $row["Bezeichnung"] + ], + "SUB" => [ + "TERMINE" => $this->getSub("TERMINE", $row["Termine"]), + "EINSÄTZE" => $this->getSub("EINSÄTZE", $row["Einsätze"]), + "PERSONAL" => $this->getSub("PERSONAL", $row["Personal"]) + ], + ]; + $this->addEntryToOutput("DEFAULT", $entry, $id); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + $res = $this->db->query("SELECT ID, Beginn, Thema " + ."FROM Termine " + ."ORDER BY Beginn DESC"); + while ($row = $res->fetch_assoc()) { + $ret["TERMINE"][] = ["ID" => $row["ID"], "BEGINN" => $row["Beginn"], "THEMA" => $row["Thema"]]; + } + $res = $this->db->query("SELECT e.ID, e.Alarmierungszeit, e.Adresse, ea.Name aName " + ."FROM Einsätze e " + ."LEFT JOIN Einsätze_Arten ea ON ea.ID=e.Art " + ."ORDER BY Alarmierungszeit DESC"); + while ($row = $res->fetch_assoc()) { + $ret["EINSÄTZE"][] = [ + "ID" => $row["ID"], + "ALARMIERUNGSZEIT" => $row["Alarmierungszeit"], + "ADRESSE" => $row["Adresse"], + "ART" => $row["aName"] + ]; + } + $res = $this->db->query("SELECT p.ID, Nachnamen, Vornamen, pk.Name pkName FROM Personal p LEFT JOIN Personal_Kategorien pk ON pk.ID=p.Kategorie " + ."ORDER BY pk.Reihenfolge ASC, Nachnamen ASC, Vornamen ASC"); + $lastKategorie = ""; + while ($row = $res->fetch_assoc()) { + if ($row["pkName"]!=$lastKategorie) { + $ret["PERSONAL"][] = ["ID" => 0, "NAME" => " === ".$row["pkName"]." === "]; + $lastKategorie = $row["pkName"]; + } + $ret["PERSONAL"][] = ["ID" => $row["ID"], "NAME" => preg_replace("/(?db->query("SELECT ID, Name, Kürzel FROM Fahrzeuge"); + $ret["FAHRZEUGE"][] = ["ID" => null, "NAME" => "Keins", "KÜRZEL" => "-"]; + while ($row = $res->fetch_assoc()) { + $ret["FAHRZEUGE"][] = [ + "ID" => $row["ID"], + "NAME" => $row["Name"], + "KÜRZEL" => $row["Kürzel"] + ]; + } + return $ret; + } + +} diff --git a/pgs/40_anwesenheit/template.html b/pgs/40_anwesenheit/template.html new file mode 100644 index 0000000..93a5c9c --- /dev/null +++ b/pgs/40_anwesenheit/template.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + +
+ ++ + + + + +Anwesenheitsliste hinzufügen
+++ + + ++
+- Bezeichnung:
+- Hinzufügen
++ + +
+ + +
+
+
+ + + + + + + + diff --git a/pgs/50_fahrzeuge/module.json b/pgs/50_fahrzeuge/module.json new file mode 100644 index 0000000..09ed850 --- /dev/null +++ b/pgs/50_fahrzeuge/module.json @@ -0,0 +1,19 @@ +{ + "route" : "Fahrzeuge", + "title" : "(Fahrzeuge)", + "table" : "Fahrzeuge", + "marker" : "FAHRZEUGE", + "dependencies": [], + "show" : true, + "useRight": "DARF_FAHRZEUGE_VERWALTEN", + "adminRight": "DARF_FAHRZEUGE_VERWALTEN", + "template": "template.html", + "isPage": true, + "links": { + "Bilder": {"route":"Dateien", "mainName":"Fahrzeuge", "subName":"Bild"}, + "Eingewiesene": {"route":"Fahrzeuge_Einweisungen", "mainName":"Fahrzeuge", "subName":"Personal"} + }, + "mainFields": { + "KÜRZEL": {"label": "Kürzel", "type": "Select", "filter": true, "add": false, "edit": false}, + "NAME": {"label": "Name", "type": "Select", "filter": true, "add": false, "edit": false} +} } diff --git a/pgs/50_fahrzeuge/page.php b/pgs/50_fahrzeuge/page.php new file mode 100644 index 0000000..721c545 --- /dev/null +++ b/pgs/50_fahrzeuge/page.php @@ -0,0 +1,136 @@ +get($id); + + $qry = "UPDATE Fahrzeuge SET Kürzel = ?, Name = ? WHERE ID = ?"; + if ($stmt = $this->db->prepare($qry)) { + $name = array_key_exists("NAME", $this->man->input) ? $this->man->input["NAME"] : $this->output["MAIN"]["NAME"]; + $kürzel = array_key_exists("KÜRZEL", $this->man->input) ? $this->man->input["KÜRZEL"] : $this->output["MAIN"]["KÜRZEL"]; + $stmt->bind_param( + "ssi", + $kürzel, + $name, + $id + ); + if ($stmt->execute()) { + $this->man->AddMessage("Fahrzeug wurde aktualisiert!"); + $this->get($id); + return 200; + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + } + return 500; + } + + protected function insert() { + $res = $this->db->query("SELECT UUID_SHORT() uuid"); + $uuid = $res->fetch_assoc()["uuid"]; + + $qry = "INSERT INTO Fahrzeuge (ID, Kürzel, Name) VALUES (?,?,?)"; + if ($stmt = $this->db->prepare($qry)) { + $Kürzel = $this->man->input["KÜRZEL"] ?? ""; + $Name = $this->man->input["NAME"] ?? ""; + $stmt->bind_param( + "sss", + $uuid, + $Kürzel, + $Name + ); + $stmt->execute(); + if ($stmt->affected_rows==1) { + $this->man->AddMessage("Fahrzeug wurde hinzugefügt!"); + $this->get($uuid); + return 201; + } else { + $this->man->AddMessage("Fahrzeug konnte nicht hinzugefügt werden! (".$Kürzel." / ".$Name.")"); + return 400; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error); + return 500; + } + } + + protected function get($id = null) { + $this->output = []; + $where = ""; + $having = ""; + if ($id) { + $where = "AND f.ID = ? "; + } else if (sizeof($this->man->Filter())>0) { + /*$where = "WHERE 1 ";*/ + foreach ($this->man->Filter() as $filter) { + $and = "or"==$filter["and"] ? "OR " : "AND "; + if ("none"==$filter["field"]) { + $where.= $and."1 "; + } else { + switch ($filter["field"]) { +/* case "RAUM": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "s", "Raum", $filter["value"]); + break; + case "BESITZER": + $where.= $and.$this->getFilterString($filter["op"], ["=", "<>"], "s", "Besitzer", $filter["value"]); + break;*/ + default: + $where.= $and."1 "; + } } } } + + $qry = "SELECT f.*, CONCAT(',', GROUP_CONCAT(DISTINCT fe.Personal SEPARATOR ','), ',') Eingewiesene FROM Fahrzeuge f " + ."LEFT JOIN Fahrzeuge_Einweisungen fe ON fe.Fahrzeuge=f.ID " + ."LEFT JOIN Personal p ON p.ID=fe.Personal " + ."WHERE 1 ".$where + ."GROUP BY f.ID "; +// ."ORDER BY s.Raum ASC "; + if ($stmt = $this->db->prepare($qry)) { + if ($id) { + $stmt->bind_param("s", $id); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $entry = [ + "ID" => strval($row["ID"]), + "GROUP" => "Fahrzeuge", + "MAIN" => [ + "KÜRZEL" => $row["Kürzel"], + "NAME" => $row["Name"], + "BILD" => [ + "EXISTIERT" => file_exists($this->prefixImage.$row["Bild"]) && null!==$row["Bild"], + "ADRESSE" => $row["Bild"], + "PFAD" => "/".$this->prefixImage, + ], + ], + "SUB" => [ + "EINGEWIESENE" => $this->getSub("EINGEWIESENE", $row["Eingewiesene"]), + ], + ]; + $this->addEntryToOutput("Fahrzeuge", $entry, $id); + } + if ($id && $res->num_rows==0) { + $this->man->AddMessage("Couldn't find requested resource!"); + return 404; + } + } else { + $this->man->AddMessage("Mysql error: ".$this->db->error." / Qry: ".$qry); + return 500; + } + return 200; + } + + protected function fillOptions($admin = false) { + $ret = array(); + $res = $this->db->query("SELECT ID, OFnr, Pnr, Vornamen, Nachnamen FROM Personal ORDER BY Nachnamen ASC, Vornamen ASC"); + while ($row = $res->fetch_assoc()) { + $ret["EINGEWIESENE"][] = ["ID" => $row["ID"], "NAME" => preg_replace("/(? + + + + + + +
+ +++ + ++ + + + + + ++ +
+ + +
+ ++ + + + + +Fahrzeug hinzufügen
++ + + ++
- Name:
+- Kürzel:
+- Hinzufügen
++ + + diff --git a/pgs/admin/admin.html b/pgs/admin/admin.html new file mode 100755 index 0000000..a11baca --- /dev/null +++ b/pgs/admin/admin.html @@ -0,0 +1,121 @@ + + + + + +
+ + + + +Passwort ändern
+ ++ + + + +Autorenschaft
+ ++ + + + +Zu dieser Gästegruppe gehörende Accounts
+ ++ + + + +Zusätzlichen Account einrichten
+ ++ + + + diff --git a/pgs/admin/index.php b/pgs/admin/index.php new file mode 100755 index 0000000..6835867 --- /dev/null +++ b/pgs/admin/index.php @@ -0,0 +1,144 @@ + tplExtrSection(tplLoadFile("pgs/admin/admin.html"), "###ADMIN###"), + "pwd" => "", + "gsp" => "", + "acc" => "", + "add" => "", + "list" => "" +); + +if ($userID = lgnCheckLogin($mysqli)) { + // Passwort ändern + $password_changed_success = false; + if (isset($input["userMod"], $input["secToken"], $input["passOld"], $input["passNew"], $input["passRepeat"]) + && $input["secToken"]==$_SESSION["secTokenVerify"] && $input["userMod"]=="Passwort ändern") + { + $password_changed_success = lgnChangePass($mysqli, $_SESSION["userID"], $input["passOld"], $input["passNew"], $input["passRepeat"]); + } else if (isset($input["addAccount"]) && $input["secToken"]==$_SESSION["secTokenVerify"]) { + if (strlen($input["admin_addacc_mail"])>4 && strpos($input["admin_addacc_mail"], "@") && strlen($input["admin_addacc_pass"])>11) { + $stmt = $mysqli->prepare("SELECT groupID, roleID FROM users WHERE ID = ?"); + $stmt->bind_param("i", $userID); + $stmt->execute(); + $stmt->bind_result($groupID, $roleID); + $stmt->fetch(); + $stmt->close(); + + $password = lgnTransformPassword($input["admin_addacc_pass"]); + if ($stmt = $mysqli->prepare("INSERT INTO users (groupID, roleID, email, iterations, salt, hash) VALUES (?, ?, ?, ?, ?, ?)")) { + $stmt->bind_param("iisiss", $groupID, $roleID, $input["admin_addacc_mail"], $password["iterations"], $password["salt"], $password["hash"]); + $stmt->execute(); + $stmt->close(); + } else { + addError("Mysql", $mysqli->error); + } + } else { + addError("wrongInput", "eMail-Adresse ordentlich? Passwort lang genug? 12 Zeichen mindestens. Besser länger als kompliziert."); + } + } else if (isset($input["chgAccount"]) && $input["secToken"]==$_SESSION["secTokenVerify"]) { + $res = $mysqli->query("SELECT groupID FROM users WHERE ID=".$userID); + $groupID = $res->fetch_assoc()["groupID"]; + foreach ($input["admin_acc"] as $ID => $val) { + $mysqli->query("UPDATE users SET notifications=".$val["notifications"]." WHERE ID=".$ID." AND groupID=".$groupID); + } + } + + $tpl["pwd"] = tplExtrSection($tpl["main"], "###ADMIN.PASSWORD###"); + $tpl["pwd"] = tplReplSection($tpl["pwd"], "###ADMIN.PASSWORD.SUCCESS###", $password_changed_success ? tplExtrSection($tpl["pwd"], "###ADMIN.PASSWORD.SUCCESS###") : ""); + + // Autorenschaft speichern + /*if (isset($input["chgAuthor"]) && $input["secToken"]==$_SESSION["secTokenVerify"] && $stmt = $mysqli->prepare("SELECT u.ID FROM users u WHERE u.groupID=(SELECT groupID FROM users WHERE ID = ?)")) { + $stmt->bind_param("i", $userID); + $stmt->execute(); + $stmt->bind_result($ID); + $array = array(); + while ($stmt->fetch()) { + $array[] = "UPDATE users SET guestID = ".($input["admin_gsp_authors"][$ID] ?? "NULL")." WHERE ID = ".$ID; + } + $stmt->close(); + foreach ($array as $update) { + $mysqli->query($update); + } + }*/ + + // Veraltet + $tpl["gsp"] = ""; + + // Accounts + $tpl["acc"] = tplExtrSection($tpl["main"], "###ADMIN.ACC###"); + $line_tpl = tplExtrSection($tpl["acc"], "###ADMIN.ACC.LINE###"); + $lines = ""; + + $qry = "SELECT u.ID, u.email, u.login, u.notifications FROM users u WHERE u.groupID=(SELECT groupID FROM users WHERE ID = ?)"; + if ($stmt = $mysqli->prepare($qry)) { + $stmt->bind_param("i", $userID); + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $replace = array( + "###ADMIN.ACC.LINE.ID###" => $row["ID"], + "###ADMIN.ACC.LINE.MAIL###" => $row["email"], + "###ADMIN.ACC.LINE.LOGIN###" => $row["login"], + "###ADMIN.ACC.NOTIFICATIONS.SELECTED.0###" => $row["notifications"]==0 ? "selected" : "", + "###ADMIN.ACC.NOTIFICATIONS.SELECTED.1###" => $row["notifications"]==1 ? "selected" : "", + "###ADMIN.ACC.NOTIFICATIONS.SELECTED.2###" => $row["notifications"]==2 ? "selected" : "" + ); + $lines.= tplReplMarkerArray($line_tpl, $replace); + } + $stmt->close(); + } + $tpl["acc"] = tplReplSection($tpl["acc"], "###ADMIN.ACC.LINE###", $lines); + + // Account hinzufügen + $tpl["add"] = tplExtrSection($tpl["main"], "###ADMIN.ADD###"); + + // Account-Liste + if (lgnCheckRight($mysqli, "INVITE_ADD")) { + $tpl["list"] = tplExtrSection($tpl["main"], "###ADMIN.LIST###"); + if (isset($input["admin_list_resendpw"], $input["admin_list_resendcheck"]) && $input["secToken"]==$_SESSION["secTokenVerify"] && $input["admin_list_resendpw"]==$input["admin_list_resendcheck"]) { + $pass = lgnTransformPassword(); + $pass_tpl = tplExtrSection(tplLoadFile("pgs/admin/mail.html"), "###SENDPASSWORD###"); + $text = tplReplMarker($pass_tpl, "###PASSWORD###", $pass["password"]); + $success = emlSendSingleMail($mysqli, $input["admin_list_resendpw"], "Neues Passwort", $text); + if ($success) { + if ($stmt = $mysqli->prepare("UPDATE users SET iterations = ?, salt = ?, hash = ?, lastaction = null WHERE ID = ?")) { + $stmt->bind_param("issi", $pass["iterations"], $pass["salt"], $pass["hash"], $input["admin_list_resendpw"]); + $stmt->execute(); + $stmt->close(); + } + addError("", "Passwort senden erfolgreich!"); + } else { + addError("", "Passwort senden fehlgeschlagen!"); + } + } + + $qry = "SELECT gr.name, u.groupID, u.ID, u.login, u.email, u.eMailFrom, u.notifications, u.lastaction FROM groups gr RIGHT JOIN users u ON u.groupID=gr.ID ORDER BY gr.name ASC, u.email ASC"; + $res = $mysqli->query($qry); + $line_tpl = tplExtrSection($tpl["list"], "###ADMIN.LIST.ACCOUNT###"); + $lines = ""; + while ($row = $res->fetch_assoc()) { + $replace = array( + "###ADMIN.LIST.GROUP.ID###" => $row["groupID"], + "###ADMIN.LIST.GROUP.NAME###" => $row["name"], + "###ADMIN.LIST.USER.ID###" => $row["ID"], + "###ADMIN.LIST.USER.EMAIL###" => $row["email"], + "###ADMIN.LIST.USER.LOGIN###" => $row["login"], + "###ADMIN.LIST.USER.NOTIFICATIONS###" => $row["notifications"] + ); + $line = tplReplMarkerArray($line_tpl, $replace); + $line = tplReplSection($line, "###ADMIN.LIST.USER.LOGGEDIN###", null!=$row["lastaction"] ? tplExtrSection($line_tpl, "###ADMIN.LIST.USER.LOGGEDIN###") : ""); + $lines.= $line; + } + $tpl["list"] = tplReplSection($tpl["list"], "###ADMIN.LIST.ACCOUNT###", $lines); + } +} + +$tpl["main"] = tplReplSection($tpl["main"], "###ADMIN.PASSWORD###", $tpl["pwd"]); +$tpl["main"] = tplReplSection($tpl["main"], "###ADMIN.GSP###", $tpl["gsp"]); +$tpl["main"] = tplReplSection($tpl["main"], "###ADMIN.ACC###", $tpl["acc"]); +$tpl["main"] = tplReplSection($tpl["main"], "###ADMIN.ADD###", $tpl["add"]); +$tpl["main"] = tplReplSection($tpl["main"], "###ADMIN.LIST###", $tpl["list"]); +$output["main"] = $tpl["main"]; + +?> \ No newline at end of file diff --git a/pgs/admin/mail.html b/pgs/admin/mail.html new file mode 100755 index 0000000..b8c93c6 --- /dev/null +++ b/pgs/admin/mail.html @@ -0,0 +1,13 @@ + + + +Account-Liste
+ ++ ###ADDRESSING### ###NICKNAME###,
+ diff --git a/pgs/blog/blog.css b/pgs/blog/blog.css new file mode 100755 index 0000000..d2234cf --- /dev/null +++ b/pgs/blog/blog.css @@ -0,0 +1,38 @@ +/* pgs/blog/blog.css */ + +#blog_new td { + padding: 0.5em; +} +#blog_new textarea { + width: calc(100% - 1em); + height: 20em; + font-size: inherit; + font-family: inherit; + padding: 0.5em; +} +#blog_new label { + width: 8em; + display: inline-block; +} +#blog_new input { + width: 100%; +} +#blog_new input[type="submit"] { + width: 49em; +} +#blog_new input[type="checkbox"] { + width: auto; +} + +.blog_entry { + padding: 0; + margin-bottom: 0.2em; + border: 1px solid #000000; +} +.blog_entry_header { + background-color: #fbc8b1; + padding: 0.2em; +} +.blog_entry_main { + padding: 0.4em; +} diff --git a/pgs/blog/blog.html b/pgs/blog/blog.html new file mode 100755 index 0000000..4b1c5e8 --- /dev/null +++ b/pgs/blog/blog.html @@ -0,0 +1,42 @@ + + + +
+ für diesen Account wurde ein neues Passwort für die Hochzeitswebsite von Sophia und Nils generiert:
+ ###PASSWORD###
+ Der Benutzername ist weiterhin die eMail-Adresse, an die diese Nachricht geht.
+ Das Passwort kann (und sollte) nach dem Einloggen unter "Einstellungen" geändert werden.
+ Viele Grüße,
+ Sophia & Nils ++ + + +Bisherige Rundmails
+Dies ist ein Archiv aller bisher versendeter Rundmails.
+ +++ +###BLOG.ENTRY.AUTHOR###, ###BLOG.ENTRY.DATETIME###: ###BLOG.ENTRY.TITLE###+Vorsicht: Das Brautpaar selbst darf von dieser Nachricht nichts wissen!+
###BLOG.ENTRY.TEXT###+ + + + \ No newline at end of file diff --git a/pgs/blog/index.php b/pgs/blog/index.php new file mode 100755 index 0000000..b086437 --- /dev/null +++ b/pgs/blog/index.php @@ -0,0 +1,105 @@ + tplExtrSection(tplLoadFile("pgs/blog/blog.html"), "BLOG"), + "entry" => "", + "new" => "" +); +addStyle("pgs/blog/blog.css"); + +if ($userID = lgnCheckLogin($mysqli)) { + // Eingabemaske für Blogeintrag + if (lgnCheckRight($mysqli, "BLOG_ADD", $userID)) { + // Neue Nachricht wurde verfasst? + if (isset($input["blog_new_submit"]) && ""!=$input["blog_new_text"] && ""!=$input["blog_new_title"] && $input["secToken"]==$_SESSION["secTokenVerify"]) { + // Autor überprüfen und gegebenenfalls überschreiben + $guestID = null; + $guestName = ""; + $qry = "SELECT g.ID,g.prenames,g.surnames FROM guests g LEFT JOIN users u ON u.groupID=g.groupID WHERE g.companion=0 AND u.ID = ? ORDER BY g.ID = ? DESC LIMIT 1"; + if ($stmt = $mysqli->prepare($qry)) { + $stmt->bind_param("ii", $userID, $input["boardnew_author"]); + $stmt->execute(); + $res = $stmt->get_result(); + if ($row = $res->fetch_assoc()) { + $guestID = $row["ID"]; + $guestName = $row["prenames"]." ".$row["surnames"]; + } + } + + // Datensatz einfügen + $insert = "INSERT INTO blog (guestID, rightID, dttm, title, text) VALUES (?, (SELECT r.ID FROM rights r WHERE name = ?), NOW(), ?, ?)"; + if ($stmt->prepare($insert)) { + $encTitle = cntCipherTextSym($input["blog_new_title"], SYM_CIPHER_KEY); + $encText = cntCipherTextSym($input["blog_new_text"], SYM_CIPHER_KEY); + $right = isset($input["blog_new_brautpaar"]) && $input["blog_new_brautpaar"]=="1" ? null : "HIDDEN_FROM_BRAUTPAAR"; + $stmt->bind_param("isss", $guestID, $right, $encTitle, $encText); + if ($stmt->execute()) { + $text = tplExtrSection(tplLoadFile("pgs/blog/mail.html"), "###MASSMAIL###"); + if ($right==null) { + $text = tplReplSection($text, "###NO_BRAUTPAAR_WARNING###", ""); + } else { + $text = tplReplSection($text, "###NO_BRAUTPAAR_WARNING###", tplExtrSection($text, "###NO_BRAUTPAAR_WARNING###")); + } + $text = tplReplMarker($text, "###TITLE###", $input["blog_new_title"]); + $text = tplReplMarker($text, "###MESSAGE###", str_replace("\\r\\n", "Neue Rundmail verfassen
+ +
", $input["blog_new_text"])); + $text = tplReplMarker($text, "###FROM###", $guestName); + emlSendMassmail($mysqli, $userID, $guestName, "Rundmail zur Hochzeit von Sophia und Nils", $text, $right); + } + } + } + + $tpl["new"] = tplExtrSection($tpl["main"], "###BLOG.NEW###"); + $tpl["author"] = tplExtrSection($tpl["new"], "###BLOG.NEW.AUTHOR###"); + $authors = ""; + $qry = "SELECT g.ID, g.prenames, g.surnames FROM guests g WHERE g.companion=0 AND g.groupID = (SELECT groupID FROM users WHERE ID = ".$userID.")"; + if ($res = $mysqli->query($qry)) { + while ($row = $res->fetch_assoc()) { + $author = tplReplMarker($tpl["author"], "###BLOG.NEW.AUTHOR.ID", $row["ID"]); + $author = tplReplMarker($author, "###BLOG.NEW.AUTHOR.NAME", $row["prenames"]." ".$row["surnames"]); + $authors.= $author; + } + } + $tpl["new"] = tplReplSection($tpl["new"], "###BLOG.NEW.AUTHOR###", $authors); + if (lgnCheckRight($mysqli, "HIDDEN_FROM_BRAUTPAAR", $userID)) { + $tpl["new"] = tplReplMarker($tpl["new"], "###BLOG.NEW.BRAUTPAAR.OPTIONS###", ""); + } else { + $tpl["new"] = tplReplMarker($tpl["new"], "###BLOG.NEW.BRAUTPAAR.OPTIONS###", "checked style='visibility: hidden;' "); + } + } + + // Blogeinträge auflisten + $tpl["entry"] = tplExtrSection($tpl["main"], "###BLOG.ENTRY###"); + $entries = ""; + $qry = "SELECT b.*, g.prenames, g.surnames FROM blog b LEFT JOIN guests g ON g.ID=b.guestID " + ."LEFT JOIN rolerights r ON r.rightID=b.rightID LEFT JOIN users u ON u.roleID=r.roleID " + ."WHERE b.rightID IS NULL OR u.ID=".$userID; + if ($res = $mysqli->query($qry)) { + while ($row = $res->fetch_assoc()) { + $replace = array( + "###BLOG.ENTRY.ID###" => $row["ID"], + "###BLOG.ENTRY.DATETIME###" => $row["dttm"], + "###BLOG.ENTRY.AUTHOR###" => $row["prenames"]." ".$row["surnames"], + "###BLOG.ENTRY.TITLE###" => cntCipherTextSym($row["title"], SYM_CIPHER_KEY), + "###BLOG.ENTRY.TEXT###" => str_replace("\\r\\n", "
", cntCipherTextSym($row["text"], SYM_CIPHER_KEY)) + ); + $entry = tplReplMarkerArray($tpl["entry"], $replace); + if ($row["rightID"]==null) { + $entry = tplReplSection($entry, "###NO_BRAUTPAAR_WARNING###", ""); + } else { + $entry = tplReplSection($entry, "###NO_BRAUTPAAR_WARNING###", tplExtrSection($entry, "###NO_BRAUTPAAR_WARNING###")); + } + + $entries.= $entry; + } + } else { + addError("Mysql", $mysqli->error." // Query: ".$qry); + } + $tpl["entry"] = $entries; +} + +$tpl["main"] = tplReplSection($tpl["main"], "###BLOG.ENTRY###", $tpl["entry"]); +$tpl["main"] = tplReplSection($tpl["main"], "###BLOG.NEW###", $tpl["new"]); +$output["main"] = $tpl["main"]; + +?> \ No newline at end of file diff --git a/pgs/blog/mail.html b/pgs/blog/mail.html new file mode 100755 index 0000000..4705fdb --- /dev/null +++ b/pgs/blog/mail.html @@ -0,0 +1,13 @@ + ++ ###ADDRESSING### ###NICKNAME###,
+
+ folgende Rundmail wurde von ###FROM### über die Hochzeitswebsite von Sophia und Nils geschrieben: +
Vorsicht: Das Brautpaar selbst darf von dieser Nachricht nichts wissen! +
+###TITLE###
++ ###MESSAGE### +
+
+ diff --git a/pgs/board/board.css b/pgs/board/board.css new file mode 100755 index 0000000..6cfef2e --- /dev/null +++ b/pgs/board/board.css @@ -0,0 +1,71 @@ +/* pgs/board/board.css */ + +article { + border-top: 3px solid #000000; +} +article:first-of-type { + border-top: none; +} + +#boardnew td { + padding: 0.5em; +} +#boardnew textarea { + width: calc(100% - 1em); + height: 20em; + font-size: inherit; + font-family: inherit; + padding: 0.5em; +} +#boardnew label { + width: 8em; + display: inline-block; +} +#boardnew input { + width: 100%; +} +#boardnew input[type="submit"] { + width: 49em; +} + +.boardmsg { + padding: 0; + /*margin-bottom: 0.2em;*/ + border: 1px solid #000000; +} +.boardmsg_header { + background-color: #fbc8b1; + padding: 0.2em; +} +.boardmsg_main { + padding: 0.4em; +} +.boardmsg_footer { + padding: 0.2em; + /*text-align: right;*/ +} + +a.blocklink { + text-decoration: none; +} + +.boardmsg_indent { + border-left: 1px solid #fbc8b1; + margin-left: 1em; + padding-left: 1em; +} +.boardmsg_indent_first { + margin-left: 1em; +} +.boardmsg_indent_last { + padding-top: 0.2em; +} +.boardmsg_indent_helper { + height: 0.7em; + width: 1em; + float: left; + margin-left: -1em; +} +.boardmsg_indent_last .boardmsg_indent_helper { + border-bottom: 1px solid #fbc8b1; +} \ No newline at end of file diff --git a/pgs/board/categories.html b/pgs/board/categories.html new file mode 100755 index 0000000..41bbe59 --- /dev/null +++ b/pgs/board/categories.html @@ -0,0 +1,19 @@ + + + + + ++ + + + diff --git a/pgs/board/index.php b/pgs/board/index.php new file mode 100755 index 0000000..004a817 --- /dev/null +++ b/pgs/board/index.php @@ -0,0 +1,169 @@ +query($qry)) { + while ($row = $res->fetch_assoc()) { + $replace = array( + "###BOARDMSG.LEVEL###" => ($lvl < 10 ? $lvl : 9), + "###BOARDMSG.ID###" => $row["ID"], + "###BOARDMSG.DATETIME###" => $row["dttm"], + "###BOARDMSG.TITLE###" => $row["deleted"]==0 ? cntCipherTextSym($row["title"], SYM_CIPHER_KEY) : "", + "###BOARDMSG.TEXT###" => $row["deleted"]==0 ? str_replace("\\r\\n", "###BOARDCATS.CAT.NAME###
+###BOARDCATS.CAT.DESC###
+ + ###BOARDCATS.CAT.THREAD.MSG### + + +Bisher noch keine Diskussionen vorhanden!
+ + +
", cntCipherTextSym($row["text"], SYM_CIPHER_KEY)) : "Nachricht wurde gelöscht" + ); + preg_match_all("/([\s-]?)([A-Z])/", $row["surnames"], $matches); + $replace["###BOARDMSG.AUTHOR###"] = $row["prenames"]." ".implode(".", $matches[0])."."; + $msg = tplReplMarkerArray($tpl, $replace); + + $indent_in_tpl = tplExtrSection($tpl, "###BOARDMSG.INDENT.IN###"); + $indent_out_tpl = tplExtrSection($tpl, "###BOARDMSG.INDENT.OUT###"); + $indent_in = ""; + $indent_out = ""; + for ($i = 0; $i<$lvl; $i++) { + $class = ""; + if ($i==0) { + $class.= " boardmsg_indent_first"; + } + if ($i==$lvl-1) { + $class.= " boardmsg_indent_last"; + } + $indent_in.= tplReplMarker($indent_in_tpl, "###BOARDMSG.INDENT.CLASS###", $class); + $indent_out.= $indent_out_tpl; + } + $msg = tplReplSection($msg, "###BOARDMSG.INDENT.IN###", $indent_in); + $msg = tplReplSection($msg, "###BOARDMSG.INDENT.OUT###", $indent_out); + + if ($row["deleted"]==1) { + $msg = tplReplSection($msg, "###BOARDMSG.FOOTER###", ""); + } elseif ($row["own"]=="0" && !lgnCheckRight($mysqli, "BOARD_DEL", $userID)) { + $msg = tplReplSection($msg, "###BOARDMSG.DEL###", ""); + } + $ret.= $msg.($recursive ? boardLoadThread($mysqli, $userID, $row["ID"], $tpl, $lvl+1) : ""); + } + } else { + addError("Mysql", $mysqli->error." // Query: ".$qry); + } + return $ret; +} +function boardCleanDeletedParents($mysqli, $ID) { + $res = $mysqli->query("SELECT parentID FROM board_messages m WHERE m.ID=".$ID." AND m.deleted=1 " + ." AND (SELECT COUNT(*) FROM board_messages ms WHERE ms.parentID=".$ID.")=0"); + if ($res->num_rows==1) { + $parentID = $res->fetch_assoc()["parentID"]; + $mysqli->query("DELETE FROM board_messages WHERE ID = ".$ID); + if ($parentID!=null) { + boardCleanDeletedParents($mysqli, $parentID); + } + } +} + +if (isset($input["boardnew_submit"], $input["secToken"]) && $input["secToken"]==$_SESSION["secTokenVerify"] && ($userID = lgnCheckLogin($mysqli))) { + // Einfügen neuer Nachricht + + // Kategorie herausfinden und testen ob erlaubt + $categoryID = null; + $parentID = null; + $rootID = null; + $replyto = isset($input["boardnew_replyto"]); + $qry = "SELECT c.ID " + .($replyto ? ", m.rootID FROM board_messages m LEFT JOIN board_categories c ON c.ID=m.categoryID " : "FROM board_categories c ") + ."LEFT JOIN rolerights r ON r.rightID=c.rightID " + ."LEFT JOIN users u ON u.roleID=r.roleID " + ."WHERE (c.rightID IS NULL OR u.ID = ?) AND ".($replyto ? "m.ID = ? " : "c.ID = ? "); + if ($stmt = $mysqli->prepare($qry)) { + $whereID = $replyto ? $input["boardnew_replyto"] : $input["boardnew_cat"]; + $stmt->bind_param("ii", $userID, $whereID); + $stmt->execute(); + $res = $stmt->get_result(); + if ($res->num_rows==0) { + addError("Access", "Kategorie nicht vorhanden oder Zugriff nicht erlaubt1"); + } else { + $cat = $res->fetch_assoc(); + $categoryID = $cat["ID"]; + if ($replyto) { + $parentID = $input["boardnew_replyto"]; + $rootID = $cat["rootID"] ?? $parentID; + } + } + } + + // Autor überprüfen und gegebenenfalls überschreiben + $guestID = null; + $qry = "SELECT g.ID FROM guests g LEFT JOIN users u ON u.groupID=g.groupID WHERE u.ID = ? AND g.companion=0 ORDER BY g.ID = ? DESC LIMIT 1"; + if ($stmt = $mysqli->prepare($qry)) { + $stmt->bind_param("ii", $userID, $input["boardnew_author"]); + $stmt->execute(); + $res = $stmt->get_result(); + if ($res->num_rows==0) { + addError("Access", "Kategorie nicht vorhanden oder Zugriff nicht erlaubt"); + } else { + $guestID = $res->fetch_assoc()["ID"]; + } + } + + // Datensatz einfügen. Wenn Kategorie verboten war, steht sie auf NULL und wird von der Datenbank abgelehnt + $insert = "INSERT INTO board_messages (guestID, categoryID, rootID, parentID, dttm, title, text) VALUES (?, ?, ?, ?, NOW(), ?, ?)"; + if ($stmt->prepare($insert)) { + $encTitle = cntCipherTextSym($input["boardnew_title"], SYM_CIPHER_KEY); + $encText = cntCipherTextSym($input["boardnew_text"], SYM_CIPHER_KEY); + $stmt->bind_param("iiiiss", $guestID, $categoryID, $rootID, $parentID, $encTitle, $encText); + if ($stmt->execute()) { + if (!$replyto) + $input["thread"] = $mysqli->insert_id; + + $neededRight = null; + $res = $mysqli->query("SELECT r.name FROM board_categories c LEFT JOIN rights r ON r.ID=c.rightID WHERE c.ID=".$categoryID); + if ($res->num_rows>0) + $neededRight = $res->fetch_assoc()["name"]; + + $text = tplExtrSection(tplLoadFile("pgs/board/notify.txt"), "###NOTIFICATION###"); + $text = tplReplMarker($text, "###PAGEMAIN###", $page["main"]); + $url = parse_url("https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); + $text = tplReplMarker($text, "###PAGEURL###", "https://".$url["host"].$url["path"]); + $text = tplReplMarker($text, "###THREADID###", $input["thread"]); + emlSendNotification($mysqli, $userID, "Neuer Beitrag unter Diskussion", $text, $replyto ? 2 : 1, $neededRight); + } + } +} elseif (isset($input["del"]) && ($userID = lgnCheckLogin($mysqli))) { + $qry = "SELECT m.ID, m.rootID, m.parentID, g.groupID=u.groupID own, (SELECT COUNT(*) FROM board_messages ms WHERE ms.parentID=m.ID) children " + ."FROM board_messages m LEFT JOIN guests g ON g.ID=m.guestID LEFT JOIN users u ON u.ID = ? WHERE m.ID = ?"; + if ($stmt = $mysqli->prepare($qry)) { + $stmt->bind_param("ii", $userID, $input["del"]); + $stmt->execute(); + $res = $stmt->get_result(); + if ($row = $res->fetch_assoc()) { + $input["thread"] = $row["rootID"] ?? $row["ID"]; + + // Darf löschen? + if ($row["own"]==1 || lgnCheckRight($mysqli, "BOARD_DEL", $userID)) { + // Wenn Antworten auf diese Nachricht existieren, nur als gelöscht markieren + if ($row["children"]>0) { + $mysqli->query("UPDATE board_messages SET deleted=1 WHERE ID = ".$row["ID"]); + } else { + $mysqli->query("DELETE FROM board_messages WHERE ID = ".$row["ID"]); + if ($row["parentID"]!=null) { + boardCleanDeletedParents($mysqli, $row["parentID"]); + } + } + if ($row["rootID"]==null) { + $page["sub"] = "main"; + } + } + } + } +} + +addStyle("pgs/board/board.css"); +$tpl = array( + "main" => "" +); +if ($sub = pgsInclSub($page["sub"])) + include $sub; + +$output["main"] = $tpl["main"]; + +?> \ No newline at end of file diff --git a/pgs/board/main.php b/pgs/board/main.php new file mode 100755 index 0000000..8b7396c --- /dev/null +++ b/pgs/board/main.php @@ -0,0 +1,74 @@ +prepare($qry)) { + $stmt->bind_param("i", $userID); + $stmt->execute(); + $catres = $stmt->get_result(); + while ($catrow = $catres->fetch_assoc()) { + $replace = array( + "###BOARDCATS.CAT.ID###" => $catrow["ID"], + "###BOARDCATS.CAT.NAME###" => $catrow["name"], + "###BOARDCATS.CAT.DESC###" => $catrow["description"] + ); + $cat = tplReplMarkerArray($tpl["cat"], $replace); + $threads = ""; + + $qry = "SELECT m.ID, m.dttm, m.title, m.text, g.prenames, g.surnames, " + ."(SELECT COUNT(*) FROM board_messages ms WHERE ms.rootID=m.ID) replies " + ."FROM board_messages m LEFT JOIN guests g ON g.ID = m.guestID " + ."WHERE m.parentID IS NULL AND m.categoryID = ".$catrow["ID"]." " + ."ORDER BY m.dttm DESC"; + if ($thrres = $mysqli->query($qry)) { + if ($thrres->num_rows==0) { + $threads = $tpl["nothreads"]; + } else { + while ($thrrow = $thrres->fetch_assoc()) { + $thread = tplReplMarker($tpl["thread"], "###BOARDCATS.CAT.THREAD.MSG###", tplExtrSection(tplLoadFile("pgs/board/msg.html"), "###BOARDMSG###")); + $thread = tplReplSection($thread, "###BOARDMSG.FOOTER###", ""); + $thread = tplReplSection($thread, "###BOARDMSG.INDENT.IN###", ""); + $thread = tplReplSection($thread, "###BOARDMSG.INDENT.OUT###", ""); + preg_match_all("/([\s-]?)([A-Z])/", $thrrow["surnames"], $matches); + $replace = array( + "###BOARDMSG.ID###" => $thrrow["ID"], + "###BOARDMSG.TITLE###" => cntCipherTextSym($thrrow["title"], SYM_CIPHER_KEY), + "###BOARDMSG.AUTHOR###" => $thrrow["prenames"]." ".implode(".", $matches[0]).".", + "###BOARDMSG.DATETIME###" => $thrrow["dttm"], + "###BOARDMSG.LEVEL###" => 0 + ); + $decText = cntCipherTextSym($thrrow["text"], SYM_CIPHER_KEY); + if (strlen($decText)>33) { + $replace["###BOARDMSG.TEXT###"] = substr($decText, 0, 30)."...
".$thrrow["replies"]." Antwort".($thrrow["replies"]!="1" ? "en" : "").""; + } else { + $replace["###BOARDMSG.TEXT###"] = $decText."
".$thrrow["replies"]." Antwort".($thrrow["replies"]!="1" ? "en" : "").""; + } + $threads.= tplReplMarkerArray($thread, $replace); + } + } + } else { + addError("Mysql", $mysqli->error); + } + $cat = tplReplSection($cat, "###BOARDCATS.CAT.NOTHREADS###", ""); + $cats.= tplReplSection($cat, "###BOARDCATS.CAT.THREAD###", $threads); + } + $stmt->close(); + $tpl["cat"] = $cats; + } +} +$tpl["main"] = tplReplSection($tpl["main"], "###BOARDCATS.CAT###", $tpl["cat"]); + +?> diff --git a/pgs/board/msg.html b/pgs/board/msg.html new file mode 100755 index 0000000..5b25549 --- /dev/null +++ b/pgs/board/msg.html @@ -0,0 +1,22 @@ + + + + ++ ++ diff --git a/pgs/board/new.html b/pgs/board/new.html new file mode 100755 index 0000000..52a991e --- /dev/null +++ b/pgs/board/new.html @@ -0,0 +1,43 @@ + + + ++ ++###BOARDMSG.AUTHOR###, ###BOARDMSG.DATETIME###: ###BOARDMSG.TITLE###+ + +###BOARDMSG.TEXT###+ + + + ++ + diff --git a/pgs/board/new.php b/pgs/board/new.php new file mode 100755 index 0000000..2f8b1a3 --- /dev/null +++ b/pgs/board/new.php @@ -0,0 +1,77 @@ +prepare($qry)) { + $whereID = $replyto ? $input["replyto"] : $input["cat"]; + $stmt->bind_param("ii", $userID, $whereID); + $stmt->execute(); + $catres = $stmt->get_result(); + if ($catres->num_rows==0) { + addError("Access", "Kategorie nicht vorhanden oder Zugriff nicht erlaubt"); + } else { + $cat = $catres->fetch_assoc(); + $replace = array( + "###BOARDNEW.CAT.ID###" => $cat["ID"], + "###BOARDNEW.CAT.NAME###" => $cat["name"], + "###BOARDNEW.CAT.DESC###" => $cat["description"], + "###BOARDNEW.REPLYTO.ID###" => $cat["mID"] ?? "created", + "###BOARDNEW.REPLYTO.ROOTID###" => $cat["rootID"] ?? ($cat["mID"] ?? "error"), + "###BOARDMSG.DATETIME###" => $cat["dttm"] ?? "", + "###BOARDMSG.TITLE###" => cntCipherTextSym($cat["title"] ?? "", SYM_CIPHER_KEY), + "###BOARDMSG.TEXT###" => str_replace("\\r\\n", "###BOARDNEW.CAT.NAME###
+###BOARDNEW.CAT.DESC###
+ + + + + +
", cntCipherTextSym($cat["text"] ?? "", SYM_CIPHER_KEY)) + ); + if ($replyto) { + $tpl["new"] = tplReplSection($tpl["new"], "###BOARDNEW.START###", ""); + preg_match_all("/([\s-]?)([A-Z])/", $cat["surnames"], $matches); + $replace["###BOARDMSG.AUTHOR###"] = $cat["prenames"]." ".implode(".", $matches[0])."."; + $tpl["msg"] = tplExtrSection(tplLoadFile("pgs/board/msg.html"), "###BOARDMSG###"); + $tpl["msg"] = tplReplSection($tpl["msg"], "###BOARDMSG.FOOTER###", ""); + $tpl["msg"] = tplReplSection($tpl["msg"], "###BOARDMSG.INDENT.IN###", ""); + $tpl["msg"] = tplReplSection($tpl["msg"], "###BOARDMSG.INDENT.OUT###", ""); + $tpl["new"] = tplReplMarker($tpl["new"], "###BOARDNEW.REPLYTO.PRINT###", $tpl["msg"]); + } else { + $tpl["new"] = tplReplSection($tpl["new"], "###BOARDNEW.REPLYTO###", ""); + $tpl["new"] = tplReplSection($tpl["new"], "###BOARDNEW.BACKTOTHREAD###", ""); + } + $tpl["new"] = tplReplMarkerArray($tpl["new"], $replace); + + $tpl["author"] = tplExtrSection($tpl["new"], "###BOARDNEW.AUTHOR###"); + $authors = ""; + $qry = "SELECT g.ID, g.prenames, g.surnames FROM guests g WHERE g.companion=0 AND g.groupID = (SELECT groupID FROM users WHERE ID = ?)"; + if ($stmt = $mysqli->prepare($qry)) { + $stmt->bind_param("i", $userID); + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + preg_match_all("/([\s-]?)([A-Z])/", $row["surnames"], $matches); + $author = tplReplMarker($tpl["author"], "###BOARDNEW.AUTHOR.ID", $row["ID"]); + $author = tplReplMarker($author, "###BOARDNEW.AUTHOR.NAME", $row["prenames"]." ".implode(".", $matches[0])."."); + $authors.= $author; + } + } + $stmt->close(); + $tpl["new"] = tplReplSection($tpl["new"], "###BOARDNEW.AUTHOR###", $authors); + } + } else { + addError("Mysql", $mysqli->error); + } +} +$tpl["main"] = $tpl["new"]; + +?> diff --git a/pgs/board/notify.txt b/pgs/board/notify.txt new file mode 100755 index 0000000..92e61ec --- /dev/null +++ b/pgs/board/notify.txt @@ -0,0 +1,12 @@ + +###ADDRESSING### ###NICKNAME###, + +auf der Hochzeitswebsite von Sophia und Nils wurde unter Diskussionen ein neuer Beitrag geschrieben. +Diesen findest du hier: +###PAGEURL###?main=###PAGEMAIN###&sub=thread&thread=###THREADID### + +Benachrichtigungen dieser Art können unter "Einstellungen" eingeschränkt oder deaktiviert werden. +Bitte nicht auf diese Nachricht antworten, dieses Postfach wird nicht gelesen. + +Viele Grüße wünscht der Mailroboter von Sophia und Nils! + diff --git a/pgs/board/thread.html b/pgs/board/thread.html new file mode 100755 index 0000000..a9052d9 --- /dev/null +++ b/pgs/board/thread.html @@ -0,0 +1,10 @@ + + + ++ + diff --git a/pgs/board/thread.php b/pgs/board/thread.php new file mode 100755 index 0000000..31b4cb2 --- /dev/null +++ b/pgs/board/thread.php @@ -0,0 +1,41 @@ +prepare($qry)) { + $stmt->bind_param("ii", $userID, $input["thread"]); + $stmt->execute(); + $catres = $stmt->get_result(); + if ($catres->num_rows==0) { + addError("Access", "Kategorie nicht vorhanden oder Zugriff nicht erlaubt3"); + } else { + $cat = $catres->fetch_assoc(); + $replace = array( + "###BOARDTHR.CAT.ID###" => $cat["ID"], + "###BOARDTHR.CAT.NAME###" => $cat["name"], + "###BOARDTHR.CAT.DESC###" => $cat["description"] + ); + $tpl["thr"] = tplReplMarkerArray($tpl["thr"], $replace); + $tpl["msg"] = tplExtrSection(tplLoadFile("pgs/board/msg.html"), "###BOARDMSG###"); + + $rootID = $cat["rootID"] ?? $cat["mID"]; + $tpl["thr"] = tplReplMarker($tpl["thr"], "###BOARDTHR.MSGS###", boardLoadThread($mysqli, $userID, $rootID, $tpl["msg"])); + } + } else { + addError("Mysql", $mysqli->error); + } +} +$tpl["main"] = $tpl["thr"]; + +?> diff --git a/pgs/dienste/index.php b/pgs/dienste/index.php deleted file mode 100644 index dcb6fd5..0000000 --- a/pgs/dienste/index.php +++ /dev/null @@ -1,362 +0,0 @@ - tplExtrSection(tplLoadFile("pgs/dienste/main.html"), "DIENSTE"), - "show" => "", - "list" => "" -); - -//die("Wartung"); - -addStyle("pgs/dienste/main.css"); - -// Anleitung zeigen -$tpl["show"] = tplExtrSection($tpl["main"], "###DIENSTE.SHOW###"); - -if ($userID = lgnCheckLogin($mysqli)) { - // Abteilungen und Gruppen abrufen - $res = $mysqli->query("SELECT l.Abteilungen, a.Name FROM link_Abteilungen_Personal l LEFT JOIN Abteilungen a ON a.ID=l.Abteilungen WHERE l.Personal='".$userID."'"); - $abteilungen = array(); - $abteilungenIDs = array(); - while ($row = $res->fetch_assoc()) { - $abteilungen[] = $row["Name"]; - $abteilungenIDs[] = $row["Abteilungen"]; - } - $res = $mysqli->query("SELECT l.Personalgruppen, p.Name FROM link_Personal_Personalgruppen l LEFT JOIN Personalgruppen p ON p.ID=l.Personalgruppen WHERE l.Personal='".$userID."'"); - $gruppenIDs = array(null); - while ($row = $res->fetch_assoc()) { - $gruppen[] = $row["Name"]; - $gruppenIDs[] = $row["Personalgruppen"]; - } - - // Terminaktion abarbeiten - $confirm = ""; - if (isset($input["termin"], $input["action"], $input["secToken"]) && $input["secToken"]==$_SESSION["secTokenVerify"]) { - $qry = "SELECT t.ID tID, t.Beginn, t.Ausgelost, IF(t.Beginn###BOARDTHR.CAT.NAME###
+###BOARDTHR.CAT.DESC###
+ ###BOARDTHR.MSGS### + +=1) num, " - ."(SELECT Status FROM Terminteilnahmen tln WHERE Termin=t.ID AND Personal = ?) Status " - .", (SELECT COUNT(*) FROM Terminteilnahmen t4 LEFT JOIN Termine t5 ON t5.ID=t4.Termin " - ." WHERE t4.Personal=? AND t5.ID!=t.ID AND t5.Terminart=t.Terminart " - ." AND t5.Beginn BETWEEN t.Beginn - INTERVAL 7 DAY AND t.Beginn + INTERVAL 7 DAY) naheDienste " - ."FROM Termine t " - ."LEFT JOIN Dienstpläne d ON d.ID=t.Dienstplan " - ."LEFT JOIN Terminplatzvergabe tpv ON tpv.ID=t.Platzvergabe " - ."LEFT JOIN Terminarten ta ON ta.ID = t.Terminart " - ."LEFT JOIN Berechtigungen b ON ta.Verwaltungsrecht = b.ID " - ."WHERE t.ID = ?"; - if ($stmt = $mysqli->prepare($qry)) { - $stmt->bind_param("iii", $userID, $userID, $input["termin"]); - $stmt->execute(); - $row = $stmt->get_result()->fetch_assoc(); - $stmt->close(); - - // Mögliche Rechte zum Bearbeiten - $possiblePrivileges = array("DARF_DIENSTE_VERWALTEN"); - if (null!==$row["privName"]) { - $possiblePrivileges[] = $row["privName"]; - } - // Unprivilegierte Aktionen - if ("join"==$input["action"] && "0"==$row["vorbei"] && - ((in_array($row["Abteilung"], $abteilungenIDs) && in_array($row["Personalgruppe"], $gruppenIDs) && intVal($row["naheDienste"])==0) || lgnCheckRight($mysqli, $possiblePrivileges, $userID))) { - // Möchte beitreten - if ($row["Ausgelost"]=="1" && (intVal($row["num"]) query("REPLACE INTO Terminteilnahmen (Termin, Personal, Status) VALUES ('".$row["tID"]."', '".$userID."', 1)"); - informParticipantFromID($mysqli, $row["tID"], $userID, true); - markOrSendParticipantsList($mysqli, $row["tID"]); - } else { - $mysqli->query("REPLACE INTO Terminteilnahmen (Termin, Personal, Status) VALUES ('".$row["tID"]."', '".$userID."', 0)"); - } - } else if ("leave"==$input["action"] && "0"==$row["vorbei"]) { - // Möchte verlassen - $mysqli->query("DELETE FROM Terminteilnahmen WHERE Termin = ".$row["tID"]." AND Personal = ".$userID); - if (1==$mysqli->affected_rows && "1"==$row["Ausgelost"]) { - markOrSendParticipantsList($mysqli, $row["tID"]); - } - } else if (lgnCheckRight($mysqli, $possiblePrivileges) || "2"==$row["Status"]) { - // Privilegierte Aktionen - - if ("remove"==$input["action"]) { - // Teilnehmer soll entfernt werden - if (isset($input["target"], $input["confirm"])) { - // Entferne Teilnehmer - $qry = "DELETE FROM Terminteilnahmen WHERE Termin = ? AND Personal = ?"; - if ($stmt = $mysqli->prepare($qry)) { - $stmt->bind_param("ii", $row["tID"], $input["target"]); - $stmt->execute(); - if (1==$stmt->affected_rows && "1"==$row["Ausgelost"]) { - informParticipantFromID($mysqli, $row["tID"], $input["target"], false); - markOrSendParticipantsList($mysqli, $row["tID"]); - } - } - } - } else if ("add"==$input["action"] && isset($input["target"], $input["stage"], $input["confirm"])) { - // Teilnehmer soll hinzugefügt werden - $qry = "REPLACE INTO Terminteilnahmen VALUES (?, ?, ?)"; - if ($stmt = $mysqli->prepare($qry)) { - $stmt->bind_param("iii", $row["tID"], $input["target"], $input["stage"]); - $stmt->execute(); - if (1==$stmt->affected_rows && "1"==$row["Ausgelost"]) { - informParticipantFromID($mysqli, $row["tID"], $input["target"], true); - markOrSendParticipantsList($mysqli, $row["tID"]); - } - } - } - if (("edit"==$input["action"] || "add"==$input["action"]) && (!isset($input["confirm"]) || "addmore"==$input["confirm"])) { - // Bestätigung abfragen - $addqry = "SELECT p.ID,Vornamen,Nachnamen,tln.Status FROM Personal p LEFT JOIN Terminteilnahmen tln ON tln.Personal=p.ID AND tln.Termin = ".$row["tID"]." "; - if (isset($input["target"]) && !isset($input["confirm"])) { - $addqry.= "WHERE p.ID = ? "; - $confirm = "Teilnahmestatus des Kameraden ändern zu?
"; - } else { - $confirm = "Kameraden zum Termin hinzufügen?
"; - } - $addqry.= "ORDER BY Nachnamen ASC, Vornamen ASC"; - if ($stmt = $mysqli->prepare($addqry)) { - if (isset($input["target"]) && !isset($input["confirm"])) { - $stmt->bind_param("i", $input["target"]); - } - $stmt->execute(); - $addres = $stmt->get_result(); - //$stmt->close(); - $confirm.= ""; - } else { - addError("Mysql", $mysqli->error); - } - } - - } // /Privilegierte Aktionen - } // /Termin existiert - // TODO: Corona-Unterweisung - // INSERT IGNORE INTO link_Ausbildungen_Personal SELECT DISTINCT 9, Personal FROM Terminteilnahmen t WHERE Status=1 - } // /Terminaktion - - // Termine auflisten und Anmeldestatus anzeigen - $tpl["list"] = tplExtrSection($tpl["main"], "###DIENSTE.LIST###"); - - // Bestätigungen anzeigen oder verstecken - if ($confirm!="") { - $confirm_tpl = tplExtrSection($tpl["list"], "###DIENSTE.LIST.CONFIRM###"); - $confirm = tplReplMarker($confirm_tpl, "###DIENSTE.LIST.CONFIRM.TEXT###", $confirm); - } - $tpl["list"] = tplReplSection($tpl["list"], "###DIENSTE.LIST.CONFIRM###", $confirm); - - $monthsback = $input["monthsback"] ?? 0; - $tpl["list"] = tplReplMarker($tpl["list"], "###DIENSTE.LIST.DEPARTMENTS###", implode(", ", $abteilungen)); - $tpl["list"] = tplReplMarker($tpl["list"], "###DIENSTE.LIST.GROUPS###", implode(", ", $gruppen)); - $tpl["list"] = tplReplMarker($tpl["list"], "###DIENSTE.LIST.NEWMONTHSBACK###", $monthsback+2); - - $dnst_list_entry_tpl = tplExtrSection($tpl["list"], "###DIENSTE.LIST.ENTRY###"); - $dnst_list_entry = ""; - $qry = "SELECT t.ID, t.Beginn, t.Ende, t.Ort, t.Thema, t.Verantwortliche, t.Ausgelost, t.Personalgruppe, IF(t.Beginn=1) numDabei " - .", (SELECT COUNT(*) FROM Terminteilnahmen t3 WHERE t3.Termin=t.ID AND t3.Status=0) numWill " - .", (SELECT COUNT(*) FROM Terminteilnahmen t4 LEFT JOIN Termine t5 ON t5.ID=t4.Termin " - ." WHERE t4.Personal=? AND t5.ID!=t.ID AND t5.Terminart=t.Terminart " - ." AND t5.Beginn BETWEEN t.Beginn - INTERVAL 7 DAY AND t.Beginn + INTERVAL 7 DAY) naheDienste " - ."FROM Termine t " - ."LEFT JOIN Terminplatzvergabe pv ON pv.ID=t.Platzvergabe " - ."LEFT JOIN Terminarten ta ON ta.ID=t.Terminart " - ."LEFT JOIN Berechtigungen b ON b.ID=ta.Verwaltungsrecht " - ."LEFT JOIN Dienstpläne d ON d.ID=t.Dienstplan " - ."LEFT JOIN Abteilungen ab ON ab.ID=d.Abteilung " - ."LEFT JOIN Personalgruppen pg ON pg.ID=t.Personalgruppe " - ."LEFT JOIN Terminteilnahmen tln ON t.ID=tln.Termin AND tln.Personal = ? " - ."WHERE t.Ende > NOW() - INTERVAL ? MONTH " - ."ORDER BY t.Beginn ASC "; - if ($stmt = $mysqli->prepare($qry)) { - $stmt->bind_param("iii", $userID, $userID, $monthsback); - $stmt->execute(); - $res = $stmt->get_result(); - while ($row = $res->fetch_assoc()) { - $entry = $dnst_list_entry_tpl; - $restrictions = $row["MaxTeilnehmer"]!==null && $row["MaxTeilnehmer"]>0 ? "Auf ".$row["MaxTeilnehmer"]." Plätze begrenzt!" : "Keine Platzbegrenzung."; - $terminPrivileges = $row["privName"]!==null ? array("DARF_DIENSTE_VERWALTEN", $row["privName"]) : "DARF_DIENSTE_VERWALTEN"; - $replace = array( - "###DIENSTE.LIST.ENTRY.ID###" => $row["ID"], - "###DIENSTE.LIST.ENTRY.BEGIN###" => $row["Beginn"], - "###DIENSTE.LIST.ENTRY.BEGIN.DATE###" => strftime("%a, %d.%m.%Y", strtotime($row["Beginn"])), - "###DIENSTE.LIST.ENTRY.BEGIN.TIME###" => strftime("%H:%M", strtotime($row["Beginn"])), - "###DIENSTE.LIST.ENTRY.END###" => $row["Ende"], - "###DIENSTE.LIST.ENTRY.END.DATE###" => strftime("%d.%m.%Y", strtotime($row["Ende"])), - "###DIENSTE.LIST.ENTRY.END.TIME###" => strftime("%H:%M", strtotime($row["Ende"])), - "###DIENSTE.LIST.ENTRY.TYPE###" => $row["taName"], - "###DIENSTE.LIST.ENTRY.DEPARTMENT###" => $row["abName"], - "###DIENSTE.LIST.ENTRY.TOPIC###" => $row["Thema"], - "###DIENSTE.LIST.ENTRY.RESTRICTIONS###" => $restrictions - ); - - if ((in_array($row["abID"], $abteilungenIDs) && in_array($row["Personalgruppe"], $gruppenIDs)) || lgnCheckRight($mysqli, $terminPrivileges)) { - $linkJoin = " ###NAME### "; - $linkLeave = "###NAME### "; - if (intVal($row["naheDienste"])>0) { - $replace["###DIENSTE.LIST.ENTRY.ALLOWED###"] = ""; - } else { - $replace["###DIENSTE.LIST.ENTRY.ALLOWED###"] = "
"; - } - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = ""; - $replace["###DIENSTE.LIST.ENTRY.ACTIONNAME###"] = ""; - if ($row["vorbei"]=="0" && $row["Ausgelost"]=="0") { - // Noch nicht ausgelost - if ("1"==$row["Status"] || "2"==$row["Status"]) { - // Abmeldung - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = tplReplMarker($linkLeave, "###NAME###", "Abmelden"); - } else if (intVal($row["naheDienste"])==0 || lgnCheckRight($mysqli, $terminPrivileges)) { - // Anmeldung - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = tplReplMarker($linkJoin, "###NAME###", "Anmelden"); - } - } else if ($row["vorbei"]=="0") { - // Auslosung bereits geschehen, Dienst aber noch nicht gestartet - if ("1"==$row["Status"] || "2"==$row["Status"]) { - // Dabei: Abmeldung möglich - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = tplReplMarker($linkLeave, "###NAME###", "Abmelden"); - } else if (("0"==$row["MaxTeilnehmer"] || $row["numDabei"]<$row["MaxTeilnehmer"]) && (intVal($row["naheDienste"])==0 || lgnCheckRight($mysqli, $terminPrivileges))) { - // Nicht dabei und Plätze frei: Mitmachen möglich - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = tplReplMarker($linkJoin, "###NAME###", "Mitmachen"); - } else if ($row["Status"]==null && (intVal($row["naheDienste"])==0 || lgnCheckRight($mysqli, $terminPrivileges))) { - // Nicht auf Warteliste und keine Plätze frei: Warteliste möglich - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = tplReplMarker($linkJoin, "###NAME###", "Warteliste eintragen"); - } - } - } else { - $reason = in_array($row["abID"], $abteilungenIDs) ? "Kein Mitglied der Gruppe: ".$row["pgName"] : "Falsche Abteilung: ".$row["abName"]; - $replace["###DIENSTE.LIST.ENTRY.ALLOWED###"] = "
"; - $replace["###DIENSTE.LIST.ENTRY.ACTION###"] = ""; - } - if ("2" == $row["Status"] || lgnCheckRight($mysqli, $terminPrivileges)) { - $replace["###DIENSTE.LIST.ENTRY.ACTION###"].= "" - ."
Teilnehmer hinzufügen "; - } - // Kandidatenliste - $cqry = "SELECT p.*,GROUP_CONCAT(a.Kürzel) ausb, tln.Status " - ."FROM Terminteilnahmen tln " - ."LEFT JOIN Personal p ON p.ID=tln.Personal " - ."LEFT JOIN link_Ausbildungen_Personal l ON l.Personal=p.ID LEFT JOIN Ausbildungen a ON a.ID=l.Ausbildungen " - ."WHERE tln.Termin = '".$row["ID"]."' GROUP BY p.ID ORDER BY tln.Status DESC, p.Pool DESC, p.Nachnamen ASC, a.ID ASC"; - $cres = $mysqli->query($cqry); - $chosen = array(); - $want = array(); - $responsibles = array(); - if ($cres) { - while ($cand = $cres->fetch_assoc()) { - $name = substr($cand["Vornamen"],0,1).". ".$cand["Nachnamen"]; - $line = $name; - $ausb = explode(",", $cand["ausb"]); - $feats = ""; - if (in_array("KF", $ausb)) { //Kraftfahrer - //$feats.= "🚒"; // <- There is a unicode fire engine in between the quotes - $feats.= ""; - } - /*if (in_array("CU", $ausb)) { //Corono Unterweisung - $feats.= "☣️"; // <- There is a unicode biohazard symbol in between the quotes - }*/ - if ($feats != "") { - $line.= " "./*"(".*/$feats/*.")"*/; - } - - $line = "
".$line." "; - switch ($cand["Status"]) { - case "2": - $responsibles[] = $name; - $chosen[] = "".$line.""; - break; - case "1": - $chosen[] = $line; - break; - case "0": - $want[] = $line; - break; - } - } - $cres->close(); - } else { - addError("mysql", $mysqli->error); - } - $replace["###DIENSTE.LIST.ENTRY.RESPONSIBLES###"] = implode(" / ", $responsibles); - $candidates = ""; - if (lgnCheckRight($mysqli, $terminPrivileges) || 2 == $row["Status"]) { - $candidates = tplExtrSection($entry, "###DIENSTE.LIST.ENTRY.CANDIDATES###"); - if (count($want)>0) { - $candidates = tplReplMarker($candidates, "###DIENSTE.LIST.ENTRY.CANDIDATES.WANT###", implode("", $want)); - } else { - $candidates = tplReplSection($candidates, "###DIENSTE.LIST.ENTRY.CANDIDATES.HASJOIN###", ""); - } - if (count($chosen)>0) { - $candidates = tplReplMarker($candidates, "###DIENSTE.LIST.ENTRY.CANDIDATES.CHOSEN###", implode("", $chosen)); - } else { - $candidates = tplReplSection($candidates, "###DIENSTE.LIST.ENTRY.CANDIDATES.HASREST###", ""); - } - } - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.CANDIDATES###", $candidates); - - //Anmeldung - if ((!in_array($row["abID"], $abteilungenIDs) || !in_array($row["Personalgruppe"], $gruppenIDs)) && !lgnCheckRight($mysqli, $terminPrivileges)) { - // Keine Anmeldung zugelassen, da falsche Gruppe - $answer = "FOREIGN"; - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.REGISTRATION###", ""); - } else if ($row["MaxTeilnehmer"]===null) { - // Es gibt gar keine Anmeldung, der Dienst ist für alle offen - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.CANDIDATES.HASREST###", ""); - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.CANDIDATES.HASJOIN###", ""); - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.REGISTRATION###", ""); - $answer = "NOLOTTERY"; - } else { - // Anmeldung ist möglich bzw. erwünscht - if ($row["MaxTeilnehmer"]=="0") { - // Gibt aber keine Beschränkung, daher Warteliste ausblenden - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.CANDIDATES.HASJOIN###", ""); - } - $freePlaces = intVal($row["MaxTeilnehmer"]) - intVal($row["numDabei"]); - if ("1"==$row["Ausgelost"] && $freePlaces>0) { - $entry = tplReplMarker($entry, "###DIENSTE.LIST.ENTRY.FREE.NUM###", $freePlaces); - } else { - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.FREE###", ""); - } - - if (2 == $row["Status"]) { - $answer = "LEAD"; - } else if (1 === $row["Status"] || (0 === $row["Status"] && 0 === $row["MaxTeilnehmer"])) { - // TODO: Teilnehmer bei Diensten ohne Begrenzung auf Dabei setzen, nicht auf Will - $answer = "CAN"; - } else if (0 === $row["Status"] && 1 === $row["Ausgelost"]) { - $answer = "WAIT"; - } else if (0 === $row["Status"] && 0 === $row["Ausgelost"]) { - $answer = "WANTS"; - } else { - $answer = "ADD"; - } - } - $entry = tplReplSection($entry, "###DIENSTE.LIST.ENTRY.COMES", tplExtrSection($entry, "###DIENSTE.LIST.ENTRY.COMES.".$answer."###")); - $dnst_list_entry.= tplReplMarkerArray($entry, $replace); - } - $stmt->close(); - } else { - $dnst_list_entry = $mysqli->error; - } - - $tpl["list"] = tplReplSection($tpl["list"], "###DIENSTE.LIST.ENTRY###", $dnst_list_entry); -} - -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTE.SHOW###", $tpl["show"]); -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTE.LIST###", $tpl["list"]); -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTE.LIST###", $tpl["list"]); -$output["main"] = $tpl["main"]; - -?> \ No newline at end of file diff --git a/pgs/dienste/invite_add.js b/pgs/dienste/invite_add.js deleted file mode 100644 index 5c23cff..0000000 --- a/pgs/dienste/invite_add.js +++ /dev/null @@ -1,9 +0,0 @@ -var count = 1; - -function addInviteAddRow(row) { - var tableRef = document.getElementById('invite_add').getElementsByTagName('tbody')[0]; - - // Insert a row in the table at the last row - tableRef.insertAdjacentHTML('beforeend', row.replace(/###COUNT###/g, count)); - count++; -} \ No newline at end of file diff --git a/pgs/dienste/invite_list.js b/pgs/dienste/invite_list.js deleted file mode 100644 index 176d4af..0000000 --- a/pgs/dienste/invite_list.js +++ /dev/null @@ -1,12 +0,0 @@ -var inviteListShowComers = true; - -function toggleInviteListComers() -{ - var elements = document.getElementsByClassName('invite_list_comes_0'); - var display = inviteListShowComers ? "table-row" : "none"; - for (var i = 0; i".$val["Nachnamen"].", ".$val["Vornamen"]." "; - $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.= " Corona-Unterweisung: ".($val["CoronaUnt"] ? "Ja" : "Nein")." " - ."".($val["Teilnehmer"] ? "Dabei" : "Will")." \n"; - return $ret; -} - -function informParticipant($name, $mail, $can, $dienst, $verantwortliche) { - $text = "Hallo ".$name."!
\nDu hast dich für ".$dienst." angemeldet.
"; - if ($can) { - $text.= "Du wurdest zu diesem Dienst als Teilnehmer ausgelost und DARFST KOMMEN. Sei bitte pünktlich und bring eine Maske mit!
" - ."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."
\n"; - } else { - $text.= "Leider hattest Du diesmal Pech und DARFST N I C H T KOMMEN. Dafür wirst Du bei den nächsten Verlosungen bevorzugt.
\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 Terminarten a ON a.ID = t.Terminart 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 markOrSendParticipantsList($mysqli, $terminID) { - - $mysqli->query("UPDATE Termine SET TeilnahmeGeändert=1 WHERE Beginn > NOW() + INTERVAL 1 DAY AND ID=".$terminID); - if ($mysqli->affected_rows==0) { - sendParticipantsListToResponsibles($mysqli, $terminID); - } -} - -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 Terminarten a ON a.ID=t.Terminart " - ."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,po.Name poolName,k.Kürzel kom,GROUP_CONCAT(a.Kürzel) ausbStr, tln.Status " - ."FROM Terminteilnahmen tln " - ."LEFT JOIN Personal p ON p.ID=tln.Personal " - ."LEFT JOIN Pools po ON po.ID=p.Pool " - ."LEFT JOIN Kommandofunktionen k ON k.ID=p.Kommandofkt " - ."LEFT JOIN link_Ausbildungen_Personal lap ON lap.Personal=p.ID " - ."LEFT JOIN Ausbildungen a ON a.ID=lap.Ausbildungen " - ."WHERE tln.Termin='".$terminID."' " - ."GROUP BY p.ID ORDER BY tln.Status DESC, p.Pool DESC"; - $res = $mysqli->query($qry); - - $status = ""; - $recipients = array(); - $liste = "Teilnahmeliste für ".$tmrow["aName"]." am ".date("d.m.Y H:i", strtotime($tmrow["Beginn"])).": ".$tmrow["Thema"]."
\n\n"; - $markedCauser = false; - $liste.= "
\n"; - - if (!in_array("nils.otterpohl@feuerwehr-bs.net", $recipients)) - $recipients[] = "nils.otterpohl@feuerwehr-bs.net"; - foreach ($recipients as $recipient) { - emlSendEmail($recipient, "Teilnahmeliste - Dienst ".strftime("%a, den %d.%m.%Y um %H:%M", strtotime($tmrow["Beginn"])), $liste); - } - $mysqli->query("UPDATE Termine SET TeilnahmeGeändert = 0 WHERE ID = ".$terminID); - - return $liste; - } - return ""; -} - -?> diff --git a/pgs/dienste/main.css b/pgs/dienste/main.css deleted file mode 100644 index d7f69bb..0000000 --- a/pgs/dienste/main.css +++ /dev/null @@ -1,79 +0,0 @@ -/* pgs/invite/invite.css */ -div.confirm { - padding: 1em; - background-color: yellow; - margin-bottom: 1em; -} -li img { - height: 0.8em; -} -.dienste_list_entry div { - border-bottom: 0; - border-left: 0.1em solid #000000; - border-right: 0.1em solid #000000; - border-top: 1px dashed #d0d0d0; - width: 100%; - padding: 0.2em; -} -.dienste_list_entry:last-of-type div:last-of-type { - border-bottom: 0.1em solid #000000; - width: 100%; - padding: 0.2em; -} -.dienste_list_entry div.dienste_title_row { - border-top: 0.1em solid #000000; - background-color: #d0d0d0; -} -.dienste_list_entry ul { - padding: 0; - margin: 0; - list-style: none; -} -.dienste_list_entry ul a { - text-decoration: none; - color: #000000; -} -.dienste_list_entry li { - display: inline-block; - padding: 0.2em; - margin: 0.2em; - background-color: #eeeeee; - border-radius: 0.2em; -} -.dienste_list_entry div.dienste_title_row li { - background-color: #ffffff; -} -.dienste_list_entry li img { - height: 0.8em; -} -.dienste_list_entry a li { - border: 1px solid #000000; -} -.dienste_list_entry .dienste_list_desc { - background-color: #ffffff; - font-weight: bold; -} -.dienste_list_entry .dienste_answer_good { - background-color: green; - font-weight: bold; -} -.dienste_list_entry .dienste_answer_bad { - background-color: red; -} -.dienste_list_entry .dienste_answer_pending { - background-color: lightblue; -} -.dienste_list_entry .dienste_answer_not { - background-color: white; -} -.dienste_list_entry .dienste_answer_lead { - background-color: orange; - font-weight: bold; -} -.dienste_list_entry .dienste_answer_free { - background-color: green; - font-weight: bold; -} -.dienste_list_entry .dienste_button { - /*float:right;*/ -} \ No newline at end of file diff --git a/pgs/dienste/main.html b/pgs/dienste/main.html deleted file mode 100644 index c33cefc..0000000 --- a/pgs/dienste/main.html +++ /dev/null @@ -1,69 +0,0 @@ - - - -\n"; - while ($row = $res->fetch_assoc()) { - if ($status!=$row["Status"]) { - $status = $row["Status"]; - if ("2" == $status) { - $liste.= " Name Funktionen Corona-Unterweisung \n"; - } else if ("1" == $status) { - $liste.= " Diensthabende \n"; - } else if ("0" == $status) { - $liste.= " Erlaubte Teilnehmer \n"; - } - } - $liste.= " Weitere Interessenten \n"; - - if ("2" == $row["Status"]) { - $recipients[] = $row["Login"]."@feuerwehr-bs.net"; - } - } - $liste.= " ".$row["Nachnamen"].", ".$row["Vornamen"]." "; - $ausb = explode(",", $row["ausbStr"]); - - $traits = array(); - if ($row["kom"]!=null) $traits[] = $row["kom"]; - 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.= " ".(in_array("CU", $ausb) ? "Ja" : "Nein")." - - - -Anleitung
---Hier werden die kommenden Dienste aufgelistet. Zu einigen davon kannst bzw. musst Du dich anmelden und zu einigen bist Du nicht zugelassen. Lies daher bitte die folgenden Hinweise:
--
-- Du darfst zu einem Termin nur kommen, wenn dies auch so in der Liste unten steht: "Du darfst kommen!"
-- Bei Terminen mit mehr Interessenten als Plätzen wird gelost, zur Zeit eine Woche vor Dienstbeginn.
-- Corona-Dienste werden zwei Tage vor Beginn für alle Gruppen freigegeben, falls noch Plätze frei sind.
-- Erscheine pünktlich zum Dienstbeginn! Bring eine Maske mit!
-- -
= Zugelassen zum Dienst
= Nicht zugelassen
-= Du bist bereits zu einem anderen Dienst 7 Tage vorher oder nachher angemeldet. Denke daran, dass wir die Gruppeneinteilung auch als Infektionsschutz eingerichtet haben.
- - - - diff --git a/pgs/dienste/module.json b/pgs/dienste/module.json deleted file mode 100644 index 4ef4fd8..0000000 --- a/pgs/dienste/module.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "moduleName" : "Dienste", - "title" : "Dienste", - "show" : 1, - "useRight": null, - "adminRight": "DARF_DIENSTE_VERWALTEN" -} \ No newline at end of file diff --git a/pgs/dienstplaene/index.php b/pgs/dienstplaene/index.php deleted file mode 100644 index 2fd609b..0000000 --- a/pgs/dienstplaene/index.php +++ /dev/null @@ -1,116 +0,0 @@ - tplExtrSection(tplLoadFile("pgs/dienstplaene/main.html"), "DIENSTPLAENE"), - "plans" => "", - "add" => "", - "list" => "" -); - -addStyle("pgs/dienstplaene/main.css"); - - -if ($userID = lgnCheckLogin($mysqli)) { - // Lesezeichen zeigen - $tpl["plans"] = tplExtrSection($tpl["main"], "###DIENSTPLAENE.PLANS###"); - - // Dienstpläne abrufen - $res = $mysqli->query("SELECT d.*, a.Kürzel FROM Dienstpläne d LEFT JOIN Abteilungen a ON a.ID=d.Abteilung ORDER BY d.Jahr DESC, d.Name DESC, a.ID ASC"); - $dp_select_tpl = tplExtrSection($tpl["plans"], "###DIENSTPLAENE.PLANS.PLAN###"); - $dp_select = ""; - while ($row = $res->fetch_assoc()) { - $repl = array( - "###DIENSTPLAENE.PLANS.PLAN.NAME###" => $row["Kürzel"].": ".$row["Jahr"]." - ".$row["Name"], - "###DIENSTPLAENE.PLANS.PLAN.LINK###" => "#dienstplan_".$row["ID"] - ); - $dp_select.= tplReplMarkerArray($dp_select_tpl, $repl); - } - $tpl["plans"] = tplReplSection($tpl["plans"], "###DIENSTPLAENE.PLANS.PLAN###", $dp_select); - - // Dienstplan hinzufügen - $res = $mysqli->query("SELECT * FROM Abteilungen"); - $dp_addtp_tpl = tplExtrSection($tpl["plans"], "###DIENSTPLAENE.PLANS.ADD###"); - $dp_addtp_select = ""; - while ($row = $res->fetch_assoc()) { - $repl = array( - "###DIENSTPLAENE.PLANS.ADD.ABTEILUNG.ID###" => $row["ID"], - "###DIENSTPLAENE.PLANS.ADD.ABTEILUNG.NAME###" => $row["Name"] - ); - $dp_addtp_select.= tplReplMarkerArray($dp_addtp_tpl, $repl); - } - $tpl["plans"] = tplReplSection($tpl["plans"], "###DIENSTPLAENE.PLANS.ADD###", $dp_addtp_select); - -// Termin(e) hinzufügen - $tpl["add"] = tplExtrSection($tpl["main"], "###DIENSTPLAENE.ADD###"); - - // Termine auflisten und Anmeldestatus anzeigen - $tpl["list"] = tplExtrSection($tpl["main"], "###DIENSTPLAENE.LIST###"); - $dp_block_tpl = tplExtrSection($tpl["list"], "###DIENSTPLAENE.LIST.BLOCK###"); - $dp_entry_tpl = tplExtrSection($dp_block_tpl, "###DIENSTPLAENE.LIST.ENTRY###"); - $qry = "SELECT t.ID, t.Beginn, t.Ende, t.Thema, t.Ort, d.ID dID, d.Jahr, d.Name dName, " - ."a.Name aName, ta.Name taName, b.Name bName, tpv.Name tpvName, pg.Name pgName, " - ."GROUP_CONCAT(CONCAT(LEFT(p.Vornamen, 1), '. ', p.Nachnamen) SEPARATOR ', ') Verantwortliche " - ."FROM Termine t " - ."LEFT JOIN Dienstpläne d ON d.ID = t.Dienstplan " - ."LEFT JOIN Abteilungen a ON a.ID = d.Abteilung " - ."LEFT JOIN Terminarten ta ON ta.ID = t.Terminart " - ."LEFT JOIN Berechtigungen b ON b.ID = ta.Verwaltungsrecht " - ."LEFT JOIN Terminplatzvergabe tpv ON tpv.ID = t.Platzvergabe " - ."LEFT JOIN Personalgruppen pg ON pg.ID = t.Personalgruppe " - ."LEFT JOIN Terminteilnahmen tln ON tln.Termin = t.ID AND tln.Status >= 2 " - ."LEFT JOIN Personal p ON p.ID = tln.Personal " - ."GROUP BY t.ID " - ."ORDER BY d.Jahr DESC, d.Abteilung ASC, d.Name DESC, t.Beginn ASC "; - $res = $mysqli->query($qry); - $lastdID = null; - $dienstplaene = array(); - $entries = array(); - $current = null; - while ($row = $res->fetch_assoc()) { - if ($row["dID"]!=$lastdID) { - $repl = array( - "###DIENSTPLAENE.LIST.BLOCK.DID###" => $row["dID"], - "###DIENSTPLAENE.LIST.BLOCK.ANAME###" => $row["aName"], - "###DIENSTPLAENE.LIST.BLOCK.DJAHR###" => $row["Jahr"], - "###DIENSTPLAENE.LIST.BLOCK.DNAME###" => $row["dName"] - ); - $current = &$dienstplaene[]; - $current = array( - "tpl" => tplReplMarkerArray($dp_block_tpl, $repl), - "entries" => array() - ); - $lastdID = $row["dID"]; - } - - $repl = array( - "###DIENSTPLAENE.LIST.ENTRY.ID###" => $row["ID"], - "###DIENSTPLAENE.LIST.ENTRY.BEGIN###" => $row["Beginn"], - "###DIENSTPLAENE.LIST.ENTRY.BEGIN.DATE###" => strftime("%a, %d.%m.%Y", strtotime($row["Beginn"])), - "###DIENSTPLAENE.LIST.ENTRY.BEGIN.TIME###" => strftime("%H:%M", strtotime($row["Beginn"])), - "###DIENSTPLAENE.LIST.ENTRY.END###" => $row["Ende"], - "###DIENSTPLAENE.LIST.ENTRY.END.DATE###" => strftime("%a, %d.%m.%Y", strtotime($row["Ende"])), - "###DIENSTPLAENE.LIST.ENTRY.END.TIME###" => strftime("%H:%M", strtotime($row["Ende"])), - "###DIENSTPLAENE.LIST.ENTRY.TOPIC###" => $row["Thema"], - "###DIENSTPLAENE.LIST.ENTRY.PLACE###" => $row["Ort"], - "###DIENSTPLAENE.LIST.ENTRY.TYPE###" => $row["taName"], - "###DIENSTPLAENE.LIST.ENTRY.TPVNAME###" => $row["tpvName"], - "###DIENSTPLAENE.LIST.ENTRY.PGNAME###" => $row["pgName"], - "###DIENSTPLAENE.LIST.ENTRY.RESPONSIBLES###" => $row["Verantwortliche"] - ); - $current["entries"][] = tplReplMarkerArray($dp_entry_tpl, $repl); - } - $list = ""; - foreach ($dienstplaene as $dp) { - $list.= tplReplSection($dp["tpl"], "###DIENSTPLAENE.LIST.ENTRY###", implode("", $dp["entries"])); - } - $tpl["list"] = tplReplSection($tpl["list"], "###DIENSTPLAENE.LIST.BLOCK###", $list); -} - -// Template füllen -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTPLAENE.PLANS###", $tpl["plans"]); -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTPLAENE.LIST###", $tpl["list"]); -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTPLAENE.LIST###", $tpl["list"]); -$tpl["main"] = tplReplSection($tpl["main"], "###DIENSTPLAENE.ADD###", $tpl["add"]); -$output["main"] = $tpl["main"]; - -?> \ No newline at end of file diff --git a/pgs/dienstplaene/lib.php b/pgs/dienstplaene/lib.php deleted file mode 100644 index 01be8ab..0000000 --- a/pgs/dienstplaene/lib.php +++ /dev/null @@ -1,115 +0,0 @@ -Bitte bestätige den Vorgang: ###DIENSTE.LIST.CONFIRM.TEXT###- -- Deine Abteilung(en): ###DIENSTE.LIST.DEPARTMENTS###
- -
- Deine Gruppe(n): ###DIENSTE.LIST.GROUPS###
- Ältere Dienste anzeigen --- --- --
-- ###DIENSTE.LIST.ENTRY.ALLOWED###
-- -
###DIENSTE.LIST.ENTRY.BEGIN.DATE###
- -
###DIENSTE.LIST.ENTRY.BEGIN.TIME### - ###DIENSTE.LIST.ENTRY.END.TIME###
- -
###DIENSTE.LIST.ENTRY.TYPE###
- -
###DIENSTE.LIST.ENTRY.TOPIC###
- -
###DIENSTE.LIST.ENTRY.RESPONSIBLES###
-- - --
-- ↳ ###DIENSTE.LIST.ENTRY.RESTRICTIONS###
- -- Du darfst kommen
-- Auf Warteliste
-- Zur Auslosung angemeldet
-- Bei Interesse bitte anmelden
-- Du leitest diesen Dienst
- -- Noch ###DIENSTE.LIST.ENTRY.FREE.NUM### Plätze frei
- ###DIENSTE.LIST.ENTRY.ACTION### -- - -
- ↳ Dürfen:
###DIENSTE.LIST.ENTRY.CANDIDATES.CHOSEN###- - - -
- ↳ Wollen:
###DIENSTE.LIST.ENTRY.CANDIDATES.WANT###".$val["Nachnamen"].", ".$val["Vornamen"]." "; - $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.= " Corona-Unterweisung: ".($val["CoronaUnt"] ? "Ja" : "Nein")." " - ."".($val["Teilnehmer"] ? "Dabei" : "Will")." \n"; - return $ret; -} - -function informParticipant($name, $mail, $can, $dienst, $verantwortliche) { - $text = "Hallo ".$name."!
\nDu hast dich für ".$dienst." angemeldet.
"; - if ($can) { - $text.= "Du wurdest zu diesem Dienst als Teilnehmer ausgelost und DARFST KOMMEN. Sei bitte pünktlich und bring eine Maske mit!
" - ."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."
\n"; - } else { - $text.= "Leider hattest Du diesmal Pech und DARFST N I C H T KOMMEN. Dafür wirst Du bei den nächsten Verlosungen bevorzugt.
\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 Terminarten a ON a.ID = t.Terminart 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 markOrSendParticipantsList($mysqli, $terminID) { - - $mysqli->query("UPDATE Termine SET TeilnahmeGeändert=1 WHERE Beginn > NOW() + INTERVAL 1 DAY AND ID=".$terminID); - if ($mysqli->affected_rows==0) { - sendParticipantsListToResponsibles($mysqli, $terminID); - } -} - -function sendParticipantsListToResponsibles($mysqli, $terminID) { - $qry = "SELECT t.Beginn,t.Thema,t.Verantwortliche,a.Name as aName FROM Termine t " - ."LEFT JOIN Terminarten a ON a.ID=t.Terminart " - ."WHERE t.ID='".$terminID."'"; - $res = $mysqli->query($qry); - $tmrow = $res->fetch_assoc(); - - // Generate participants list - $qry = "SELECT p.ID,p.Login,p.Nachnamen,p.Vornamen,po.Name poolName,k.Kürzel kom,GROUP_CONCAT(a.Kürzel) ausbStr, tln.Status " - ."FROM Terminteilnahmen tln " - ."LEFT JOIN Personal p ON p.ID=tln.Personal " - ."LEFT JOIN Pools po ON po.ID=p.Pool " - ."LEFT JOIN Kommandofunktionen k ON k.ID=p.Kommandofkt " - ."LEFT JOIN link_Ausbildungen_Personal lap ON lap.Personal=p.ID " - ."LEFT JOIN Ausbildungen a ON a.ID=lap.Ausbildungen " - ."WHERE tln.Termin='".$terminID."' " - ."GROUP BY p.ID ORDER BY tln.Status DESC, p.Pool DESC"; - $res = $mysqli->query($qry); - - $status = ""; - $recipients = array(); - $liste = "Teilnahmeliste für ".$tmrow["aName"]." am ".date("d.m.Y H:i", strtotime($tmrow["Beginn"])).": ".$tmrow["Thema"]."
\n\n"; - $markedCauser = false; - $liste.= "
\n"; - - if (!in_array("nils.otterpohl@feuerwehr-bs.net", $recipients)) - $recipients[] = "nils.otterpohl@feuerwehr-bs.net"; - foreach ($recipients as $recipient) { - emlSendEmail($recipient, "Teilnahmeliste - Dienst ".strftime("%a, den %d.%m.%Y um %H:%M", strtotime($tmrow["Beginn"])), $liste); - } - $mysqli->query("UPDATE Termine SET TeilnahmeGeändert = 0 WHERE ID = ".$terminID); - - return $liste; -} - -?> \ No newline at end of file diff --git a/pgs/dienstplaene/main.css b/pgs/dienstplaene/main.css deleted file mode 100644 index 6885d8d..0000000 --- a/pgs/dienstplaene/main.css +++ /dev/null @@ -1,50 +0,0 @@ -/* pgs/dienstplaene/main.css */ - -label { - width: 6em; - float: left; -} -li img { - height: 0.8em; -} -.dienstplaene_list_header { - margin: 1em 0; -} -.dienstplaene_list_entry { - border-bottom: 0; - border-left: 0.1em solid #000000; - border-right: 0.1em solid #000000; - border-top: 0.1em solid #000000; - width: 100%; - padding: 0.2em; -} -.dienstplaene_list_entry:first-child { - -} -.dienstplaene_list_entry:last-of-type { - border-bottom: 0.1em solid #000000; - width: 100%; - padding: 0.2em; -} -.dienstplaene_list_entry ul { - padding: 0; - margin: 0; - list-style: none; -} -.dienstplaene_list_entry ul a { - text-decoration: none; - color: #000000; -} -.dienstplaene_list_entry li { - display: inline-block; - padding: 0.2em; - margin: 0.2em; - background-color: #eeeeee; - border-radius: 0.2em; -} -.dienstplaene_list_entry li img { - height: 0.8em; -} -.dienstplaene_list_entry a li { - border: 1px solid #000000; -} \ No newline at end of file diff --git a/pgs/dienstplaene/main.html b/pgs/dienstplaene/main.html deleted file mode 100644 index 20d4152..0000000 --- a/pgs/dienstplaene/main.html +++ /dev/null @@ -1,72 +0,0 @@ - - - -\n"; - while ($row = $res->fetch_assoc()) { - if ($status!=$row["Status"]) { - $status = $row["Status"]; - if ("2" == $status) { - $liste.= " Name Funktionen Corona-Unterweisung \n"; - } else if ("1" == $status) { - $liste.= " Diensthabende \n"; - } else if ("0" == $status) { - $liste.= " Erlaubte Teilnehmer \n"; - } - } - $liste.= " Weitere Interessenten ".$row["Nachnamen"].", ".$row["Vornamen"]." "; - $ausb = explode(",", $row["ausbStr"]); - - $traits = array(); - if ($row["kom"]!=null) $traits[] = $row["kom"]; - 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.= " ".(in_array("CU", $ausb) ? "Ja" : "Nein")." \n"; - - if ("2" == $row["Status"]) { - $recipients[] = $row["Login"]."@feuerwehr-bs.net"; - } - } - $liste.= "- - - -- Springe zu Dienstplan: - -
-Neuen Dienstplan erstellen
-- --- - - -Dienste hinzufügen
----
-- Beginn (DateTime-Picker - Rome.js? - Vereinstellung 18 Uhr)
-- Ende (DateTime-Picker - Rome.js? - Datum gekoppelt an Beginn, Vereinstellung 21 Uhr)
-- Thema
-- Ort
-- Verantwortliche (Multi-Select - aus Personal)
-- Dienstplan (Select - aus Dienstpläne - ggfs. nach Abteilung filtern)
-- Terminart (Select - aus Terminarten - ggfs. an Verwaltungsrecht gekoppelt)
-- Platzvergabe (Select - aus Terminplatzvergabe)
-- Personalgruppe (Select - aus Personalgruppen - ggfs. nach Abteilung filtern)
-- - - - - diff --git a/pgs/dienstplaene/module.json b/pgs/dienstplaene/module.json deleted file mode 100644 index e1f3bb4..0000000 --- a/pgs/dienstplaene/module.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "moduleName" : "Dienstpläne", - "title" : "Dienstpläne", - "show" : false, - "useRight": "ADMIN", - "adminRight": "DARF_DIENSTE_VERWALTEN" -} \ No newline at end of file diff --git a/pgs/info/Anfahrtskizze.png b/pgs/info/Anfahrtskizze.png new file mode 100755 index 0000000..6a9fc0d Binary files /dev/null and b/pgs/info/Anfahrtskizze.png differ diff --git a/pgs/info/index.php b/pgs/info/index.php new file mode 100755 index 0000000..3f0d176 --- /dev/null +++ b/pgs/info/index.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/pgs/info/info.css b/pgs/info/info.css new file mode 100755 index 0000000..644ef87 --- /dev/null +++ b/pgs/info/info.css @@ -0,0 +1,32 @@ +#cimpressum { +} +#ccontact { + width: 300px; + /*height: 200px;*/ + float: right; + padding-left: 5px; + border-left: 1px solid #FF0000; +} +#ctechnical { + margin-top: 10px; +} +span.cheadline { + width: 100%; + display: block; + border-bottom: 1px solid #FF0000; + color: #FF0000; + font-size: 15px; + font-weight: bold; + margin-bottom: 5px; +} +span.cheadcorrect { + width: 660px; +} +span.cclabel { + width: 100px; + float: left; + font-weight: bold; +} +tr.gone { + text-decoration: line-through; +} \ No newline at end of file diff --git a/pgs/info/info.html b/pgs/info/info.html new file mode 100755 index 0000000..4395914 --- /dev/null +++ b/pgs/info/info.html @@ -0,0 +1,48 @@ +- -- --- --
-- -
###DIENSTPLAENE.LIST.ENTRY.BEGIN.DATE###
- -
###DIENSTPLAENE.LIST.ENTRY.BEGIN.TIME### - ###DIENSTPLAENE.LIST.ENTRY.END.TIME###
- -
###DIENSTPLAENE.LIST.ENTRY.TYPE###
- -
###DIENSTPLAENE.LIST.ENTRY.TOPIC###
- -
###DIENSTPLAENE.LIST.ENTRY.PLACE###
- -
###DIENSTPLAENE.LIST.ENTRY.RESPONSIBLES###
- -
###DIENSTPLAENE.LIST.ENTRY.TPVNAME###
- -
###DIENSTPLAENE.LIST.ENTRY.PGNAME###
+ diff --git a/pgs/pics/index.php b/pgs/pics/index.php new file mode 100755 index 0000000..ae50abb --- /dev/null +++ b/pgs/pics/index.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/pic.php b/pic.php new file mode 100644 index 0000000..c212c82 --- /dev/null +++ b/pic.php @@ -0,0 +1,49 @@ + QRCode::OUTPUT_MARKUP_SVG, + 'imageBase64' => false, + 'eccLevel' => QRCode::ECC_L, + 'cssClass' => 'my-css-class', + 'svgOpacity' => 1.0, + 'addQuietzone' => false, + 'svgDefs' => + 'Örtlichkeit
+Die Trauung und die anschliessende Feier finden im wunderschönen Schloss Eldingen statt:
+Schloss Eldingen im Landkreis Celle
+
Website
Bargfelder Straße 6
29351 EldingenAuf Grund von Denkmal- und Naturschutz müssen wir beim Feiern ein paar Dinge beachten. So sind sowohl im Innen- als auch im Aussenbereich leider keine Haustiere erlaubt. Des Weiteren muss auf den Gebrauch von Konfetti, Reis, Wunderkerzen, Seifenblasen und Ähnlichem verzichtet werden.
+Im Obergeschoss gibt es ein bis zwei "Ruheräume" für die jüngeren Gäste, die nicht bis zum Ende durchhalten. Wer diese nutzen möchte, bringt bitte "Betten" und alles Nötige selbst mit.
+Anfahrt
+ ++
Parkplätze sind auf dem Gelände in ausreichender Zahl vorhanden.
+Dresscode
+Wir bitten in Abendgarderobe zu erscheinen. Gerne in fröhlichen, sommerlichen Farben. :)
+Geschenke
+Wir planen unsere Flittermonate mit einer Weltreise zu verbringen. Daher würden wir uns über Beiträge zu unserer Reisekasse sehr freuen.
+Kontakt
+Für Rückmeldungen zur Einladung benutzt bitte die Eingabemaske unter Einladung. Anfragen, deren Beantwortung alle Gäste interessieren, stellt bitte auf der Seite Diskussionen. Dort gibt es auch die Möglichkeit mit anderen Gästen zu kommunizieren, ohne dass wir die Nachrichten lesen können. Ansonsten erreicht ihr uns folgendermaßen:
++
+ Brautpaar + Beide: brautpaar@###PAGEDOMAIN### + Nur Nils: braeutigam@###PAGEDOMAIN### / 0163-6063613 + Nur Sophia: braut@###PAGEDOMAIN### + Trauzeugen + Beide: trauzeugen@###PAGEDOMAIN### + Nur Norman: trauzeuge-norman@###PAGEDOMAIN### Nur Katharina: trauzeugin-katharina@###PAGEDOMAIN### Übernachtungsmöglichkeiten
+In der näheren Umgebung gibt es einige Übernachtungsmöglichkeiten, die wir hier aufgelistet haben. Bitte beachtet, dass wir die Informationen von unserem Veranstalter übernommen und nicht auf Richtigkeit geprüft haben, fragt also lieber genau nach. Falls etwas ausgebucht ist bzw. von euch gebucht wurde, sagt uns bitte Bescheid, dann können wir das Angebot von der Liste streichen und euch langes Telefonieren ersparen. Falls alles ausgebucht sein sollte, gibt es in Celle und Wienhausen (bis zu 20 km) mehrere große Hotels, die genug Kapazitäten bieten sollten.
+Im Ort gibt es einen 24h Taxi-Service, der unter der Nummer 05148-9125445 erreichbar ist.
++
++ Kategorie Inhaber Ort Kontakt Entfernung zum Schloss Anzahl Zimmer Preis Zimmer + Privatzimmer Fam. Knoop Eldingen 05148-910573
reiner_knoop@web.de500 m 1 DZ mit Bad ab 32,50€ p.P. inkl. Frühstück + Appartment Conny Peesel Eldingen 0160-97568550
conny.peesel@t-online.de600 m 2 DZ 28€ p.P. ohne Frühstück + Privatzimmer Fam. Schrader Eldingen 05148-897
h.w.schrader@gmx.de700 m 1 DZ, 1 EZ mit Bad ab 25€ p.P. ohne Frühstück + Privatzimmer Fam. Häring Eldingen 05148-835
fam-haering@freenet.de700 m 1 DZ mit Bad ab 25€ p.P. inkl. Frühstück + Privatzimmer Fam. Denecke Eldingen 05148-1473
denecke@zahn-technik.com1000 m 1 DZ mit Bad 25€ p.P. ohne Frühstück + Pension Landhaus Severloh Metzingen 05148-604
0176-65544642
norgard@severloh.de2 km 4 DZ, 1 EZ mit 4 Bädern ab 38€ inkl. Frühstück + Privatzimmer Schlote Hohnhorst 05145-1590
i.schlote@web.de4 km 1 DZ, 1 Zimmer mit franz. Bett und 1 Bad ab 34€ p.P. inkl. Frühstück + Privatzimmer Fam. von Welck Steinhorst 05148-910638
karinvonwelck@gmx.de4 km 2 DZ mit 2 Bädern 40€ p.P. inkl. Frühstück + Appartment Jennifer Harms Luttern 05145-2847530
0172-4918271
jenna.luttern@gmail.com4 km 1 DZ 30€ p.P. ohne Frühstück + Pension und Gasthaus Landhaus Räderloh Rainer Kirk Räderloh 05148-810
info@landhaus-raederloh.de
www.landhaus-raederloh.de7 km 8 DZ ab 40€ p.P. inkl. Frühstück + Pension / Ferienwohnung Eichenhof Räderloh Familie Röling Räderloh 05148-666
webmaster@eichenhof-roeling.de
www.eichenhof-roeling.de7 km 4 DZ, 2 EZ, 3 Ferienwohnungen ab 40€ p.P. inkl. Frühstück + Hotel Deutsches Haus H. Fergel Eschede 05142-2236
hartmutfergel@aol.com
www.hotel-eschede.de10 km 11 DZ ab 44€ p.P. inkl. Frühstück + Hotel Hotel Dierks Repke 05832-977980
hotel-dierks@t-online.de
www.hotel-dierks.de12 km 17 Zimmer ab 38,50€ p.P. inkl. Frühstück Falls ihr über das Internet auf die Pension "Wassermühle Eldingen" stoßen solltet, diese ist bereits von uns für die engsten Verwandten ausgebucht, hier braucht ihr also nicht anzufragen.
++ ++ + + + ', + ]); + + + + $qrcode = (new QRCode($options))->render($data, "qr".$data.".svg"); + + /*if($gzip === true){ + header('Vary: Accept-Encoding'); + header('Content-Encoding: gzip'); + $qrcode = gzencode($qrcode ,9); + }*/ + echo $qrcode; + } +} else { + http_response_code(400); +} + diff --git a/res/dark/active.png b/res/dark/active.png new file mode 100755 index 0000000..228054e Binary files /dev/null and b/res/dark/active.png differ diff --git a/res/dark/add.png b/res/dark/add.png new file mode 100755 index 0000000..0f85c09 Binary files /dev/null and b/res/dark/add.png differ diff --git a/res/dark/alarm.png b/res/dark/alarm.png new file mode 100644 index 0000000..7a25390 Binary files /dev/null and b/res/dark/alarm.png differ diff --git a/res/dark/barcode.png b/res/dark/barcode.png new file mode 100644 index 0000000..9eccd9d Binary files /dev/null and b/res/dark/barcode.png differ diff --git a/res/cancel.png b/res/dark/cancel.png old mode 100644 new mode 100755 similarity index 100% rename from res/cancel.png rename to res/dark/cancel.png diff --git a/res/check.png b/res/dark/check.png old mode 100644 new mode 100755 similarity index 100% rename from res/check.png rename to res/dark/check.png diff --git a/res/dark/corona.png b/res/dark/corona.png new file mode 100755 index 0000000..4fa084d Binary files /dev/null and b/res/dark/corona.png differ diff --git a/res/dark/date.png b/res/dark/date.png new file mode 100755 index 0000000..e0f03e2 Binary files /dev/null and b/res/dark/date.png differ diff --git a/res/dark/delete.png b/res/dark/delete.png new file mode 100755 index 0000000..54d2023 Binary files /dev/null and b/res/dark/delete.png differ diff --git a/res/dark/department.png b/res/dark/department.png new file mode 100755 index 0000000..b9db731 Binary files /dev/null and b/res/dark/department.png differ diff --git a/res/edit.png b/res/dark/edit.png old mode 100644 new mode 100755 similarity index 100% rename from res/edit.png rename to res/dark/edit.png diff --git a/res/hide.png b/res/dark/filter_neg.png old mode 100644 new mode 100755 similarity index 100% rename from res/hide.png rename to res/dark/filter_neg.png diff --git a/res/dark/filter_off.png b/res/dark/filter_off.png new file mode 100755 index 0000000..6199024 Binary files /dev/null and b/res/dark/filter_off.png differ diff --git a/res/show.png b/res/dark/filter_on.png old mode 100644 new mode 100755 similarity index 100% rename from res/show.png rename to res/dark/filter_on.png diff --git a/res/dark/group.png b/res/dark/group.png new file mode 100755 index 0000000..fb28b77 Binary files /dev/null and b/res/dark/group.png differ diff --git a/res/dark/hide.png b/res/dark/hide.png new file mode 100755 index 0000000..2c4020b Binary files /dev/null and b/res/dark/hide.png differ diff --git a/res/dark/list.png b/res/dark/list.png new file mode 100644 index 0000000..e2f2d74 Binary files /dev/null and b/res/dark/list.png differ diff --git a/res/dark/location.png b/res/dark/location.png new file mode 100755 index 0000000..6ce1085 Binary files /dev/null and b/res/dark/location.png differ diff --git a/res/dark/logout.png b/res/dark/logout.png new file mode 100644 index 0000000..dcdd37d Binary files /dev/null and b/res/dark/logout.png differ diff --git a/res/dark/main.css b/res/dark/main.css new file mode 100755 index 0000000..4f2300f --- /dev/null +++ b/res/dark/main.css @@ -0,0 +1,184 @@ +/* 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(../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;} +ul.inline { + display: inline; + margin: 0 0.5em; + position: relative; + bottom: 0.2em; +} +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;} +a > li > img, li > a {margin: 0 0.2em 0 0.4em;} +li a { + padding: 0 0.1em; + background-color: #000; + border-radius: 0.2em; + border: 0.05em solid #fff; +} +a li {border: 0.05em solid #fff; background-color: #000;} +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; +} +nav li:hover {background-color: #f00;} +nav .current li {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%;} +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; +} +.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;} + +#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%;} + +@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%;} +} \ No newline at end of file diff --git a/res/dark/mechanism.png b/res/dark/mechanism.png new file mode 100755 index 0000000..cfea216 Binary files /dev/null and b/res/dark/mechanism.png differ diff --git a/res/dark/minus.png b/res/dark/minus.png new file mode 100644 index 0000000..db99cc6 Binary files /dev/null and b/res/dark/minus.png differ diff --git a/res/dark/note.png b/res/dark/note.png new file mode 100755 index 0000000..9491557 Binary files /dev/null and b/res/dark/note.png differ diff --git a/res/dark/number.png b/res/dark/number.png new file mode 100644 index 0000000..9e0b8bf Binary files /dev/null and b/res/dark/number.png differ diff --git a/res/dark/person.png b/res/dark/person.png new file mode 100755 index 0000000..8b3f280 Binary files /dev/null and b/res/dark/person.png differ diff --git a/res/dark/preview.png b/res/dark/preview.png new file mode 100755 index 0000000..f28be3d Binary files /dev/null and b/res/dark/preview.png differ diff --git a/res/dark/print.png b/res/dark/print.png new file mode 100755 index 0000000..9f16b58 Binary files /dev/null and b/res/dark/print.png differ diff --git a/res/dark/progress.png b/res/dark/progress.png new file mode 100755 index 0000000..d57a405 Binary files /dev/null and b/res/dark/progress.png differ diff --git a/res/dark/refresh.png b/res/dark/refresh.png new file mode 100755 index 0000000..7feb5c1 Binary files /dev/null and b/res/dark/refresh.png differ diff --git a/res/dark/save.png b/res/dark/save.png new file mode 100755 index 0000000..f4565a8 Binary files /dev/null and b/res/dark/save.png differ diff --git a/res/dark/show.png b/res/dark/show.png new file mode 100755 index 0000000..67de221 Binary files /dev/null and b/res/dark/show.png differ diff --git a/res/dark/skill.png b/res/dark/skill.png new file mode 100755 index 0000000..8692d5b Binary files /dev/null and b/res/dark/skill.png differ diff --git a/res/dark/sort_asc.png b/res/dark/sort_asc.png new file mode 100755 index 0000000..2df5645 Binary files /dev/null and b/res/dark/sort_asc.png differ diff --git a/res/dark/sort_desc.png b/res/dark/sort_desc.png new file mode 100755 index 0000000..191a728 Binary files /dev/null and b/res/dark/sort_desc.png differ diff --git a/res/dark/star.png b/res/dark/star.png new file mode 100755 index 0000000..8dfaea9 Binary files /dev/null and b/res/dark/star.png differ diff --git a/res/dark/teacher.png b/res/dark/teacher.png new file mode 100755 index 0000000..d5616ca Binary files /dev/null and b/res/dark/teacher.png differ diff --git a/res/dark/time.png b/res/dark/time.png new file mode 100755 index 0000000..1e50cb7 Binary files /dev/null and b/res/dark/time.png differ diff --git a/res/dark/topic.png b/res/dark/topic.png new file mode 100755 index 0000000..5905279 Binary files /dev/null and b/res/dark/topic.png differ diff --git a/res/dark/truck.png b/res/dark/truck.png new file mode 100755 index 0000000..6c9fce6 Binary files /dev/null and b/res/dark/truck.png differ diff --git a/res/dark/type.png b/res/dark/type.png new file mode 100755 index 0000000..00a17d7 Binary files /dev/null and b/res/dark/type.png differ diff --git a/res/dark/unavailable.png b/res/dark/unavailable.png new file mode 100755 index 0000000..8e88e82 Binary files /dev/null and b/res/dark/unavailable.png differ diff --git a/res/uncheck.png b/res/dark/uncheck.png old mode 100644 new mode 100755 similarity index 100% rename from res/uncheck.png rename to res/dark/uncheck.png diff --git a/res/dark/undo.png b/res/dark/undo.png new file mode 100755 index 0000000..859696d Binary files /dev/null and b/res/dark/undo.png differ diff --git a/res/warn.png b/res/dark/warn.png old mode 100644 new mode 100755 similarity index 100% rename from res/warn.png rename to res/dark/warn.png diff --git a/res/dropzone.min.js b/res/dropzone.min.js new file mode 100755 index 0000000..a4d6480 --- /dev/null +++ b/res/dropzone.min.js @@ -0,0 +1 @@ +"use strict";function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _possibleConstructorReturn(e,t){return!t||"object"!==_typeof(t)&&"function"!=typeof t?_assertThisInitialized(e):t}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n+ + '),this.element.appendChild(e));var l=e.getElementsByTagName("span")[0];return l&&(null!=l.textContent?l.textContent=this.options.dictFallbackMessage:null!=l.innerText&&(l.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(e,t,n,i){var r={srcX:0,srcY:0,srcWidth:e.width,srcHeight:e.height},o=e.width/e.height;null==t&&null==n?(t=r.srcWidth,n=r.srcHeight):null==t?t=n*o:null==n&&(n=t/o);var a=(t=Math.min(t,r.srcWidth))/(n=Math.min(n,r.srcHeight));if(r.srcWidth>t||r.srcHeight>n)if("crop"===i)a \n \n\n\n\n\n\n\n\n \n\n\n \n\n',drop:function(){return this.element.classList.remove("dz-drag-hover")},dragstart:function(){},dragend:function(){return this.element.classList.remove("dz-drag-hover")},dragenter:function(){return this.element.classList.add("dz-drag-hover")},dragover:function(){return this.element.classList.add("dz-drag-hover")},dragleave:function(){return this.element.classList.remove("dz-drag-hover")},paste:function(){},reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(t){var n=this;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){t.previewElement=C.createElement(this.options.previewTemplate.trim()),t.previewTemplate=t.previewElement,this.previewsContainer.appendChild(t.previewElement);var e=!0,i=!1,r=void 0;try{for(var o,a=t.previewElement.querySelectorAll("[data-dz-name]")[Symbol.iterator]();!(e=(o=a.next()).done);e=!0){var l=o.value;l.textContent=t.name}}catch(e){i=!0,r=e}finally{try{e||null==a.return||a.return()}finally{if(i)throw r}}var s=!0,u=!1,c=void 0;try{for(var d,p=t.previewElement.querySelectorAll("[data-dz-size]")[Symbol.iterator]();!(s=(d=p.next()).done);s=!0)(l=d.value).innerHTML=this.filesize(t.size)}catch(e){u=!0,c=e}finally{try{s||null==p.return||p.return()}finally{if(u)throw c}}this.options.addRemoveLinks&&(t._removeLink=C.createElement(''.concat(this.options.dictRemoveFile,"")),t.previewElement.appendChild(t._removeLink));var h=function(e){return e.preventDefault(),e.stopPropagation(),t.status===C.UPLOADING?C.confirm(n.options.dictCancelUploadConfirmation,function(){return n.removeFile(t)}):n.options.dictRemoveFileConfirmation?C.confirm(n.options.dictRemoveFileConfirmation,function(){return n.removeFile(t)}):n.removeFile(t)},f=!0,v=!1,m=void 0;try{for(var y,g=t.previewElement.querySelectorAll("[data-dz-remove]")[Symbol.iterator]();!(f=(y=g.next()).done);f=!0){y.value.addEventListener("click",h)}}catch(e){v=!0,m=e}finally{try{f||null==g.return||g.return()}finally{if(v)throw m}}}},removedfile:function(e){return null!=e.previewElement&&null!=e.previewElement.parentNode&&e.previewElement.parentNode.removeChild(e.previewElement),this._updateMaxFilesReachedClass()},thumbnail:function(e,t){if(e.previewElement){e.previewElement.classList.remove("dz-file-preview");var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-thumbnail]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){var l=o.value;l.alt=e.name,l.src=t}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}return setTimeout(function(){return e.previewElement.classList.add("dz-image-preview")},1)}},error:function(e,t){if(e.previewElement){e.previewElement.classList.add("dz-error"),"String"!=typeof t&&t.error&&(t=t.error);var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-errormessage]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){o.value.textContent=t}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}}},errormultiple:function(){},processing:function(e){if(e.previewElement&&(e.previewElement.classList.add("dz-processing"),e._removeLink))return e._removeLink.innerHTML=this.options.dictCancelUpload},processingmultiple:function(){},uploadprogress:function(e,t){if(e.previewElement){var n=!0,i=!1,r=void 0;try{for(var o,a=e.previewElement.querySelectorAll("[data-dz-uploadprogress]")[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){var l=o.value;"PROGRESS"===l.nodeName?l.value=t:l.style.width="".concat(t,"%")}}catch(e){i=!0,r=e}finally{try{n||null==a.return||a.return()}finally{if(i)throw r}}}},totaluploadprogress:function(){},sending:function(){},sendingmultiple:function(){},success:function(e){if(e.previewElement)return e.previewElement.classList.add("dz-success")},successmultiple:function(){},canceled:function(e){return this.emit("error",e,this.options.dictUploadCanceled)},canceledmultiple:function(){},complete:function(e){if(e._removeLink&&(e._removeLink.innerHTML=this.options.dictRemoveFile),e.previewElement)return e.previewElement.classList.add("dz-complete")},completemultiple:function(){},maxfilesexceeded:function(){},maxfilesreached:function(){},queuecomplete:function(){},addedfiles:function(){}},this.prototype._thumbnailQueue=[],this.prototype._processingThumbnail=!1}},{key:"extend",value:function(e){for(var t=arguments.length,n=new Array(1"))),this.clickableElements.length){!function l(){return s.hiddenFileInput&&s.hiddenFileInput.parentNode.removeChild(s.hiddenFileInput),s.hiddenFileInput=document.createElement("input"),s.hiddenFileInput.setAttribute("type","file"),(null===s.options.maxFiles||1 ".concat(this.options.dictFallbackText,"")),n+='');var i=C.createElement(n);return"FORM"!==this.element.tagName?(t=C.createElement(''))).appendChild(i):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=t?t:i}},{key:"getExistingFallback",value:function(){for(var e=function(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;if(/(^| )fallback($| )/.test(a.className))return a}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}},t=0,n=["div","form"];t ".concat(t," ").concat(this.options.dictFileSizeUnits[n])}},{key:"_updateMaxFilesReachedClass",value:function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")}},{key:"drop",value:function(e){if(e.dataTransfer){this.emit("drop",e);for(var t=[],n=0;n 1024*this.options.maxFilesize*1024?t(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(e.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):C.isValidFile(e,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(t(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",e)):this.options.accept.call(this,e,t):t(this.options.dictInvalidFileType)}},{key:"addFile",value:function(t){var n=this;t.upload={uuid:C.uuidv4(),progress:0,total:t.size,bytesSent:0,filename:this._renameFile(t)},this.files.push(t),t.status=C.ADDED,this.emit("addedfile",t),this._enqueueThumbnail(t),this.accept(t,function(e){e?(t.accepted=!1,n._errorProcessing([t],e)):(t.accepted=!0,n.options.autoQueue&&n.enqueueFile(t)),n._updateMaxFilesReachedClass()})}},{key:"enqueueFiles",value:function(e){var t=!0,n=!1,i=void 0;try{for(var r,o=e[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;this.enqueueFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"enqueueFile",value:function(e){var t=this;if(e.status!==C.ADDED||!0!==e.accepted)throw new Error("This file can't be queued because it has already been processed or was rejected.");if(e.status=C.QUEUED,this.options.autoProcessQueue)return setTimeout(function(){return t.processQueue()},0)}},{key:"_enqueueThumbnail",value:function(e){var t=this;if(this.options.createImageThumbnails&&e.type.match(/image.*/)&&e.size<=1024*this.options.maxThumbnailFilesize*1024)return this._thumbnailQueue.push(e),setTimeout(function(){return t._processThumbnailQueue()},0)}},{key:"_processThumbnailQueue",value:function(){var t=this;if(!this._processingThumbnail&&0!==this._thumbnailQueue.length){this._processingThumbnail=!0;var n=this._thumbnailQueue.shift();return this.createThumbnail(n,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.thumbnailMethod,!0,function(e){return t.emit("thumbnail",n,e),t._processingThumbnail=!1,t._processThumbnailQueue()})}}},{key:"removeFile",value:function(e){if(e.status===C.UPLOADING&&this.cancelUpload(e),this.files=without(this.files,e),this.emit("removedfile",e),0===this.files.length)return this.emit("reset")}},{key:"removeAllFiles",value:function(e){null==e&&(e=!1);var t=!0,n=!1,i=void 0;try{for(var r,o=this.files.slice()[Symbol.iterator]();!(t=(r=o.next()).done);t=!0){var a=r.value;a.status===C.UPLOADING&&!e||this.removeFile(a)}}catch(e){n=!0,i=e}finally{try{t||null==o.return||o.return()}finally{if(n)throw i}}return null}},{key:"resizeImage",value:function(r,e,t,n,o){var a=this;return this.createThumbnail(r,e,t,n,!0,function(e,t){if(null==t)return o(r);var n=a.options.resizeMimeType;null==n&&(n=r.type);var i=t.toDataURL(n,a.options.resizeQuality);return"image/jpeg"!==n&&"image/jpg"!==n||(i=ExifRestore.restore(r.dataURL,i)),o(C.dataURItoBlob(i))})}},{key:"createThumbnail",value:function(e,t,n,i,r,o){var a=this,l=new FileReader;l.onload=function(){e.dataURL=l.result,"image/svg+xml"!==e.type?a.createThumbnailFromUrl(e,t,n,i,r,o):null!=o&&o(l.result)},l.readAsDataURL(e)}},{key:"displayExistingFile",value:function(t,e,n,i,r){var o=this,a=!(4 u.options.chunkSize),s[0].upload.totalChunkCount=Math.ceil(t.size/u.options.chunkSize)}if(s[0].upload.chunked){var r=s[0],o=e[0];r.upload.chunks=[];var i=function(){for(var e=0;void 0!==r.upload.chunks[e];)e++;if(!(e>=r.upload.totalChunkCount)){0;var t=e*u.options.chunkSize,n=Math.min(t+u.options.chunkSize,r.size),i={name:u._getParamName(0),data:o.webkitSlice?o.webkitSlice(t,n):o.slice(t,n),filename:r.upload.filename,chunkIndex:e};r.upload.chunks[e]={file:r,index:e,dataBlock:i,status:C.UPLOADING,progress:0,retries:0},u._uploadData(s,[i])}};if(r.upload.finishedChunkUpload=function(e){var t=!0;e.status=C.SUCCESS,e.dataBlock=null,e.xhr=null;for(var n=0;n >1}var s=l/t;return 0==s?1:s},drawImageIOSFix=function(e,t,n,i,r,o,a,l,s,u){var c=detectVerticalSquash(t);return e.drawImage(t,n,i,r,o,a,l,s,u/c)},ExifRestore=function(){function e(){_classCallCheck(this,e)}return _createClass(e,null,[{key:"initClass",value:function(){this.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}},{key:"encode64",value:function(e){for(var t="",n=void 0,i=void 0,r="",o=void 0,a=void 0,l=void 0,s="",u=0;o=(n=e[u++])>>2,a=(3&n)<<4|(i=e[u++])>>4,l=(15&i)<<2|(r=e[u++])>>6,s=63&r,isNaN(i)?l=s=64:isNaN(r)&&(s=64),t=t+this.KEY_STR.charAt(o)+this.KEY_STR.charAt(a)+this.KEY_STR.charAt(l)+this.KEY_STR.charAt(s),n=i=r="",o=a=l=s="",u e.length)break}return n}},{key:"decode64",value:function(e){var t=void 0,n=void 0,i="",r=void 0,o=void 0,a="",l=0,s=[];for(/[^A-Za-z0-9\+\/\=]/g.exec(e)&&console.warn("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."),e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");t=this.KEY_STR.indexOf(e.charAt(l++))<<2|(r=this.KEY_STR.indexOf(e.charAt(l++)))>>4,n=(15&r)<<4|(o=this.KEY_STR.indexOf(e.charAt(l++)))>>2,i=(3&o)<<6|(a=this.KEY_STR.indexOf(e.charAt(l++))),s.push(t),64!==o&&s.push(n),64!==a&&s.push(i),t=n=i="",r=o=a="",l - - -Es sind Fehler aufgetreten:- \ No newline at end of file diff --git a/res/frontpage.jpg b/res/frontpage.jpg old mode 100644 new mode 100755 diff --git a/res/light/active.png b/res/light/active.png new file mode 100755 index 0000000..228054e Binary files /dev/null and b/res/light/active.png differ diff --git a/res/add.png b/res/light/add.png old mode 100644 new mode 100755 similarity index 100% rename from res/add.png rename to res/light/add.png diff --git a/res/light/cancel.png b/res/light/cancel.png new file mode 100755 index 0000000..f60162e Binary files /dev/null and b/res/light/cancel.png differ diff --git a/res/light/check.png b/res/light/check.png new file mode 100755 index 0000000..5cc2e9a Binary files /dev/null and b/res/light/check.png differ diff --git a/res/date.png b/res/light/date.png old mode 100644 new mode 100755 similarity index 100% rename from res/date.png rename to res/light/date.png diff --git a/res/light/delete.png b/res/light/delete.png new file mode 100755 index 0000000..cf39054 Binary files /dev/null and b/res/light/delete.png differ diff --git a/res/light/department.png b/res/light/department.png new file mode 100755 index 0000000..44ced0d Binary files /dev/null and b/res/light/department.png differ diff --git a/res/light/edit.png b/res/light/edit.png new file mode 100755 index 0000000..2cb2cc6 Binary files /dev/null and b/res/light/edit.png differ diff --git a/res/group.png b/res/light/group.png old mode 100644 new mode 100755 similarity index 100% rename from res/group.png rename to res/light/group.png diff --git a/res/light/hide.png b/res/light/hide.png new file mode 100755 index 0000000..2c4020b Binary files /dev/null and b/res/light/hide.png differ diff --git a/res/location.png b/res/light/location.png old mode 100644 new mode 100755 similarity index 100% rename from res/location.png rename to res/light/location.png diff --git a/res/light/logout.png b/res/light/logout.png new file mode 100644 index 0000000..c3b828a Binary files /dev/null and b/res/light/logout.png differ diff --git a/res/main.css b/res/light/main.css old mode 100644 new mode 100755 similarity index 63% rename from res/main.css rename to res/light/main.css index 6d03dc9..ef3692e --- a/res/main.css +++ b/res/light/main.css @@ -8,29 +8,36 @@ body { padding: 0; } header { - background: #ffffff url(frontpage.jpg) center no-repeat; + background: #ffffff url(../frontpage.jpg) center no-repeat; background-size: cover; - /*padding: 1em;*/ width: 100%; -} -header.frontpage { height: 100vh; } -header.content { - display: none; -} -#login { +#loginbox { position: absolute; width: 18em; left: calc(50% - 9em); bottom: 5em; } -#login p { +#loginbox p { background-color: #ffffff; text-align: center; padding: 0.2em; border-radius: 0.2em; } +ul { + list-style: none; + padding: 0; + margin: 0; +} +li { + display: inline-block; + padding: 0.2em; + margin: 0.2em; + background-color: #eeeeee; + border-radius: 0.2em; +} +li img {height: 0.8em;} nav { vertical-align: middle; text-align: left; @@ -38,38 +45,34 @@ nav { position: sticky; top: 0; z-index: 100; - /*width: 100vw;*/ } nav ul { padding: 0 0.5em; - margin: 0; - list-style: none; } nav a { color: #ffffff; } nav li { - display: inline-block; padding: 0.5em 0.75em; font-weight: bold; margin: 0; - border-top-left-radius: 0.2em; - border-top-right-radius: 0.2em; + background-color: #9b0000; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +nav li img { + height: 0.9em; } nav li:hover { background-color: #ff0000; } -nav a.current li { +nav .current li { background-color: #ffffff; color: #9b0000; } main { padding: 0 1.75em; } -footer { - padding: 0 2em; - background-color: #ffff00; -} article { margin: 0 0 2em 0; max-width: 60em; @@ -83,18 +86,14 @@ article img { article h1 img { height: 0.8em; } -input,button,select { - font-size: 1em; - width: 12em; -} -input[type="radio"],input[type="checkbox"] { +/*input[type="radio"],input[type="checkbox"] { width: auto; -} +}*/ input[type="submit"] { background-color: #9b0000; color: #ffffff; } -input.login { +.login { width: 12em; text-align: center; border: 0.1em solid #000000; @@ -118,9 +117,7 @@ a.toggleVisibility > * { border-radius: 0.2em; } div.toggleVisibility { -/* background-color: #eeeeee;*/ padding: 0.2em; -/* border-radius: 0.2em;*/ } div.toggleVisibility > * { margin-top: 1em; @@ -149,6 +146,41 @@ th { color: #ff0000; font-weight: bold; } +.hidden { + display: none; +} +.list_entry_row { + border-bottom: 0; + border-left: 2px solid #000000; + border-right: 2px solid #000000; + border-top: 1px dashed #d0d0d0; + padding: 0.2em; +} +.list_entry:last-of-type .list_entry_row:last-of-type { + border-bottom: 0.1em solid #000000; + padding: 0.2em; +} +.list_entry_title { + border-bottom: 0; + border-left: 2px solid #000000; + border-right: 2px solid #000000; + padding: 0.2em; + border-top: 0.1em solid #000000; + background-color: #d0d0d0; +} +.list_entry ul a { + text-decoration: none; + color: #000000; +} +.list_entry li a { + padding: 0em 0.1em; + background-color: #ffffff; + border-radius: 0.2em; + border: 1px solid #000000; +} +.list_entry_title li {background-color: #ffffff;} +.list_entry a li {border: 1px solid #000000;} + @media only screen and (orientation: portrait) { body { font-size: 5vw; @@ -157,7 +189,7 @@ th { font-size: 5vw; } main { - padding: 0 1em; + padding: 0 1em; } nav li { padding: 1em 0.35em; @@ -166,10 +198,10 @@ th { font-size: 5vw; } input,button,select { - font-size: 5vw; + font-size: 5vw; } input[type=radio] { - border: 0px; + border: 0; height: 1em; } } \ No newline at end of file diff --git a/res/mechanism.png b/res/light/mechanism.png old mode 100644 new mode 100755 similarity index 100% rename from res/mechanism.png rename to res/light/mechanism.png diff --git a/res/light/note.png b/res/light/note.png new file mode 100755 index 0000000..5222671 Binary files /dev/null and b/res/light/note.png differ diff --git a/res/light/number.png b/res/light/number.png new file mode 100644 index 0000000..257fcef Binary files /dev/null and b/res/light/number.png differ diff --git a/res/light/person.png b/res/light/person.png new file mode 100755 index 0000000..f8af1a3 Binary files /dev/null and b/res/light/person.png differ diff --git a/res/light/progress.png b/res/light/progress.png new file mode 100755 index 0000000..14606f1 Binary files /dev/null and b/res/light/progress.png differ diff --git a/res/light/save.png b/res/light/save.png new file mode 100755 index 0000000..f4565a8 Binary files /dev/null and b/res/light/save.png differ diff --git a/res/light/show.png b/res/light/show.png new file mode 100755 index 0000000..67de221 Binary files /dev/null and b/res/light/show.png differ diff --git a/res/light/star.png b/res/light/star.png new file mode 100755 index 0000000..4aabfa0 Binary files /dev/null and b/res/light/star.png differ diff --git a/res/teacher.png b/res/light/teacher.png old mode 100644 new mode 100755 similarity index 100% rename from res/teacher.png rename to res/light/teacher.png diff --git a/res/time.png b/res/light/time.png old mode 100644 new mode 100755 similarity index 100% rename from res/time.png rename to res/light/time.png diff --git a/res/topic.png b/res/light/topic.png old mode 100644 new mode 100755 similarity index 100% rename from res/topic.png rename to res/light/topic.png diff --git a/res/type.png b/res/light/type.png old mode 100644 new mode 100755 similarity index 100% rename from res/type.png rename to res/light/type.png diff --git a/res/light/unavailable.png b/res/light/unavailable.png new file mode 100755 index 0000000..8e88e82 Binary files /dev/null and b/res/light/unavailable.png differ diff --git a/res/light/uncheck.png b/res/light/uncheck.png new file mode 100755 index 0000000..237f321 Binary files /dev/null and b/res/light/uncheck.png differ diff --git a/res/light/undo.png b/res/light/undo.png new file mode 100755 index 0000000..859696d Binary files /dev/null and b/res/light/undo.png differ diff --git a/res/light/warn.png b/res/light/warn.png new file mode 100755 index 0000000..bab7ed4 Binary files /dev/null and b/res/light/warn.png differ diff --git a/res/loading.gif b/res/loading.gif new file mode 100644 index 0000000..762b0cb Binary files /dev/null and b/res/loading.gif differ diff --git a/res/main.html b/res/main.html old mode 100644 new mode 100755 index cd1f1ea..0875f4b --- a/res/main.html +++ b/res/main.html @@ -1,47 +1,42 @@ - - -
--
- ###ERRORMSG###
-###TITLE### - - - +OF56-App (REST-Style) + + + + + + + + -- - -- - ++ - - -+-- Anmeldedaten von IServ!
- (vorname.nachname)
- Sollte der Login nicht klappen, bitte versuchen ob die Zugangsdaten auf feuerwehr-bs.net funktionieren und danach bei Nils melden. + Willkommen! Bitte Anmeldedaten von IServ verwenden! (vorname.nachname)
+ Sollte der Login nicht klappen, bitte versuchen ob die Zugangsdaten auf + feuerwehr-bs.net funktionieren und danach bei Nils melden.- ###MAIN### - + + + - \ No newline at end of file +