From e9b3a8d313fe769d28d9ffb0365bc7fd1d9df0a9 Mon Sep 17 00:00:00 2001
From: Nils Otterpohl ";
+ }
+ break;
+ case "text":
+ $rotate = "";
+ if (isset($params["rotate"]) && $params["rotate"]=="1") {
+ $rotate = "transform: rotate(-90deg); ";
+ $left = "calc(".($left + ($height - $width)/2)."mm - ".$border["left"]."px); ";
+ $top = "calc(".($top + ($width - $height)/2)."mm - ".$border["top"]."px); ";
+ } else {
+ $left.= "mm; ";
+ $top.= "mm; ";
+ }
+ $font = "font-family: ".(isset($params["font"]) && $params["font"]=="narrow" ? "Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; " : "Arial, Liberation Sans; ");
+ $font.= "font-size: ".(isset($params["size"]) ? $params["size"] : $height)."mm; ";
+ if (isset($params["weight"])) {
+ $font.= "font-weight: ".$params["weight"]."; ";
+ }
+ if (isset($params["align"])) {
+ $font.= "text-align: ".$params["align"]."; ";
+ }
+ echo "".$cnt."";
+ break;
+ }
+ }
+ }
+ echo "
+
+
+
+
" : "")
+ + "
";
+ 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
+ + + + ++ ++ + + + + + +++ + + + ++
+- ↳ ###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
+ + + + + ++ + + 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
+ + + + + ++ + + 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
+ + + + ++ + +
+ + +
+ + +
+ + + 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
+ + + + + ++ + + 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
+ + + + ++ + +
+ + +
+
+
+ + + + + + + + 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
+ + + + ++ + + 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
- -- - - - 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
- -- - - - - - - 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 0000000000000000000000000000000000000000..6a9fc0df071fab2db517088a3c5f37ecfdf53368 GIT binary patch literal 227574 zcmeFZWmHsA+dn*XHxkl~NOyO4Dlv3ROLupRlysMLOE=Of-AZ@Y!2kHf9nbyk{rs-Q zSum`bIkWe^;ujZ0sw&H%A-_cifk0?-vXbf`5cCLep&`NpzoGNiQ3Sq0IjhTvgQ_No z4}b?qj^pLUQpun86F@IIY>@YOw(ie*w?*XUn}|5J?rz6FU?uUwY)3^#n-85 z%_1Ia=z@qS *wN2@i*dhz2W){G}m-? |Bk0t ze~b`g*Btvgy_;PUXhD{qqqo=enOjWboO4E*1t~iD0C(CYRoE^P5-o5H+}2xR~D@Uf4aJ zcyL>HX4AKnnO4Trkqb+$-ib3>txX~E;wq2GG{f_{Hu=A{>tA;7IesTU!J!o+< q3)rYJ{@2x@kUQ!ooqwFo|J z>|Z ^}IlqkfXExr~vX`qY*2*>@!z z&h@Z}4LoGJPi1Pm@S|Ke_e{`>h>A&bsSg!s)->)ZB7AA9iHN$Th-k$-Q!ScyGeH zC!0)2;@p*V<=SDTi*Fs;zU}iuV**ZE EzFFPr#-E)&GR+KR)!*yA~@yIRn`uAq!Z igNP@H=b;h!$S1)WHnbBs(u!s~Ss1V$O+>s?ypO8nsY>Ui61Ejd zrgXft%!__qz^VW1!hmXkgg;)gOI6D PzD zXBd45qH0iDuJnIIH;3aiy&tA@at%5r>*x>}3T#z@7yg`pW-k9mY@l0)^n2P5?+GFB z`Sc3NvBqP){#2&2;-KRHh`Ex@{163`9HU_IoF!B$;@4Kw_azahm{v|xTKz216pj|r zDQ=%zFIH`IS|*+*l dM3^4#RD n+qMBCSOu&2}-| z>Ngziur}0**GpC5$B(k5_-xihN6mr#C!GuE({y1$USE$m?H3f(x(NH(6CL$m33EF8 z6-xTne^YKto9;^H*Cn(LdO9p9WT(*lM;7U{&>LjiQ<5^-Q6N0?1T92m(FA4Ttwgp` zQ_`}ewh|d0EUXMCg%Gt{p1lm(HD$Fz9@)O#KU1SsyIQOEv-kapCSF{ZbSqN`0Uw(D zkPol;XJ`4J(5l06nr;_dZaR~RD3d5;KvGSqA)?&(vv*kS(Dm`Rt8)&2YV%`RRp+J3 zow5+wouaT>zuS;eXO@P1_BqAo33NC6hGdRRx|QwrCO$srW||1) WSxiG46;2J zw{mq${#O4P$8h*egMUH?n0|b$=~E=h@5B3< OCFd#x*4kUk#j6tzQctQ~e>#o!RWDfv&F^wpgnwe;ItGl{tR*rc2Y zRUb09?-ro9Qtu97yKGb6E?->GvT#M)Skmzvk$JvDd1IDQr>e=Ntd`5~Pjohgl}cs7 z7~@M&`6BHaQ`CO$`i#CTYHGEjy`om__f}%%|8%_dz81_VDysgJKTid$2uz3B vm_7RVm+3DA*IQ4@JLPjs~U7vH7kOn_cpy6~AjF`X{n`EfAmRbbWAU z$=5AIj4VY`Bg2@cq7;^hZY%n=sA5780UL9Cu(T2(FtI`0^2>?f?s+njo;lP#lZQXF zA%D7|n3sv+)O9t&G1%tN+QRfaG7>ygV3kt FEnhZ1BfU({YQ1HmdUlvI7r|E=Q7QMAZchVO@)7&P%@6=gA!p`PJJtp&)^M zgZ9F!tQCu)44Q=4u+>`3;`9hM+2Pky&-QK+BcnVfgNh~hK71JY|Fgkv2w?hr`fgK9 zC^i8?xnAE(cBCIqhPNCLHqmwI^qpbRe&yC~XT@N|izpNXo?E9SQ?M?&(+sO*l+_&r zPmBh!l)u@|Jg>;t>5shH^a`5nvWxpqJ!yXK>6qCMSjiEX?Fxu9E*_1+ H9k)Z() hR4)p0aD2UGHkD-b+7c Yn})7cHw#cQovc zJrkv&>U2?;iMZl@*_v(_8|HiZgKaudHS1R68nv%NValtW{hea!-T^6YMP)akDKfzy z!k+K!dAAQEA4-21DOE-NQS$!8OF%4`A1E65ztH-B3!wKlc6pfTDp_x}Ni2ksV4M(k zpgJ2Gsn?fF%0=g5195?!=Tp6geQ9%^El2jx?2aznqy1jA=9>#fA`=o~lhj-13KR%h z3=+A)WMUJ}p?WlV8fv&0?uIh5DfI+;6j-Il&Cy4vM;qjiD*J!de%D)+otP?B(6B-$ zGL^_zQ}U+y2^Hj2g+Kj*?C%@@WvX%PMsN5y#9ZCXDxGjOLCYL>%Sh)!3G--^4fki> zWvSSPxbNPhoh5oH9^1+8u;p>gMj8#c$vdt$&-4Fule)IenQnvkhrEG8-z}pod-_Q< z`5M&JOC_7mHHUl*Qc>f@@w_NBqVRJMh>2Nc=3>rOXI5b8656H;K(!m3W70TK;Yay9 z8(0Gda|`sBj*iEDlC{3Zc)=g2>~^O@wj9HxwtT{l1c%4+>i#X*WWjFi%zl@cEKM~% z1{{7}W3{)>Rz}6$Ct|39H ~SZ{HklGLC8eJ_To(RGU~jc4v{R5q zS+jn75O@b&q>Xz|#h`8&5$)1q33hT#dVcHEq!d%P$dFv2vTx#o! F@0a z%cq5~Q=5YocD~F0OTx&42Lqzj%2bDr3DZ1>16di8>6ypGKJBLIE-Aq79BaWuUiPE1 z_|fO(lzj^gq4E<7lw>AvNw=_FJ_uM_bMM|&Q>XPxMwTHgqzAtx$USkW49k{pwIsMv z7yq2qRZyg8Hm8EZhbKcwYTnEJ>HXUV%ZBSjGz}egP%;zkmeF7k^yq~31RCvzKzcWJ zD%&=@{L{acx)`if`Md~8W*Dj20C}!M)Sa0DwxJzyqf6PXh~U~Jdt{r>>e?$0O3#cz z+?LLn@pUx<_ I042Y~y?V<@~rNxvAzEkJbLnSj=**eaYBO5oFo=OyOx6dNj#rK<|rhxv9 zOPY2|DNx(qf$?TG2j0fLBNiB<$go$u6>d_B?x!{o&rf9~&@JR~8uUeP%p7DL&pe~Q z%%=2`BZp^)zZBJYyKhL^(bUz9!C$Ho6ScEPiV8mJAPerqcg(;FmK!?1?=Lqr>2<-I z+2%FWA^$y&eM;mHy*@lCHBz2mNbBUXp}^btgoHcSuy=o2;*Z+tw$bK&IJ_7(WQz-4 zy(}M~)YK18k@fg<_O4~_J&$_IBKNd=SnxsQ3u(*n-ak>qnH>1A*71C0ik|m{yWDj2 zc2M a6~5aa&mUyd!5Et~QIJ?%-SSYXE dMEMz+>HNyIu8sWs3zvKX1wH!SKVC*>A6Kk2frw~$=+kfa zcLk H$FxEngBX`u8lD97E5T6$jXJ>29#(LO%R>n~4EksplE&}}@ z=*EOj=?Rh +=uOYXCw8Sbh=6vo6y&ORG-WO@I&`bVu!UBktXTJgH1 zH^o{PO-dLF5r5LV)7=jEsT2LJU5-;Bsn2$+y*MB`%$doPkzng3TyZ@JuD(_zPLBmH z2(?3mu^m$=Ti==Q;noLxmK$1mjsod7`6_uAI&>8+)CK|aI3f0DIpqEs+!n)@Nt;v; z#x2Z9%x$6Hp-Pl&P${bXP^yd2qBW>64@f)KGs4b2DSvJT>KLin2^+@K;F;1vdMWik zD+8I}u@dvGRW l9~~av99tYJte57WAXJ7-t&}Ae`eF1h;#5jD4b&VE8oS)G{w~ z-4v;)Vo&FBc2pBkd2wL+Pj*!La(U@WcuKhPDl9#`m~T)`$NX|yJ;hK`v$8I9OX)3+ zrQm#RxD4na)4+z*>AI{P4whughy?>-mVXqiB`QZO;V4#p)&;5#T7Quv*Ppv$H7cvV zv^>~J0p8amu%RJaG;alFW$u?3z3m23&&alW#|HD+Lq<`*#&3Ge*ZM7tQZ6AP=MR3k zZefo+yrc2)WwbNg?@UunA?HQK4}^gVL!Q-;W$crz1lRKPrI _7epv(UKp?=?97MGd#6ac z{7VX0eBX#nb~ohph2-yJOaG_0K*Cs^f}4V9nbSs3>Mf?On%6?@Mn7HxSxB%H=k(Qn z Fq0`Tqu0$D^W9kGNL!%v*ulh~>^75h9nFM=5EC*2pX*YAk^mIkh) zorQb*1iZ>VFl+^p`Jp^qoV1*NMe%qHe3~9Qy{8fJKRVmrSJ5AdH@s8ux|VtGw7vI= zH3aq~bo;&}nqq7;w2UVB3K`mdtQS#)WHgDYl(-2qcUqjTDdh2Q@3Lc}#^k0o35XGF z-&}hBx10$?=%{_Hf(r|SoBm=wJI>(=lfD2` =FI26#iv7 zrV5G4nnXljzaQ~JWN9-p*KY(={y-iJWHVUHCQE&~+NuPi4x$)2vChN@{hW}mXW2J~ z{UV$e_MW(2lvqArw24{cCOwDoa9MFZ5D>;xjXiPJ=s)t#E(g7TaS_0ks@0EY!YL~j zR9tC<@?wbOA@jkVe7-x`- Cw=x%$r|O>A=1;)!vVA+F?JVicJT55H=Uc+K}}O_2Pa$ zod%EkyW=Q7HNQsd2|xPVTEwVFLa5$~SE_I6wkk@(7JM+JG|EYe+aY3k=KZm*I~qN~ zi!}7IGvox^QGS%nBjjpl<)H5sVoH#V%-9z}tZ~c;7LOLUB{|2(iXi0U+fBzhLT^y} z*Gd567|DR^+MMZhGi~L)==H%s&3L9i)}?=wc*b=RA^qKO`0;H#C4WH6g*#H-(Vv@L z{#Y8Z3gc?_oaBM4BI&i&<5SmDE=uz#cciCdSL4?#kdguFKvad)mwj)X_-{7w#Q{Nd z{pF!M^b!k!h^%h}S|iBBMpT(*ZNB7})u7@yJYR@el~J?lu~u+3oN?ih)LSlf_LWCf zC0DSW0v?RNpuN2^{7pZx?Zz(KtHib6%M~VM@$hpM|AtHk3kt~*uF!` z|6nK*t!Trc*k`1*us<*IjC7RaNBXxWugy;GwCeeXOEB~0e74&$O1IdL=)aA1gRE74 zgAz*a)! &1~gQ$d9FgeZ$b_t z5ADb6U4Lu+D08sF>fsRDvuM5qL5ob`0lCO$bo*`Lo(Sec!YLE@MAM@p^6=01%ltcU zKSUA)ey3Br^3YFxgADPxe@lS0fwg_0)@%QuSlX >VUu`q`#Fm1Mq__5 z(1b2+!QUiI?o+>vj_GJ+8c^CjJqT1FmkVD%jd1AEY9UJ<`(AJHUiaa5bE2|xD8F3p zId&tc?3w%frNQ$zbLio{D<3)CZDdzB5=w@BhkR{@-KdU~Vl1vw1cHu{50z8%nO` 6gq$`mN)M SR*79Z?sGvp@Bn16yF~v0p!}f=%jhdGw zd>tj~AnABsu&h|6=AfTtwmb)h5N1V=$J>fj!M^gUyblb =L=5zjV{N*PM7FxIMQaOYsTwQLd(JOHsYOwUAw6=Nk{zVl5|)vktX0 zVU2a5Y+$xB45&O5y7;&mB;)r?c3#iHGI%(Y1JIJ~mPsG>`?1yxqe*XhDBP_j6u4O| zC?y=aTuxqCDGfE!w}br;&PIgr@B(c{Z{cT{lt2M9o2;MyuwR~>Bq}pRN%&go?CUn- z&ElOQXf3GjlLe?%<(l3y3b3l>>BFbGv!_z3^}2qbMZP;ZyKZlZ(@U|=*QL+5+jSv! zJUt-bapoUX%0ZrGWnFEA={VvsKAEwc?(ias`8<$~e|GN4Hu8p3 tY@4h?`b9bI2H&tuHX)SEbw$PVDGKt;i|L|_Ea;P#TAH~yPVK1Q? zwA?T=w!T=H4K?O}VBYC=p6qWJc* q`L?hsZe1C9chn zSkdf@AEInEc=^cK5^*B-7;7f9>r$!O{51NAXXmV -YqE zVUx(%3>-6;qQv<|LKrr_;^~<2>>-blK~AxVMe=4F<_jz$`-xO($LTgv@JX6tFm<_l z@GGdRic$GB`POmf36bf>NSaP2yf#k;^;TATJa~p4+}H=4vTAnnxLTq4Hc&jIS_^{s zXsl_tlw5f$Yg)MQSSQ|ta*Ox9ql?cYL=CTeCC0DL%6)ws4{T7~VS5Ha+42`#srrg6 ztA0GUej8Y6O&%h)zIJ@Lp*|-cczv}mXEV+tH`YVyyIj!>Ue-F&S|-83X%cEOCFjzQ zR0X5J1rux^p6_O;a-)2W%Zm{;Sqt2wu|Mrh+6y%%+gXq|C-%R2^W6EKnM!MRJB8eD z<4x+c;{y6i&xK`DwtVN#-hhQU8zKn*38CLFHVOq%lbAr9J%_5^!UH#%jnwvx5RAxe zXL9vbZjRmc^Xf9egSAzT5ROHq^zocFDXB3?^Su_YZF+Th^qPZsk!nNop2^R=cgWde zKP CiI77$+tSnN}7Ph*2ppfoS#0V0)DV%sw6tli_ zWNUXvLH>6ZfJba>uhYSYp7NIvd+xn(gl`p&`9U)u=aC>df)Ku}M%L-%{yVFLWuJ0$ zX*u|qO!Re^N%6M$*U~U{j65NgP{-64Bo}7AK)UMJHxrNXELYtr%FB;~86HQ|G$4+n z1Kc-WPc^3FUVGuGX-v|S%V_ks7 3B)v`}$ppA75^eh#$954q#p{ z36(ACJ+@1Dra5>vCenXd*m_?v30~$bY=unf_dI2}B3J4^3Z^_iTQrPk!qeo{>u^kU z6(!jeQOc@mtCUQ&`q21Y{j6|WwG@uz=(;es^|^Q>=sil}b6p~g>|Nhrv#IV-6}E?s zWs;SLInyoS>TQP`EV6!bm0!w`3J>qJFT>pL^idxj)P6GFyx-{k64P_CMh<$2-0>XA z_pRB#PT~{EqpCq~FW93wwX#KRVO=m^+&V{oFz6rn{(%tW#bcW< aEu=2hsAfceGGPi%w!b>R|W)P3CZ>YL*>@&I=)2uO~21rqGM$5{{oCK$TPVvG9$n z2_fGn_;)oKHYrby`OH4v7MxRl$7joC(X^yMW7CKAY}jEOX(G!1EIywkfWX(wUor z?i|N;x~4TWM}EL?vZWm0?IMojWZ1vJQAQ$r@kCbdt(mf)-k?41q59%QH5YDWRro|* zEA>`X@+~8Lpk~@s>!4zYrbc#l#~)?C+Y|vgm6GlhDUgIpQH}4wC$Rg>0|+FP++QH_ zgLLxcQF%r~4%Pi`aR{{xwox%X u)?sj(cqyTbg+2vVg?f(ID@80Rm} zkuj!vIngpK{8SsUaOw*>*rhGUQvkXiRia!{ez|O+hBkn>o-fz#60_y=7ezp$4Y?&> zWG&b=kI6Q3ay*fsHYI75$W5fQE|$q&gOdIAf?PCkym;(;!>ig-;G10X1qFM5|FAxm z1WW4TJD?-%u|Nx3n|Myszz3#;3vQRj4ZbR8 Z% xc{$dmPo;ghaQT2WDQr$5D04pUT;MK4}zh>WE ze_s>*+v_7;qV!5sZYoCs_Nm2iiy=DAInvN1s)ozXpfx8?>L Mly$9%{7Kxs-W$7> z3nx*n%+cB?K~sNE(I)ySU5K_=PM5ML(l$(1EsZG}+HPIK&v3osD6QZ8Zh*#x?ZI^G z;j3!Q8NvVowD`3w*w4ZFA~$Tx^npi|3(Ltmv{wD{ zxeH@?!|y0f8q#QSXFp@pFWbI3X4MOsG+(R}jWW^br%5V}!&N1GCpyoWF|yK-Nk&QM z+EDJrbQe@5`5AYVa^Yh-6N(o<+9)U6%nAZDMf&LD@Aar4AQYurV{}_>+uTbTbc+92 zz*3j^a*yj5MLplU@gH8s8x4JPB{(?fGfp*#kPz{qc)6Z;$kSz1r%Xb6jDb||U<>hA zc>4m@R4Db(SB0KigzRwltM)mDH;OslAoj{}kf_S*odbXr60GfNxyal-jf-cxF%4Ru zRKAWCLL?QG)hT9KRRIiC2zNEb=8d;(p+v`&-hUoPYU_8vWouZA>_tn0w&)Bgh?Y`Z zwIo#%vGO-t(VgycLa8*kL`C-Xm9?m^O&5`1CR``3P99NpMdQHGkBRHJ6R#?W1xnap z+`LWgpLz2_%bD|4h2a%Nb#pV=NJV%Ql_${Pcv_UE25E94bW{ffV4lz`AHM^L&8v%a zUOW(2sjnR;PVDJ8UACDMkWvb!R%G-YDtmq{q$GWtDnO4a*vz+LsH `WD9 za5R%~G8K2MvwBNQ%d(81fw7nhh%5WxCTM`KhJDIIjJmk0j=^R1oQBT4xM49ivZ3p6 zd^*dz0hxUrwaVZkDe=2O0XBQ!FT0Yj4T(hAHCf(su-QcJcEQ$n}apSz( z;6zQEf2*|Q09DMW8!*=LVY$hNrm6iF9=^%8FP}rDCvzvR7l0Z0JY>%qS+N@D(ZGEZ zM)p{bA|zP-PHoL}Gp5^&Irt3?<4(DbWEE(IV$uV3Ya=4B_GTzv9fg51G&4iw5NiG{ zt=&<$m}#A?teE(b+qPIsMKE^c9FbU}oAmbSa$SE2z-T `SlMMON_f56C zCp&luDVt2`9FfvuvtZdEz$3Hw*A0IsZ_o$no_#e6ros~gjk2;~I#fpDE*QfcKaAyT zXzq_gTQ@!LrkV-q7c{9|EYuNU2h0uY8&&AEsNR)NO3EVRDn&K~62hwI(j$%`d{$SB zPK(M(EMH!D&+yK~5R*Y>7Ft+kA`ltj(D4)?)b`neSWlTgf>?<2CRkmsy 7{XCz z+-*rtk;MKKE+IkP;QUfNl5?URRWm0NL)J$DA(j;5&z%KpYE@t_-sH@Y ?pnX;~nr27*2(@x$z6qTYa@H*XR-`50eqbOC3H zaC)HM6?yvOadh?O*C%<4dfRjhUzGCMIY0|eDNAzP<>?%&M;GC3SKV$k+=%S*J1b!= z=jvjK>MiP__$vWtdX3|k+6^`=IwtP KguMagUi zs@dKeavXYNC|X V(V@*=g*4cJS90pZyu{me+l8ylhM}fvib3f}=BKtv{ zoGAVqw^o H^VS!48fq|a3NM4oO@Z#?84-{rQsY71sk zfFnR*@&{VW(P6dfU3sId*~~hJQ9A9|Cp0?$gHOb62jS3NOhEqmd2S7{opeKL?w{*< zXIDdbPW=%J>DQgCJUw9R(FG7KeIN?^X+j)R4)q@jXhgcwQ=gA%DW5n;EOg(cwE9*P zYWuwC(4(VnU*xPtMEgDFXggja1^PJ{*1mPuhtOO*tdDpxG$KGc+nX2I0L?54WyqV) zBdqyRTCd7I9)t>)zW99iJ2tk1V>;gxmyMS6kLa(92z|Q4^PQ6kxrTHZSN7c>oGx2G z<$`=ODRq^Hdu8)XF{abovpc1XE8oqfsb1S`hEH7iA!)Zk^M}W~-aF8 F({?5 z4<;ncd+K~tS}{z*)#h#_IXIY8?xV4&yc~>pC*LZI%O|}; ri=GWKB}#};!bvwu1Fj1(^0+))F-c6MxPwU6 zU5A6wbUNMaa!Izt!eQfAYwO-14duey4vTF-&c*3!-`oM9N9hmrQBJ^C04liufEa!@ z3`qOxH^<`h MM zkpSl|UE*rPpE2&WL`AKt0PW8IL|5LTjyzNpIZ@zlq>rlQ-g09=n2zKxhn518DD%v! z1dc8z;IAvxy79#Sdf4|dL8pH2_VoFwuI;-icIp}#{^Z_*HEyT}M0{{n&Eu;tzED>d z#RAIH8#WwH2Z3>7KosU;a{K;8`8wBIsFiOk?Jzt_Qf`O%SIlFy&lGkfsFo|pkB9g+ z1s%hUb>s_`HF>Zf2XUMA-Php#Vqve3xpZzz%vx|Lu)aY&{=*LRw{#8a8cxIOYrK%$ zSWtIS6gu)nLGz!{md(;e*)FTi(D@y{EY9IsN6bi%*un6yWdcu3H)!Un0hUDNClzt_ zt=3S<_S_eICnT~Q)iecm4^2GO!_BF!G38#GL}k;f3I`*{BkqRrZxiAAj}$XskQkq? z`W8D)-YhV@`ONM8y+osMkq+KXHNE_^wId>(&+ROy=Y}s}__f>_cEv56J)w-1XR%GQ zqbX*JgzJ_l*SdS;;Y9Ar;nbWRkae%+yfOe#MA*WDy@Ko5{a2aKmg|B#;J0XdEI(ar zpLEHvr&d}D(Wv9j$BuixI0oUyRctvn{2!M+o7#p$TMna(W;bq@K z{as-P5%+Yp@Mjvg?>Y{8N=!f?e_r{1Fvsu9Q1b2bEo~0b4tA4IM&mcTt>#|gk=vcB zLtQX>U^o8 % 5jG$rcFP$)GlDiBR!)1VHXumSUS4^h z7(_#0CWUbJd|!!th6nbhFA+$4ev}xB{%C!t`m4oSgLgp~{qMds90$s@D>DnfG76t{ zb&6@=F?IiiFGT)chyb|~@$R*Me5w=p@AytlFy(!!YCleuyXdz$w!k%}wRApWGZN+8 z@~-Z1p+ Da@1>=!-%1fpI6U@)vR5 z$BuiYxop&v9@k{;@P#cQYe2%oLDfJ~atjlW`IbkXm(F_-FuqSHZz|zVS{4#GO;1Lp zN!^aJg?%2ni#bU(Ac*-Y&?4F3lZ&q5v#3|*H!_wLh=P?TC>ZqA<;KMPFmMM5;=IRH z_}ew{;E2m9#HP+#Vw)T _!m)6GsO*h?|P+nYZDNH&1d{7R~{Bamywuvm9K2*LO-U8%FgAC!bQ7MyP# z`}|eyeKOlsO)%Rl%n%fa^v7-Z;{A4oqc%SvF#>iA^BAug8~Xj`0q<$Kipc;~Jj@ti zD==x$LV=pvn^0l0R;Kw~>-2L95^fxp{^HJrF>^IiQnA?(bdcymdE>s94H}>)9_o*d z{pAIlx6Fl?d_%|^ LdS3e(!n5<+t^ayAnO~TCrqSsbJ z^rZ9mrb&~ZD)y50;^7|+_lVw~5BnR0U%ea=dGK7$p*3$737m=fi6a2gnz@b#q_b-? z03$mNFo*&GK)hUwodPE{EBJC8%hDb+RMiI6+T@h|dZD_2E_gqFwo}mjjH=6ymDT{X zfdqvKna% 0$rm5Cv&)YSo zDb-a_PH2TE@oMsCBHp#c%>*X&0*WQV4bmlvMjO%V4Y#ytd$vhL?V2g(9a(rM&L}2N zN&$Aj&>YsO26=X@$#b}@AU5U*3Iu0}&hZ!Js%aW>TxJ(Zl*v+t8QopMo-*^QDwifT z4lVks4T9!C(17r3)E;j 71z9xV$6DfLILBH>l%HU{qijzo9(@-+ zgV!BHpy7i+0DiY1b0tuI+_t =nQ%Hkev%fWZd{p-Evg#(PpfRKUTT zn=0lu=HTpiaD4yfyU%YVfN|wnl{?o}lLJK=V1%H6;})kl*XADSR`B2)3|>&gRJ~t; z^Vf~veCzMN!D}sCJo@@WfjE`k#slrM`}@2ovgaw>Q}zqg82>Rjs{N6;dEq82g9js4 zlNK0oZAhU&$FBVpi+6K(rZvt2K=Uh2J~?VMgDwLzW9yNGD`?}=p;)YnNX_P!s4FKJ zLN00;QObPqX3@d0Adi!E5rL>ELt{T5fe(V0ynt2GwWIVI@Cx+&^-Om5GV$oq{nl)L zL4yD%OYo@u+Y}qXz(Cy
wMu@bPDol|O&6=4rfw2N=dft)O2XQJTan&ZajTf7_;3-z~V2&A!{$ukBv+t2Pbo zx5PwKjc|lR#FCJh odeDS0@n)wRIyr+@^4&Mq5XG5IbuB6_ S)CQ_R+0!s@Ebt+808P5%0*5BQYCtv9H?-|I;UK<9=3x z_qi^*)pJJjaq)y_u_0VVDEelyJz%Sf6xja6F>}DNQhy+>_Vam10x6596zL>MeZCBD ze@Wl79#5Jy>APZadM&P`$3SzYObRTw3A)lC0gJ<@0@mn3w*Q0sZf-@CzsYD!-pQJ( z-LiB)*=Kf5eVGZuq- Z06Xi{3OQ#=8QBtmoSo+d(lYWgsEpmHq+!PUlKO?^u? zw&eVG^k}GTAo*raiFvNYmt+2D=P*qvmwt;3v7)f8Dti!0s2|(A@ b0K-YDIUlqbtKwS`qhIw#AisUDM=fn=MTYVtoZ-+ToR zxjnclZr90dZWkRPo(3OoW7`syk}}T)Y`isMvgq+E`E}YN#nn3_H!TIp(q7& pV%46Jcd8C9WiP*%qYB5V`Kcbl^_qNHE_jgs_*8WG#dN@w=3fWJzQChK) z?I=`yi)BtL)Xc7q4`iMOZrIG|S>%AJm||^`>r8ZKw;t8}(gd4g?({Tv!29QYPT%Sd zfYAXEzp9nG!?N)#3=*%e?_sz_%N6A9r+l-i?Pxsv`%~Vg_1!iSUIy*Tkil=*3fOi8 zh=y>6bnI1=0Bq_b0-#Y*CQrhTXlla!*@BMpg~ ydu$6|vF k)S@v`H(_@J+-HJaHMJ9Hdw{;P9?C z_N{*n``!>$Ze*p0Z~E%HF@0r{PRkIQpAjG{t0=`h8xA4Csy7FwY3@oFkOjO<7r)vH zpKg%|1$vI)IhVf+KW_O@V}2n<#$U2-UA53O)`ABpi=MnKIjlDDz`$99B&Ag{oyReC zRK6sR%CM_#ssW&!V5ipZU?aBuew0RSlVu_<#YfzAUM(ltc4N_MOhsnJ9K%;qI_cL{ zTjh__mhN)TWS^cKioX9EY0|-*93>hf 62|s`ubPLV zg@=@Q11XI=!-$SJURVwc5dr}56ab3VxwRn#S}CoKXX7r}fpTqCGR2xp(U>G)&`sYF zT4#Q6QbFHc3~(Pknyr3P8?{Blij$`i>N30uR_IIW3P_H^g{xr{xHgc5*<6ZUMB`Ca zWx}-`tt+Nj`igUq({9<%uB6PvrM3wDM;+x${uGSiD=E-n#Aa{qa~&zM`5Iw Jp{Gi8{|!N!(}+s8!qNtY2%Xf A*r9ar)+&lmB$e5xdN(c&!c)g3e^ zT)c~ZX*B}hzIWxnG|#?-S@;`Mk?_HjM=B6AZ`G5QOCV0N*~pMP2D&pdo5X3P!cS<7 z5%kw244bL$pOnN-cE$We+0!U)gloO!2E3A~?%hgIfRBv8OeGUAcP6Gpf4dAuNcED2 zM*pH5H(xGRwX-6+#|;v#>Od>=L6 R*+m(;GU$}VOoYldaZiS9DPr>->DeG_8HPlejU>5>||2YFyt4VYLf$BXtja!{!8 z`pE&9$3WZ8eEu^z+pD464G?jO_oQB^HaULiUi|uGsnaQA@RKOprQGR}xB%D)0LFO% z* EgoClFf)O3N3evv(Z-&?7^?tXmMJ{!( zji&>XE!+hoS2POhPcUpL7HtNpwal^t+wbMXVHv`qu!n!Z(Al8ZHfpETvz&KfK(gyq zn^&qLbfjxMLJz>a-_gP b7n-!Znd`(2lKS$f+4=G7v0&a`>9HC=do$9~3!Ortgcri@o(tXXZi(!!udS+y0Y6YQ;}DU-s=Z3q@;BEvgxVBD5_clO3D z3e@c)MoTRt(X#RT$>!OyYSk)@9WzKud5&s=)d=4OXwLV9w&*e%Kd&x1f b+^S!y-v+ct9Y$%+Hh`QKXujmlvHY1vz7WK26}`M0%_sj&e?)(OUpkEklDD zcKR>8Gs7Q6OTObjeb5hzefmEdfQ3zTf4ZXb0$6Y0FV@QrjLy}n@ntuA?c|)=gn81r zHNf$H(sxkZBJ_bUv9kr|wXm`!?a*w!n!06 SFvTd_F?CwO^5WI{jbKgQGO#QoU87IG|>>`EYauknA kkjGckEsBuzp| z?uSkdr?TYtY3){IWB#wTs+`Hd*cD)F=K|9s9~d-%tN}2MBA7LGH_HPRGzE+gpxU2F z7Z;`fV)l*WPhI-ZLnU&7F@=qU`}k zUKkMA$gVZ90f_?sQ&HV`j4DUzqKIfD7%$w=4VZ}dMl)#yjP7Lk V^l@b)N;gJ*^naxTbwA_8=K+*d zv`>Ex@P20<-7y$v`=6@rd1si$x8>*~5T#p#K4yB~2|-}AJq{xYPM3}AlQb(>%e6G6 zk?F5*aX|a&4KfS(e&C}_y}3sEQCHiAznH)qky#ST1ZybylMCAV(&RGOE^dVZ#f3aj zsM0z1q)rSrX!k-;gS>T&UO*@HTa0A2#CFZ=TPg6@^uyVROZrhqPOmSNB@|9-DJFOM z6YDp#KjYqxZSmR<6`^l>Ev)>?7M1)&U++ckg*yhTRm;lyWxFjqI~FAl9PU#b^hpb? zL|KNx+Q^YQ(mOk2KmdMLmL?{_!9Y|G)lMzQ?(&h=DRMx{qsMef9Cn#F-) f}cu4bqhW&hE2RfF-@M1^8tW_)9 z-a31ZU~aqd8WY$e06#~cUCj{r$M#Gx1EXZWmj{ijsW7Af4Iuk*fJxJpJJYlWF1DZ_ z#yJc(G|c^avy8|uHmS>n>#u0CRQl_DxdS7YojnKu1Dhcg)!iYj^?;NDOxe -_w>4WCG&UOLcIm8zmh2z$+x`5F)d5|^ z`RZh@%NhkhI$!e3)7sCV4P1WxOk;I-(2S_MxLpjqTa&s}<%fxcnVkzAZCBE7O`HQ&wV7;PKVi8k0^wc9}or zaZ%{Sjd1`8M$hZA 2h#TU$)=}HGRA+eQX2%oVFO^xei?OBM=P>|pI?3I*FJM+ zOm3?@9PZmy$}dZ({R6_u%^cTDP~H;Aoc tUH zSSR<>+wNmFxze8@JfYa>oMBC_JDt@&I6y?SrFD9UfL~UKrkkx%faC(aHIK(G|6oAe zu05Z_$t(I21e5m%zDJ6Kl3n8eG4+)}bwo?I2X_b-+@0VM+}+*X-8Hzo2Y2`21a}WE zA-F?u4{mR`b?>XMPVozBnA6kKvexR}N;wzTj}LBnIF+mpyvTOYz>JPo)poqWBTDwY z!DjD-2-a@;7lF^MuPqwBs490l5rAI-WHYEyJvdc1Kov8}Y&)>lA1kWV7%=NDgh4C( z$oqWRhoTS|7-p>*GH(RcJ?<}dfdrdv8SFrkaIbIzjFm}y K@=blsQXB4m49 z{Rqa8s>hBcue8aC;t_|liK81 #5{|Lr(`^L*p$ zofkErTveg=<=VY5^mDBj;opc~pJ^nI%+p1fHOWTsxrVzeNHzp_zy~3H2kBXjgGg%m z>*?_;ZK!s>PnaE5OQdaCSm=g8m38LbbMF|ADPYH|@`nS-Y_1nHHUPEqGIsLvFSbSL zgO{ $ z3XG2R8{=SF?9qe0yXpZR52nSfz-B=kxGtVybfhF0jp#|9Cm DYExu^wQN zd)Y=X7#POVFZd8^$|X?}FV0TAX0;SL?0$Ya@qbNj>1>Xflk-d{WQr&dETZ!FV?}Yd zFQa*;7c|%X@>MPqRArv;f1_@z(~Sne`8YY_>ML3L^Tk*foiHiFs2{osT?xE>?iDa| zL^n5;#j&q10318>WMN=xk;DtnfGD>3Asn^S>7Fw}EAaY(4z%u?E&n{JGo?YA3vM53 zx$L|dAz*Z&?nHjaK6TUQ(&6DET0n}*?_4@2i!0XU%nl$yhOc4Xq~l)>;+ sx&m`XeR)2d{Krvo0QTU!K1tF@4 z&JF1P+B^m*R{+w9r~9PetGBqE_TJ&Vn84C4fdNt34g*(M{xc3S_iuqLHS z8tz8d8V{K$to*_F3rysOTnEC0QAnOC<;4x2iA mDZu&4lJAuH#dRo^C-JAW?f2uqrV(?4qEo06Wsz|4J%c6uz9{&$Wi4`>jjT>i zxTb#)0Y(d25QvO`WLH>*vUP?-Fm!`oHA)OnffXNT=BWh*wS~q=r_hp-0a|fp`zJPN zFfu-0?3E3%NY@pG7FozfZLxZX08F_OKz Za8tN?gB zq*+MNrz;ssyM5`y2s7+Dy6DMFutsf)@dltxiNJuW&`lNay{P`pZ3q8$8S1~AcF`T* zu&b@tuH*o!3MIAPhtH&R%?IRrz{DO &=QxSAV@eu~Xah$POKQ z=26b&-E!;5X~rGH%5BY KT{^DECkg`I|*u~(uG zk;2Tp)Th&csyF%^N7lWg3UVMp(arx0DjJ;3?g*Uy4oBWVP$!Gvo5jv^j*5Fta0Y<( zwptrV*1{6wzoiV#HN$9LGJ9Q004yDV1_E{ow`&A|p(%ERAfq7q@sipQn{5E5&b)S{ znUjVP0ba#S$|bwTRiR5bX2Y`bDbwBwp!7ERCczkCs}X3s%Bh)(GiqH%;YL1ZXK0=b z9j ( pVH(3 z5+t9EEynnr7=SjKzeE`Uqi-Rnt)DeZ_v@0Du&pY{Gt14(GfuzD_@e;90-)+e2N*xpU$%dE`Z1pO;?e-6 zZ=*Xz{wEAIu^PK )ge7z`rZavZ Jcvn1pwKE|slQ4lm6CoQ3_>pF $gFbM~{_V0#@;Cv_2JlMp{V zdeve&0_6u*N)g8KvPI)dj@)>vElG)2cvh<*OL5a__jizv`n+TdIUP-wOQ3G0RG1ov z$in5hPyMmE3%FwP)pUZ}yu=g)$Rt>V^PDxWH)dbvYS4|t#Px(-^On0I=WNMj-)QM} zs|5sl^V(-*`nO`f$GxgXK=1yALsQyZW(CZ4;PM(&VnyTmeJtA~>}X9PA|!Ra-cWPy zACuHwJ;xk>>ejVCDwgQ@w~>qzR5|E45G8rxp@W{pcyc;HtQ$1mrBrN-vmB|E7w1mW z+KPEuxfHgAqZbMva+;d>HLh~8j$k#N@88HroNChZzRhv%Ki+yl3H8S_nn5~iJm9MF ziD%($80fl`5z$_ R{{hO_? z93<1AkEXj3(n_4qxw1kZiA9s4`I}?qZ9y%m87J&gpL}S+O6UoKNF|#HHtu-kfp-k! zs88_+%)qQzIT*??5!u0LtEVm%T%hjH^3a_xF^G=f$`jVGt>_qB6C2OiMP3`;wvuSc zEn_p9{iFpo8%x&2$ae-Q?Zu;^>05K)Y3WFmFK>`(zz1uugU9K<=N(J`adnr?yJz1r zj~6GS863%ODT2Prpg*&RHzanNKVG(AYpk2HmHdRFZ(lAhy2qn?Zz}-gwuOFvMaE&b zFcX;AZ;i^i-$cVjGkktKjS=-A^_o|>yW58pH>^ l}fWuRZ0+y=^JeEGacqk6jX#zg_1pT?|2r?~_vvIo-)7s4+ zjscFrL0A_SvTC-slIMJQzGcn6ERZufP A zq;%^MStx2x%kL7i17D*bJFO);X1b)xb}@vObSEcr$E2$8{;eC(73KHr%#K%EsdGCI zYSOI4_UUE?V3j(%4@Tuad~4&{x*(2f;qj4}kp60mouc_BOk5bG8k&t$q#dW{CPlf^ zsYViO)+%vY !o1sjfm-sx&xD9KKcpzJ@!PWwyA2s!tNZxIE>k+|g74H;2&|^eTH%yOEeRvti+MsZyV?*;xxY4|(Z+3M0{rxo_eK&eX69 zb#GAxvrUAH8K18mKkf&|sv;cRNE^Qx{+Fg)D`wyA2mTQ*)yc^5I?4P(GrlA29C}mJ z$ Iq92z=7jZCjgfPRCasjWL;0>W%i Po;m6^rQ2)+dBXK6jfhXF$3~=#jKqXdngfwsW{A-);HCNBA|a)Y% Oc5I7U|GECyeoxK89@4$zl#{%(GmLK|v=D`V{TqxHRfl6CUk z8vRX%mcUlJKN|E`Jeh_9Z*&w_3T-cHEPQ3JG{AELRhH>CBKe-OH6}RP#j*GYmf0G_ zB4Et_^I2xr)zwLYSYDoxb$=3zvHL~bgS09u+AL7z=kZ9MCB=bAnUhRXSpG|x@SxZ( zf+o9EQ2+-uO1*;i7Y8N&v==HxS9$kbP;0#Q0?Ct((oO6R4T{byv}j(WqOAYOk@wy+ z_ywlC=rZXxXEd&IgOHV$%~W;z$*k!2Q`Bu=<)HCztszhH^qYBG*F7Q8q(owKY<48b zR4D4Kh?ZeB$6fN+SS@;u`czh#@;YI81Jux1_Y)&5cy+jxY$7dOWl6W!?{-~vX3Ife zmANb9gF NyfZUH5$C{q~zFfJK$DQ!DcRiY&8Ig@W9jW|vjBAHFBvXO; zYdZmdl#{hAcxUh>McWq%=BN>@3>l&^HRX6pAGk+Eu*mPJ!D6^PWt7R^-KMm^e*dKS zEDOKf!U#kCt*g~0JRS;7m(=m bm zvq;IhCOVB`)3V{AGs!O1zdjZ)b9PX3h{2(sRDp8Y%@l(?#Qz6n077DQz#@A^JgXfP zEeax_V%mX3DW_l%g`}at@@l7<-v~RtgdGbOnKGOa@aZbYZ55uMDgOg#?da&E@y+2s zXNrqcBGIP=vB_ VEl`}bw)Jo5Gsbu<#JT@+mi1s{zZPmi?Q6` zzmA7cGAyL!D40M~-iKYki{vGt6gVIml0A%gQiEqKFUJw?n?~<5=-i7Aci^`QP%>sKq%JEu1Qb&!;ouIBrdBs?*Ow_)B~$B-c66*v#UjA{79XiQ&AG@2 zs&}NWj)~BjuiybNe%CTD+Xg+swmPVj7;TZ;q-;n)8 x^B!> zx;yZM7Zq^`3&v)c*sHZ;`!ozhYKjPi=WV?>jN(1qToJ_?WbT&UApRf-lEW_4qj?=n z4ue_b# ga(tPj14OolDq(#tw=3L1MR{^oM1eU8lTL@f208i6eb0=t|ASGAL~=(vd`{ zT!U8AxnG)&|L^FMc{)y3t}o&J%Se3v@m)v4nI(dhz?p69?{TvUK0~5x$K^Dzr6`9J zB44!3nxglipP$+Zr>wJx0bp`P^H;Zg2{;;h_R hvAO!XP!CE9;R~H>=s#tn%N@DVz9(uT{AOe?xLo*J) zXLiPCCQBNgHQ+} 2fm@1ARV!g{XK#Vfw6(`^6>ZX zPt)@x@5bBI_UQ2VZPc&ugyWEWoC^wyW_GI9$&yld=IO#WdsSe9f`yi;yw;q`&tbjk zV!8L!J `hWAe zk9gNSeD-_mYpg#nkT5c3eQT`Gfx|Lqqg6X~8*>5$q!IZ*_Ph;R06NElBixWuvXsey zl7DyIkvlup0wb-YXo P_qNa{i5-(1BFKzGwRpHHx0&}? | m zKZd#}9i9oj%h+6XDCEXhGggP1AErO20*mGt{XeU?Zz93?{ex(|=@1Yu)x6AkZr&Kl z#=q4@vQv3-+-YGLKJB^>A*kL6*^bQ;63(AljjhR;XZGCo;l1m?OW5&gh^LqaN>_ru z5;MhA(xQuM<5qlF_i`oQ>i}XRc|^f`5<-ukm&`&p3?n2vDhtZg*2ZFb5;zcXoI$Yo zcp)n)R1Mr)#cMDU;*q3<0;u{)V_-`$XhYss$D$=$n#zMGur7~qOxC9Pg(W1!e7kLy zsat=kV4j5oZHD2-leo`M-*afZ=+Jcg? dTNm;PTzJtJ%RJRmwUQWZhV^c? zrHQWWQrb(*-ukG)Ogo{pWUqhau`YEvK+QoAn;RIxn~3qMK;%mi{-gBG`|7;M*zgq^ z!>gURBqUXNF(%2;LaVMAs=1g?Ap}}(F`L&sca+3nD JNib8`_9v~?)Z*q28Kz@yXukwP8t7=@xXA-{)Si%y~W^; zkN5gyvj{-0!23qc6;Dt4CiZ3O7imTYfbF)wqs2>(#g=V~On~o_I|~mE5Y<7D#b$j! z{s;;$O{KER%blJH%Q!^IAONzH0}MFW$y}9U)x-{$PNgd2vHr*OHXj<$R^0d-L6$~W zfUN|NrZn&4W=lTcdLw#1{GajNna =& z^h7RrykY{8!%n2F7N$|1PQ=U-vciX9dF1;6P+Z6DG(dpk>-W1xQ-6i(B1v%)u3wY7 z!iTM7A?k-*BrMVGO{WwirOZX<$u2te6mzNpe||>2HYDZ!@#%r~6aHl{=L%2swIMYV z+(@+7Gd>7eVMoy*g}-tH8eauNicV?Xh1v!O_CO9irtmAYRKG&dy77-fHk0&`o=wQK z^rJB&(pyKN<*C?cMrrHA(R)|0xZ*mSUj8fKJ31`3ah`TI&hptnb6dy`H2*vyW?PTj zk&0Y7#AX=+k6PE8nWur(lPJ;_0jiLNM5R?tkcc!KS(5m4wpJZ3QjG0+3s6Jiw&mKJ zu0)k1A{Fs+n&MGrhKaX r-#6PAPGRn+R@2yy=fJz zZ~|G^yaY&Y;;Y5_mSKTFx~wdspo*d| Vizt`Z|?+}&rHpBpoLlHRmWIOM)jgV zVFi@Gr!C;1%S*=Ep`6WE)Tpj?YHk`RWoZDty4*u*F MA7m4q7L$3GX|mM~Xj0)K^^qou z=ox-bG*G_xl&q_4R#3H#e%RmA9nQx;a}6_&Op;j9GZ=_t5F(n+FVd%8Gj9o{p$5LE zdG$|I&j}2F6Rm6@ddrdHcaE~`!xN 2fcGBGadQ^qeM2Jm! M3ZWT1?HHvUP9=ZA~Rv1wZciVk!)wP~pjE8mz!-v>DU$ z+x%zn-Xr7OxlnvfX-aTF%PCtBFf>9Kx{ksF3ydoW7L;Xy<+G~`18L#t%En=EcX0!f zFGtw!M@WV@L*KLC(?vq+pa#}?c&J1-fp?xei-LX~o*B=|Wv@c*whlvk%r&1LwH?u5 zXUi#2UOxgef|&p+5fG@MKQxZQ7nIfGgLC2XMB~~Uz7dUe-igxlSmrN8n>R30^A5!_ z6l8OFQ*hviAD;W{1m3IOC7(v*N>^!Ok_*OTGCkxoLp4?0t#?r9@}$bs-fd2h+wm&D zc0W*MxL~6+yQLTs5CI}(up`P}N9IaEd0B#Ca*JQ5(Hk{+ZomC@)D7dby4XDlTyz%I zOt@NAxLtFQ CbMmyVrQ*2_h0+1?;{#6HC=qpU6Ozwc0in|P(py7 zG-`#~6T)-l8(jjRNFbwKGh OwBl)F?5$VZhl39Tleb|#ugBc5dX2HV8c~AM?9N)?pho?co4&}I@`Z1|oFry> zdYn)pdaL63#IiK1!xk(9*d{S!h*5%t!NPN1jV6{IpeQf8@j^ N*6 z42Kwy+jFD;qx*y( 7Fz$zm+Gat5RLh z yi6&*>6o)%ktL=X`e`D@Q#(n$LTr^?fPZg)(YKx4hB4fr6`P*QLgK2WH z^H(+WG?y+_1@(4$frkMe2G$nJoCpU@RYmdAKk9`!PvqjSk1IDN_lh?>zlWeo&=&lp zo%FEFsZq_qU1sqik}j3pVUq17^>~Z%F(7|q?@~Di$?!7FB6GovSd|T#;j5`L4K&cz z#Ki#>R2o=5H_I@f?M_fBUZB=svbz$-_qf8sFUw=Dd;1DRzkd@sp*Z88Z(_`%na#Gr z+pmIT@^QEUm=}cY;|B4$nm1eFaf-rSCxsRQSU(3YS{@sFwPO;OkKW_Ud_ofpa(qAj zs(N^`y R P7qB?f3YFo9xhSAt&oFB$M} zQl`DbC_t&PVJ4#zwOF6}tTorl)f30em`$cBeBR@0#rblA5`Qt#AVh&E<;cL(Lx>yo zcf te#=H0QoDYA>=@JkW0VVFo!;;nM{4}3-9=Ms~4c0KlDOi1ag`Eov zWU1W73+K)oJgj;)`j>MRgWk+%qQA$7)XHu6h^sgtkiBXp(B}m!Jf4@NcN%hl_m-SN z0CG^33kH{O+k5;IJcdLZW)VbPfA0q;fZ1wG8VFGRf+a8uvOH=E+^7U00@Z)pdQOH* z>W%)| aZTf3r7We_#dAAZd{NXACj_(?wfTb>8IF3>U8zu6g5K?z#o0;L{8JU0gL z;PpTOU;RBl0ike5?FvCJSj%DSDC;et%>IQ;-|rB$&Xh?87H#Q$;(lkwMIf$Afd{V2 zz6YV|ymgfoccn~zm}avL(uU{(&;D(0kr{Y8%Avg1dIU3w1O)S?JL$E4gkEF4<0!mA zCq0*Hhz@IrOeSf;n?dt_5A^Z%z4Qr5si7U+HzL@}i2RNVD2j(i{lE-f*auhultsis zObuF^x6tONKQTu1eY$h39d8|+7VqB>e%7ODYJWJm{L>!|X79W%#~CXAkgT46wKWC! z&$ar%7O7!XppwTT4(VNic?-kie+T~i3U~le{8DYT$}j{hSc=B@LI5`hxB0*VzzCSH zRf}80jQUYXz}7JEgp2dRF@))X!XlIbW`jpwcD`ru;btV_lu1GQ ui<2lT&l`-xXWN+PLc?+r-%Rg57mt9obf?(CAO`EA zi>SqKKMQ+#E#8$R-qwN#=)V(ELxINxp}y{frQd!J# ev4D5f7=@t`o7mzbUats0K_2qkqZEaqMxoEzVUB|iGREE z>W~|-EPW#Xujex{MDTlM&7fLU3AC}>&IiM-Hl1Iz<9fRzn!_U(ddufWe0Q0DpjvRt zPnX4Gi)SyV_}eWlx3AfcKC|NpD?m-QOnO^%L>2lz%p5*ja1~KZqSRGZUIc<~8ma%m zjFABgHItqjECxP^C 5U0|1A0|II$@J7#adwgQ?& zR)mYEc!zKP^R7r4ckG#6)A~#-IGv6Mz7IG10s)EREL?*<6}ree2HMd;d;_-(6LK$> z3(^z>c%ar(Z=m-4cA`Zz80FD}KE2@%ll}Y49~BzgZ#z~`LpT<}IKX2CLIGk|Gn{A$ zpE{v<+Z1v#H1rY2fe-Sn^U%Iisf9xq1c&}Nnw-m9#!$mqRx6eUoau-^%Fr>k(Bka7 zC6Y|(D17&Hp!|`VF4RT;^QuudcVJIuL`2=+T9obfX+Jm%-B+?7-|i#7JsfWuSq@Ob z%0*|BAqr+vp4DG7?R9i8wY%Z@J^z|6(ywXtoPL>M^b-l~ChUAwA-b8gGtbwrnTfj3 zjyRIJ?_Fv>`**`SHN5oa3~&y^rN{Ojo_inHscdnT`z3;LdET(|*Lb4f*I^!;Frkn4 zy@%`B1+W&!dZ={~MaKinhnELxSIDBgo#N&KmIUN7u4@!nhMV_22s--T;B?e$``W_^ z`-aQ%f}6;>W|bWfm6z#4Zg{M4ah4I;)-UA=gKp&J-F|pKDCxd#D+s}Yk!6V$X^Z)q zgt88ShjG}Kp76UAoc%Ck{Un^A_b}3W@T?)s>WY8WreJh&pBrQGerX{|Bem4F2_~f* zHsdzt;l^F|`Kx8S9-Z|;PKU&33{du9UU*SWYc{73H=}GFSY*cn;cw4ak?70(&%ACB zpNqNC^?AofDTB ep zf=&SUhD5oWcT9SAPQ<-S6$6uxD;>JKEp~wm6yXTm^*+k?-h^4`*php13zI=qRicCL zVLW|!{|ybgtSvPE_%W~T&slSL;8;}FZ2+=*+5MTw8^fLN5QLq9sPDPww01+JVeB{{ z9)BiGKQZa%zNG3>!UAiGW$D&jG_x2C#56kz)G`IJ<|2YQwytTuZws|XfAD~3-%Of~ z{tIxz&o#Ulpv$ET`$51r0AJq7Fauz#>5V5UThHwh?7D&dMSI>_eFl@esICtK0kwkT zdx{1`zx+$%u^XV21u{zQ%Nbnfw+o0*nPJP%3@s|cJHzVI#qge|+m}iLPpc=)e|nHA zvh7UaNB}@`#H6Q-5Wr6%@_r^v9n>F0j(~#*AAZ%=xlM8dUuwdOK)nl@Ns%$-YD=vL z8U)0f&`De)Jn!!ELy?{bq>vg
d0T%N`2H7eD|QmT(4n!8EF(OJ~Knpadkwy zr4XqbkBm!kdC|IKq$ar|2vE$OgIG-?LF4^Kl~f*dR{vBbhpp155#mPKDzRNex%nC9 z1%@#1{p}H3vSm3O;@(kHrpvJ^?2T3&yQF8IaB;6;!Gj4aP{8ph_21JDd~wQME;3P| z{`NN6;BQ08G~ztPn)EkWg=Ts<7B6mCPVK8a3}++*`oi0F|DmdQfml7{6(X1WO+4R@ zDUr<=PayPvRLSA*<6MBK0pc>tg+CmC&hT0y->Twbj8|7Q62|t8i4gXIO*Md>=r~_K zH7VqVd>G?B69@QzWiU8?nT5iwloLdv~v;1ag{14Gz9OpyRc8+xH zCAu3S$RWvD|EZ1* y`RovC*ItJFLrYIS zfb;qnJ*u4wy!VL7Dt$rjy!$UC|D% AgO K$Et=r$Ef>XKiurf${4`emZ N}g D)*kEgYDp)3b!v( z-X5EAWEIBCwEk65r zO2@vtab;SyE}n)nDVX<8R8r#&TsZ>X$UV0|P?~Keri)MiVT?pZwY#?`NW4eeJRA`5 zX36(lcVu524|e!p%4NR1u4l8wVSvoZvI>kHP||TmmWC?mUr5bK3(M>~kwow~ie}~~ z)r)|Qc2EUYFAr`Uv)^~so!b-2&X-y1wj;~9a;72v+mKU+$pqX6Wd&*t>6ZTa1>db$ zuM;8l=9-vaHsfQ=1e)tIMZa#~Ag$sHmSe7e(ogH31DI@FPWaNtCiAX{a#FS8*5y3H z^_Pqw^aPQwQT)v5*sPm!oKddjx)$V`P`J*Ozn_(EJgHAXUW%fM=gbi>Dt7XsI0?kA zY~tyeZj}Z6=rnfEkFqaPqS8~SGzcMTylA?@t%`;?&JSPz<`AR>>@v8 cidv)M!yJ^aWN>$#hLd(hP)cULybofkG_Fg>LOC&-}+0^c4mLVMc z9q_`75MtZ%w82&YzJ9ebKP{wCVvMB0lwT4<=`H*ddjC*vnHH?^uSJ(3G|qC&IH@Q= zqkX4OD=BQt`ToV!_zxa?=>)M*ck>65&XF)?*8APVvMa!_QAhIGD4FJSuW}(f`!EnI zfW#`s>wQA-FjHH*R}uYrM!yVy`rhYVn&rgpj&ZxL50_UcHC41oJ^WLDi+=i)Lr# zci^Vi9~iKJi}zo(hmSnxI|YmEN&vfgtBjL`bsB~8`Xo)&F0=6z;oW_Bg!rKjdKp09 z2rrs?XZzdpO3c4^8V|tpC>;J*2Fs{|SLxY*1fIx$hzBQ!vn;Eqlg_IT{uV<31#s|p zMY&QnTx<<_afbrn(cigV;<->>kEwKfnbI! H7wsIe&z~$0-FDnaWTcLBq?8)80;?d*kk%~z+|wYla1amo{CztEHi)8< zR7@?gd7eQqJJrq9EEaok%_p<+IPnJtxni;g+Wt$M?+#>IYAa~lyVw%!AqOw*+lZQP zXpC!S)c_gWzZLQG44&xrgDG ^SASO#lsV$`%yd)Ce`f< zfz{u6c7@l>R7scJ&p{_96v@zG q0r0KV~QAt!{W>;c%neK|A5yUt} zS J Iqsd&|A; zudAO?p>bA4Y2)UNg7J4p%6XWq1%Ym-vSKyze8P0;^oRo`H?)WdFi}Gn5m@n#*iyT0 z2p|J49{b{tnxlI{eLOhe Z@zdS6#CZh3o;wru$vSvy!G3bq|a}x ztRtW?x0M9=iKM;^oaY!lK{NI@8as2QuQOVR?#$Wq8U*6(`8;CSg1&~FksaUJC9M@Z zI8jl2*}$!`V}4#CN4|k?KWp $10i=gq{l)hM8XXe~E6hznjtxf?!4 z3X};k+J?U4os*t}41_&0?n8GrG&fE)HElS|;K2hugbHj%`zT-m$Ks#`MMP*yyH01A zc?_80L|ne8=^Cnp-)uc0fqGr;^}wo(>L>FJdj_o8S^@a_IFpMny?X@pet#A8b)dIS zPjSSzV{#zdH%+Cq3u$PBkm2qwe`!8gSb+l&th6mH2H(*najvSHT1myp5s?FqjM&p0 z2{Ifjd?c5PmgKyGH?K1*n3 *uC?isq#=<*5MI=^SB9}*>{lp;b1$yMF-)N3&Gii@%Ltu`u!V*< zr@A9j8!p`0nKCdBV{$#u;J^ZX#3P~RqMX}gfLv&H3`joRVW3Zfp!$yCDSX5$cB>l2 zrdI~c5z?^NY9oP`_VZs! {w(^g_SQ36c`jFf(Y(m`U4rqE22 z9lu &SaskLH1+*w->Du7rB *0+Jx{}>mZ|2LAQt^`a zvi-nJKa1@B4tY>bTV@taG0Gutj~C$Y!^yOrk|*M;e1Vj_g%%mj;=b_;bAAChkYf%j zuyD253{@stOS(ZeNrL_UrfCSF4Jb wEP} zOGM@r6*B1?sL@S&<*2`2$wLdu#2NguOY*7!Kx#T*gVkQW9YmAkzR? d) zDtiNt-`UXtQ=K0~Ndu0)`wp`%W7@Lyl9FIE2cxlh!!Z(o4%-%`QCq(oFCtf^ed#>k zmcRiq^1WH6rtae|B7%xmn{VvH@kiHP%%2dw(SnXkSZaN@91ua?IX?IwzR0fbY|Ch# z`rYs##>MPgi1mIHTF50AY|LW4elZ12&a{h@G78XuCumhM%UJ37u`{zbQY`;iuv`pA zF$9s*|I*Rd6!GF7B8OB93&RHCw9zF|E?KAVmEZsL>4rRDj2M-o*;Zv3x0P}p2E|&Z z#K?x-@$PdzgV{)-+|1kzb#O$Nn0eldWE!Fk0>~~jz(d0XN1On6+4gP(+Tv5-Petfj zSG6-X6qtd$vV3Rvdr0ds0d<~`IFO`%FE(p4M*#@MZ-4l`o$71D!7c$k6EVqBnoh=l zLsfrk{x30nyCU|J4bnWujjz1;uk2Pg9_k}OaE7Q!`h<@|MpG2~7a083MTZzfgE!RZ z5 o9WDjkmNU{kT#S4N#KC4;Cf zQfTZJBG1aCe)}p9{@ih1-+lUP^QDjC`#g)g&jxjC@AS50rcv)BX~7B5n>YPyP5F0D z Jl|1fx17=1uY1cvcrtl_%i _NFwbsKP)$hnFAv+$yo z?YR2db8sFAbOsQ0|C(L+rRgXpv~!M2&~Sfp7h4tju3+H1^;yhtxzN&O1<^SZxhTsV zMs9A(f-G0Qi@6?J@C%v$6#qHPTnT7{jtik&e~0E3 ONjDA%V2Qs7UQq|kf_ejn~s;JBWQpY1jQ${~$)A}2#X77{Lw^XL6v__BQ- z7iah^Qu)kF#bUK%ftwP{e--$r{{fOgf@=A0=~7^zNkWJxnV33f8Gd9po1)6;G(=>^ z;IonF;-4BVoGdl}H3JuPcpQC>2odbkEf6n(XS$4w=0EuhOQu>= XP4U%W*6 zRyhMWy#rWEE7*9y91Sv@Z=6o@g%|#J$>