Initial commit

This commit is contained in:
2026-01-02 20:52:43 +01:00
commit e9d2257d73
854 changed files with 164132 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<?php
/*
Es gibt nur einen Steckertypen, daher ist dies zunächst irrelvant
$type = $_GET["type"];
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"]; //$plug ist die plug-ID!
Weitere Zeilen entfernt. Speicherung wäre notwendig.
*/
echo "Sp Dr L30";
?>

131
actions/bridge-editor.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
session_start();
$db = new SQLite3('../db-test/test.db');
$action = $_GET["action"];
$meta = $_GET["meta"];
$bridgeName = $_SESSION["meta"];
$plug = json_decode(file_get_contents("../settings.json") , true)["plug"];
// Funktion zur Bereinigung des Formats
function cleanPinsArray(&$data) {
// Überprüfen, ob das "pins"-Array vorhanden ist und es ein Array ist
if (isset($data) && is_array($data)) {
// Nur gültige Pins behalten
$data = array_filter($data, function($pin) {
// Sicherstellen, dass das Element nicht null ist und dass jedes Element im "pins"-Array genau 2 Einträge hat
return is_array($pin) && count($pin) === 2 && is_integer($pin[0]) && is_integer($pin[1]);
});
// Neu indexieren, um die Indizes korrekt zu halten
$data = array_values($data);
}
}
switch ($action) {
case "edit":
$bridge_name = $bridgeName;
$nodes = json_decode($meta, true); // Beispielwert
$nodes = $nodes["pins"]; // Beispielwert
cleanPinsArray($nodes); // Dies stellt sicher, dass Javscript valide Werte bringt und die Datenbank nicht verunreinigt wird.
if (empty($nodes))
{
$message[0] = false;
$message[1] = "Es wurden keine Änderungen gespeichert.";
$message = json_encode($message);
print ($message);
die();
}
$db->exec(" DELETE FROM nodes WHERE required_by = '" . $bridge_name . "' and plug_id = '" . $plug . "'; ");
foreach($nodes as $node) {
$from = $node[0];
$to = $node[1];
$db->exec(" INSERT INTO nodes (required_by, node_from, node_to, plug_id) VALUES ('" . $bridge_name . "', '" . $from . "', '" . $to . "', '" . $plug . "'); ");
}
$message[0] = true;
$message[1] = "Die Änderungen für die Brücke $bridgeName wurden gespeichert!";
$message = json_encode($message);
print ($message);
die();
break;
case "add":
// Eingabedaten
$bridgeName = $bridgeName;
$nodes = json_decode($meta, true);
$name = $nodes["name"];
$nodes = $nodes["pins"];
cleanPinsArray($nodes); // Dies stellt sicher, dass Javscript valide Werte bringt und die Datenbank nicht verunreinigt wird.
// sicherstellen, dass Werte gesetzt sind
if (empty($nodes) || empty($name))
{
$message[0] = false;
$message[1] = "Es wurden keine Änderungen gespeichert, da Parameter fehlten.";
$message = json_encode($message);
print ($message);
die();
}
$result = $db->query("SELECT COUNT(*) as anzahl FROM bridges WHERE id = '" . $name . "';");
$row = $result->fetchArray(SQLITE3_ASSOC);
$number = $row['anzahl'];
if($number > 0) {
$message[0] = false;
$message[1] = "Es gibt bereits eine Brücke, die " . $name . " heißt. Bitte einen anderen Namen wählen oder die andere Brücke " . $name . " löschen/bearbeiten. Stellen Sie zudem sicher, dass Sie den korrekten Steckertypen verwenden.";
$message = json_encode($message);
print ($message);
die();
}
$db->exec(" INSERT INTO bridges (plug_id, id) VALUES (" . $plug . ", '" . $name . "'); ");
foreach($nodes as $node) {
$from = $node[0];
$to = $node[1];
$db->exec(" INSERT INTO nodes (required_by, node_from, node_to, plug_id) VALUES ('" . $name . "', '" . $from . "', '" . $to . "', '" . $plug . "'); ");
}
$message[0] = true;
$message[1] = "Die Brücke $name wurde angelegt!";
$message = json_encode($message);
print ($message);
die();
break;
case "remove":
$bridge_name = $meta;
$db->exec(" DELETE FROM nodes WHERE required_by = '" . $bridge_name . "' and plug_id = '" . $plug . "'; ");
$db->exec(" DELETE FROM bridges WHERE id = '" . $bridge_name . "' and plug_id = '" . $plug . "'; ");
$message[0] = true;
$message[1] = "Die Brücke $bridge_name wurde nun gelöscht.";
$message = json_encode($message);
print ($message);
die();
break;
}

View File

@@ -0,0 +1,5 @@
<?php
print("true");
?>

1182
actions/extract-programs.php Normal file

File diff suppressed because it is too large Load Diff

138
actions/inventory.php Normal file
View File

@@ -0,0 +1,138 @@
<?php
session_start();
$db = new SQLite3('../db-test/test.db');
$page = $_GET["page"];
$action = $_GET["action"];
$meta = $_GET["meta"];
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"]; //$plug ist die plug-ID!
$bridgeName = $_SESSION["meta"];
$programID = json_decode(urldecode($_SESSION["meta"]), true);
switch($action) {
case "remove":
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"]; //$plug ist die plug-ID!
// Überprüfen, ob die Struktur korrekt ist
if (!isset($meta)) {
$message[0] = false;
$message[1] = "Es gab einen Fehler bei der Datenübermittlung.";
$message = json_encode($message);
print($message);
die();
}
// Hier wichtig! Beim Löschvorgang muss die referenzielle Integrität gewahrt werden! Erst die Fremdschlüssel löschen, dann die Tabelle mit dem Primärschlüssel!
$db->exec(" DELETE FROM measurement_nodes WHERE required_by = '" . $meta . "'; ");
$db->exec(" DELETE FROM measurement_program_id_bits WHERE measurement_id = '" . $meta . "'; ");
$db->exec(" DELETE FROM measurements WHERE id = '" . $meta . "'; ");
$message[0] = true;
$message[1] = "Die Messung wurde gelöscht.";
$message = json_encode($message);
print($message);
die();
break;
case "add":
$meta = json_decode($meta, true);
if(empty($meta["programCache"]) || empty($_SESSION["bridges"]) || empty($meta['name'])) {
$message[0] = false;
$message[1] = "Es gab einen Fehler beim Speichern!";
$message = json_encode($message);
print($message);
die();
}
$db->exec("INSERT INTO places (plug_id, name) VALUES (" . $plug . ", '" . $meta["name"]. "'); ");
$newdata["plugID"] = $plug;
$newdata["place"] = $placeID;
$newdata["programID"] = $meta["programCache"];
$bridges_original = $_SESSION["bridges"];
$newdata["special"] = $meta["special"];
$bridges_hidden = json_decode(urldecode($meta["bridges-hidden"]));
$newdata["timestamp"] = time();
// $bridges_result = $bridges_original - $bridges_hidden
$bridges_original;
$bridges_hidden;
// Hilfsfunktion: vergleicht zwei Arrays inhaltlich, unabhängig von String/Int
function arrays_are_equal($a, $b) {
return ((string)$a[0] === (string)$b[0]) && ((string)$a[1] === (string)$b[1]);
}
// Ergebnisarray initialisieren
$bridges_result = [];
// Für jedes Originalelement prüfen, ob es in hidden vorkommt
foreach ($bridges_original as $orig) {
$found = false;
foreach ($bridges_hidden as $hidden) {
if (arrays_are_equal($orig, $hidden)) {
$found = true;
break;
}
}
if (!$found) {
$bridges_result[] = $orig;
}
}
$newdata["bridges"] = $bridges_result;
$db->exec("INSERT INTO measurements (place_name, comment, timestamp) VALUES ('" . $meta["name"] . "', '" . $meta["special"] . "', " . time() . ");");
$measurement_id = $db->lastInsertRowID();
$i = 0;
foreach($meta["programCache"] as $program_id_char) {
$db->exec("INSERT INTO measurement_program_id_bits (value, position, measurement_id) VALUES ('" . $program_id_char . "', '" . $i. "', '" . $measurement_id . "'); ");
$i++;
}
foreach($newdata["bridges"] as $bridge) {
$db->exec("INSERT INTO measurement_nodes (required_by, node_from, node_to) VALUES (" . $measurement_id . ", '" . $bridge[0]. "', '" . $bridge[1] . "'); ");
}
$message[0] = true;
$message[1] = "Die Messung wurde am Ort '" . $meta['name'] . "' gespeichert!";
$message = json_encode($message);
print($message);
die();
break;
}

140
actions/program-editor.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
session_start();
$db = new SQLite3('../db-test/test.db');
$action = $_GET["action"];
$meta = $_GET["meta"];
$bridgeName = $_SESSION["meta"];
$programID = json_decode(urldecode($_SESSION["meta"]), true);
$plug = json_decode(file_get_contents("../settings.json"), true)["plug"];
switch ($action) {
case "add":
$meta = json_decode($meta, true);
$bridges = $meta["bridges"];
$programID = $meta["programID"];
$programDescription = $meta["programDescription"];
if(empty($bridges) || empty($programID)) {
$message[0] = false;
$message[1] = "Es wurden keine Änderungen gespeichert.";
$message = json_encode($message);
print($message);
die();
}
$result = $db->query("SELECT COUNT(*) as anzahl FROM program_id_bits WHERE
position = 0 AND value = '" . $programID[0] . "' AND
position = 1 AND value = '" . $programID[1] . "' AND
position = 2 AND value = '" . $programID[2] . "' AND
position = 3 AND value = '" . $programID[3] . "' AND
position = 4 AND value = '" . $programID[4] . "'
;");
$row = $result->fetchArray(SQLITE3_ASSOC);
$number = $row['anzahl'];
if($number > 0) {
$message[0] = false;
$message[1] = "Diese Programm-ID existiert unter dem aktuell gewählten Stecker bereits. Bitte anderen Namen wählen oder den Stecker wechseln.";
$message = json_encode($message);
print($message);
die();
}
$db->exec(" INSERT INTO programs (plug_id, program_description) VALUES ('" . $plug . "', '" . $programDescription . "'); ");
$program_id = $db->lastInsertRowID();
foreach($bridges as $bridge) {
$db->exec(" INSERT INTO program_bridge_relation (program_id, bridge_id, plug_id) VALUES ('" . $program_id . "', '" . $bridge . "', '" . $plug . "'); ");
}
for($i=0; $i<=4; $i++) {
$db->exec(" INSERT INTO program_id_bits (value, position, program_id) VALUES ('" . $programID[$i] . "', '" . $i . "', '" . $program_id . "'); ");
}
$message[0] = true;
$message[1] = 'Das Programm wurde als <div class="label" style="width: 160px; display: inline-block; float: unset; height: unset;margin-bottom: -10px;">
<span style="margin: unset;"><span class="label-char">' . $programID[0] . '</span><span class="label-char">' . $programID[1] . '</span><span class="label-char">' . $programID[2] . '</span><span class="label-char">' . $programID[3] . '</span><span class="label-char">' . $programID[4] . '</span>
</span>
</div> gespeichert!';
$message = json_encode($message);
print($message);
die();
break;
case "edit":
$meta = json_decode(urldecode($meta),true);
$bridges = $meta["bridges"];
$programDescription = $meta["programDescription"];
if(empty($bridges) || empty($programDescription)) {
$message[0] = false;
$message[1] = "Es wurden keine Änderungen gespeichert.";
$message = json_encode($message);
print($message);
die();
}
$db->exec(" UPDATE programs set program_description = '" . $programDescription . "' WHERE id = '" . $programID . "' AND plug_id = '" . $plug . "'; ");
$db->exec(" DELETE FROM program_bridge_relation WHERE program_id = '" . $programID . "' and plug_id = '" . $plug . "'; ");
foreach($bridges as $bridge) {
$db->exec(" INSERT INTO program_bridge_relation (program_id, bridge_id, plug_id) VALUES ('" . $programID . "', '" . $bridge . "', '" . $plug . "'); ");
}
$message[0] = true;
$message[1] = 'Die Änderungen an <div class="label" style="width: 160px; display: inline-block; float: unset; height: unset;margin-bottom: -10px;"><span style="margin: unset;">';
$program_identifiers = $db->query("SELECT * FROM program_id_bits WHERE program_id = '" . $programID . "';");
while ($program_identifier = $program_identifiers->fetchArray(SQLITE3_ASSOC)) {
$message[1] .= '<span class="label-char">' . $program_identifier["value"] . '</span>';
}
$message[1] .= '</span></div> wurden gespeichert!';
$message = json_encode($message);
print($message);
die();
break;
case "remove":
$programID = $meta;
// Überprüfen, ob die Struktur korrekt ist
if (!isset($meta)) {
$message[0] = false;
$message[1] = "Es gab einen Fehler bei der Datenübermittlung.";
$message = json_encode($message);
print($message);
die();
}
// Hier wichtig! Beim Löschvorgang muss die referenzielle Integrität gewahrt werden! Erst die Fremdschlüssel löschen, dann die Tabelle mit dem Primärschlüssel!
$db->exec(" DELETE FROM program_bridge_relation WHERE program_id = '" . $meta . "' and plug_id = '" . $plug . "'; ");
$db->exec(" DELETE FROM program_id_bits WHERE program_id = '" . $meta . "'; ");
$db->exec(" DELETE FROM programs WHERE id = '" . $meta . "' and plug_id = '" . $plug . "'; ");
$message[0] = true;
$message[1] = "Das Programm wurde nun gelöscht.";
$message = json_encode($message);
print ($message);
die();
break;
}

16
actions/save-settings.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
$type = $_GET["type"];
$value = $_GET["value"];
$settings = json_decode(file_get_contents("../settings.json"), true);
$settings[$type] = $value;
file_put_contents("../temp.json",json_encode($settings, true));
print("true");
?>

View File

@@ -0,0 +1,5 @@
<?php
print("true");
?>

32
alt/index.php Normal file
View File

@@ -0,0 +1,32 @@
<html>
<head>
<script src="/vendor/jquery.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body onload="pageload(), checkConnection(), applySettings('plug')" onhashchange="pageload()">
<div class="main">
<div class="navigation">
<div class="navigation-header" onclick="secondaryNavigation('open','plug-select')">
Steckerauswahl
<div>Wird geladen...</div>
</div>
<div id="index" id="start" onclick="window.location.href='#index'; pageload();"><img src="/vendor/icons/electric-plug-icon.svg" />Federleiste prüfen</div>
<div id="cable-check" onclick="window.location.href='#cable-check'; pageload();"><img src="/vendor/icons/fiber-cable-icon.svg" />Spurkabel prüfen</div>
<div id="index" class="navigation-footer">
<div class="connection-indicator unknown"></div>Verbinde...<br>
<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />
</div>
</div>
<div class="navigation secondary" id="navigation-secondary">
</div>
<div id="message-box" style="display: none;position: absolute; bottom: 10px; left: 260px; right: 10px; background-color:grey;height:auto;padding:20px;box-sizing:border-box;transform: translateY(calc(100% + 5px));transition-duration:.5s; border-radius: 10px;background-color: #d2d2d2;z-index: 502;"></div>
<div class="content" id="content">
</div>
</div>
</body>
</html>

24
alt/pagecontent/index.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
session_start();
header('Content-Type: text/plain');
header('Cache-Control: no-cache');
ob_implicit_flush(true);
ob_end_flush();
$view = $_GET["view"];
$meta = $_GET["meta"];
$_SESSION["meta"]=$meta;
?>
<div class="content-header">Federleiste prüfen</div>
<hr />
<p>Automatische Erkennung</p>
<i>Verbinden Sie den zu messenden Stecker mit dem Gerät. Vermeiden Sie Bewegungen während der Messung.</i>
<div class="save-button" onclick="window.location.href='#start';$('.save-button').html('<img src=\'/vendor/icons/load.gif\' \>Initialisieren...'); "><img src="/vendor/icons/play.svg" \>Jetzt prüfen</div>

339
alt/pagecontent/start.php Normal file
View File

@@ -0,0 +1,339 @@
<?php
session_start();
header('Content-Type: text/plain');
header('Cache-Control: no-cache');
ob_implicit_flush(true);
ob_end_flush();
$translationMap = [
"A" => "30",
"B" => "20",
"C" => "10",
"D" => "39",
"E" => "29",
"F" => "19",
"G" => "38",
"H" => "28",
"I" => "18",
"J" => "37",
"K" => "27",
"L" => "17",
"M" => "36",
"N" => "26",
"O" => "16",
"P" => "35",
"Q" => "25",
"R" => "15",
"S" => "34",
"T" => "24",
"U" => "14",
"V" => "33",
"W" => "23",
"X" => "13",
"Y" => "32",
"Z" => "22",
"AA" => "12",
"AB" => "31",
"AC" => "21",
"AD" => "11"
];
function translateArray($inputArray, $translationMap) {
return array_map(function($subArray) use ($translationMap) {
return array_map(function($item) use ($translationMap) {
return $translationMap[$item] ?? $item; // Falls kein Mapping existiert, bleibt das Original erhalten
}, $subArray);
}, $inputArray);
}
flush();
?>
<style>
div.check-loading-screen {
width: 250px;
height: auto;
background-color: #ebebeb;
margin: 0 auto;
text-align: left;
padding: 5px;
border-radius: 25px;
}
div.check-loading-screen div {
padding: 5px;
margin: 10px;
line-height: 30px;
transition-duration: .4s;
cursor: pointer;
border-radius: 10px;
}
div.check-loading-screen div img {
width: 30px;
margin-right: 20px;
vertical-align: middle;
filter: contrast(0.3);
line-height: 30px;
cursor: pointer;
display: inline-block;
}
</style>
<div class="check-loading-screen" id="_1">
<div>
<img src="/vendor/icons/load.gif">Initialisieren
</div>
<div>
<img src="/vendor/icons/clock-rotate-right.svg">Prüfung 1/2
</div>
<div>
<img src="/vendor/icons/clock-rotate-right.svg">Prüfung 2/2
</div>
</div>
<?php
flush();
// To Run the Python program
$command = escapeshellcmd('python3 /var/www/html/alt/python/plug-check.py');
// Use shell_exec to execute the command and capture the output
$output0 = shell_exec($command);
?>
<style>
#_1 {
display: none;
}
</style>
<div class="check-loading-screen" id="_2">
<div>
<img src="/vendor/icons/check.svg">Initialisieren
</div>
<div>
<img src="/vendor/icons/load.gif">Prüfung 1/2
</div>
<div>
<img src="/vendor/icons/clock-rotate-right.svg">Prüfung 2/2
</div>
</div>
<?php
$command = escapeshellcmd('python3 /var/www/html/alt/python/plug-check.py');
// Use shell_exec to execute the command and capture the output
$bridges1 = shell_exec($command);
$number_bridges1 = count(json_decode($bridges1, true));
?>
<style>
#_2 {
display: none;
}
</style>
<div class="check-loading-screen" id="_3">
<div>
<img src="/vendor/icons/check.svg">Initialisieren
</div>
<div>
<img src="/vendor/icons/check.svg">Prüfung 1/2 (<?php echo $number_bridges1; ?>x)
</div>
<div>
<img src="/vendor/icons/load.gif">Prüfung 2/2
</div>
</div>
<?php
$command = escapeshellcmd('python3 /var/www/html/alt/python/plug-check.py');
// Use shell_exec to execute the command and capture the output
$bridges2 = shell_exec($command);
?>
<style>
#_3 {
display: none;
}
</style>
<?php
$bridges1 = $bridges2;
if($bridges1 == $bridges2) {
$output = translateArray(json_decode($bridges1, true), $translationMap);
if($output == "error") {
die("Fehler");
}
//$output = [ ["16", "30"], ["30", "16"]];
$array = $output;
$doubles = []; // Array zur Verfolgung von Doppelungen
$errors = []; // Array für fehlende Doppelungen
// Schritt 1: Prüfung auf Doppelungen
foreach ($array as $pair) {
// Sortiere die Paare, um `[23,14]` und `[14,23]` gleich zu behandeln
sort($pair);
$key = implode("-", $pair);
if (isset($doubles[$key])) {
$doubles[$key]++; // Verbindung erneut gefunden
} else {
$doubles[$key] = 1; // Erstmaliges Auftreten
}
}
// Schritt 2: Bereinigung der doppelten Einträge
$unique = []; // Array für eindeutige Verbindungen
$seen = []; // Array zur Verfolgung bereits bearbeiteter Verbindungen
foreach ($array as $pair) {
// Sortiere das Paar
sort($pair);
$key = implode("-", $pair);
// Füge nur ein Exemplar hinzu
if (!isset($seen[$key])) {
$unique[] = $pair;
$seen[$key] = true; // Markiere als verarbeitet
}
}
$cleaned_output = json_encode($unique);
$url = '../stecker.php?data=' . urlencode($cleaned_output); // Kodiert es für die URL
?>
<div class="content-header">Ergebnis</div>
<hr />
<div class="toggle-switch">
<div align="right"><p>Gesamtergebnis</p></div>
<div><label class="switch"><input type="checkbox" onchange='$("#op-1").toggle(this.unchecked);$("#op-2").toggle(this.checked);'><span class="slider"></span></label></div>
<div align="left"><p>Einzelbrücken</p></div>
</div>
<div id="op-1">
<img src="<?php echo $url; ?>" />
</div>
<div id="op-2" style="display: none;">
<?php
foreach($unique as $unique_element) {
$cleaned_output = "[" . json_encode($unique_element) . "]";
$url = 'stecker.php?data=' . urlencode($cleaned_output); // Kodiert es für die URL
?>
<img style="margin-bottom: 5px;" src="<?php echo $url; ?>" />
<?php
}
?>
</div>
<hr />
<?php
}
else {
$output = "error";
/*
$url1 = 'stecker.php?data=' . urlencode(translateArray($bridges1, $translationMap)); // Kodiert es für die URL
$url2 = 'stecker.php?data=' . urlencode(translateArray($bridges2, $translationMap)); // Kodiert es für die URL
*/
?>
<style>
#_3 {
display: none;
}
</style>
<div class="check-loading-screen" id="_4">
<div>
<img src="/vendor/icons/check.svg">Initialisieren
</div>
<div>
<img src="/vendor/icons/xmark.svg">Prüfung 1/2
</div>
<div>
<img src="/vendor/icons/xmark.svg">Prüfung 2/2
</div>
</div>
<p>Es gab einen Fehler bei der Messung!</br>Messung 1 und Messung 2 lieferten keine übereinstimmenden Ergebnisse.<br>Bitte prüfen Sie, ob alle Steckverbindungen fest sitzen und vermeiden Sie Bewegungen, welche die Messung beeinträchtigen können.</br>Stellen Sie sicher, dass keine der Verbindungen gegen Erde kurzgeschlossen ist, da dies die Messung beeinträchtigt.<br>Erwägen Sie, eine erneute Messung zurchzuführen.</p>
<br>
<p>Ergebnis Messung 1:</p>
<i><?php echo $bridges1; ?><i>
<img src="<?php echo $url1; ?>" />
<p>Ergebnis Messung 2:</p>
<i><?php echo $bridges2; ?><i>
<img src="<?php echo $url2; ?>" />
<div class="save-button" onclick="window.location.href='#index'; pageload();$('.save-button').html('<img src=\'load.gif\' \>Bitte warten...');"><img src="/vendor/icons/nav-arrow-left.svg" \>Zurück</div>
<?php
}
?>

144
alt/python/plug-check.py Normal file
View File

@@ -0,0 +1,144 @@
import smbus
import time
import json
# Adressen der beiden MCP23017 Chips
AusgangRechts = 0x20 # Ausgang rechte Hälfte
AusgangLinks = 0x21 # Ausgang linke Hälfte
EingangRechts = 0x24 # Eingang rechte Hälfte
EingangLinks = 0x23 # Eingang linke Hälfte
# Register-Adressen für den MCP23017
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
RechtsA_ausgang = ["Z", "AA", "W", "AB", "AC", "AD", "AE", "AF"]
RechtsB_ausgang = ["Q", "U", "V", "T", "S", "R", "Y", "X"]
RechtsA_eingang = ["R", "T", "V", "X", "Z", "AB", "AD", "AE"]
RechtsB_eingang = ["Q", "S", "W", "U", "AA", "Y", "AC", "AF"]
LinksA_ausgang = ["C", "D", "E", "J", "F", "G", "H", "I"]
LinksB_ausgang = ["A", "B", "P", "O", "K", "L", "M", "N"]
LinksA_eingang = ["B", "D", "F", "H", "J", "K", "M", "I"]
LinksB_eingang = ["A", "C", "E", "G", "L", "N", "O", "P"]
array = []
def default():
adressen = [AusgangRechts, AusgangLinks, EingangRechts, EingangLinks]
# Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
for adresse in adressen:
bus.write_byte_data(adresse, register, 0x00)
# Konfiguriere alle Pins auf Chip 1 als Ausgang (A und B)
def configure_chip1_as_output():
bus.write_byte_data(AusgangRechts, IODIRA, 0x00) # Setze alle Pins von A als Ausgang
bus.write_byte_data(AusgangRechts, IODIRB, 0x00) # Setze alle Pins von B als Ausgang
bus.write_byte_data(AusgangLinks, IODIRA, 0x00) # Setze alle Pins von A als Ausgang
bus.write_byte_data(AusgangLinks, IODIRB, 0x00) # Setze alle Pins von B als Ausgang
# Konfiguriere alle Pins auf Chip 2 als Eingang (A und B)
def configure_chip2_as_input():
bus.write_byte_data(EingangRechts, IODIRA, 0xFF) # Setze alle Pins von A als Eingang
bus.write_byte_data(EingangRechts, IODIRB, 0xFF) # Setze alle Pins von B als Eingang
bus.write_byte_data(EingangLinks, IODIRA, 0xFF) # Setze alle Pins von A als Eingang
bus.write_byte_data(EingangLinks, IODIRB, 0xFF) # Setze alle Pins von B als Eingang
# Hauptprogramm
def main():
default()
time.sleep(0.5)
configure_chip1_as_output()
configure_chip2_as_input()
# Teste alle Pins auf Chip 1 (A0-A7, B0-B7)
for pin in range(32): # 0 bis 31
#print(f"Setze Pin {pin} auf HIGH auf Chip 1")
bus.write_byte_data(AusgangRechts, GPIOA, 0x00)
bus.write_byte_data(AusgangRechts, GPIOB, 0x00)
bus.write_byte_data(AusgangLinks, GPIOA, 0x00)
bus.write_byte_data(AusgangLinks, GPIOB, 0x00)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-A7
bus.write_byte_data(AusgangRechts, GPIOA, 1 << pin)
aktuellAn = RechtsA_ausgang[pin]
else: # Pins B0-B7
bus.write_byte_data(AusgangRechts, GPIOB, 1 << (pin - 8))
aktuellAn = RechtsB_ausgang[pin - 8]
else:
# Setze den gewählten Pin auf HIGH
if pin < 24: # Pins A0-A7
bus.write_byte_data(AusgangLinks, GPIOA, 1 << pin - 16)
aktuellAn = LinksA_ausgang[pin - 16]
else: # Pins B0-B7
bus.write_byte_data(AusgangLinks, GPIOB, 1 << (pin - 24))
aktuellAn = LinksB_ausgang[pin - 24]
#print("====================" + aktuellAn + "==========================")
time.sleep(0.02) # Kurze Pause, damit die Änderung sichtbar wird
Wert_Rechts_A = bus.read_byte_data(EingangRechts, GPIOA)
Wert_Links_A = bus.read_byte_data(EingangLinks, GPIOA)
Wert_Rechts_B = bus.read_byte_data(EingangRechts, GPIOB)
Wert_Links_B = bus.read_byte_data(EingangLinks, GPIOB)
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
Bit_Rechts_A = bool(Wert_Rechts_A & bitmaske) # Isoliere das entsprechende Bit
Bit_Links_A = bool(Wert_Links_A & bitmaske) # Isoliere das entsprechende Bit
Bit_Rechts_B = bool(Wert_Rechts_B & bitmaske) # Isoliere das entsprechende Bit
Bit_Links_B = bool(Wert_Links_B & bitmaske) # Isoliere das entsprechende Bit
if Bit_Rechts_A == True:
if aktuellAn != RechtsA_eingang[j]:
array.append([aktuellAn ,RechtsA_eingang[j]])
#print("Gefunden: " + RechtsA_eingang[j])
if Bit_Links_A == True:
if aktuellAn != LinksA_eingang[j]:
array.append([aktuellAn ,LinksA_eingang[j]])
#print("Gefunden: " + LinksA_eingang[j])
if Bit_Rechts_B == True:
if aktuellAn != RechtsB_eingang[j]:
array.append([aktuellAn ,RechtsB_eingang[j]])
#print("Gefunden: " + RechtsB_eingang[j])
if Bit_Links_B == True:
if aktuellAn != LinksB_eingang[j]:
array.append([aktuellAn ,LinksB_eingang[j]])
#print("Gefunden: " + LinksB_eingang[j])
json_output = json.dumps(array)
print(json_output)
if __name__ == "__main__":
main()

117
alt/script.js Normal file
View File

@@ -0,0 +1,117 @@
function pageload(view, meta) { // Funktion zum Laden des Inhalts
secondaryNavigation("close"); // Schließe das Stecker-Menü
page = window.location.hash.substr(1); // Hole die aufzurufende Seite über den URL-Hash (#xy)
$.ajax({
url: "pagecontent/" + page + ".php?view=" + view + "&meta=" + meta, // Pfad der zu ladenden Seite aufbauen
success: function(result){ // Abfrage erfolgreich
$("#content").html(result);
$("div.navigation>div").removeClass("active"); // Alle Buttons inaktiv
$("#" + page).addClass("active"); // Button mit Klasse "page" wird aktiv geschaltet (im Menü)
}});
}
function checkConnection(last) { // Verbindunsstatus überwachen // Last ist der letzte gemessene Netzwerkzustand
$.ajax({
url: "actions/connection-test.php?a=" + Date.now(), // verändere idr URL leicht, damit der Server nicht cached
timeout: 5000, // maximale Wartezeit sind 5 Sekunden; alles darüber wird als zu unzuverlässig angesehen
error: function(jqXHR, textStatus, errorThrown) { // Wenn ein Fehler bei der Anfrage auftritt (z.B. Netzwerkprobleme)
$(".navigation-footer").html('<div class="connection-indicator false"></div>Keine Verbindung<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
console.error("Fehler bei der Anfrage:", textStatus, errorThrown);
if (last != 1) { // letzter Zustand war "online" // Vermeiden von ständigen Nachrichten
message("Keine Verbindung"); // Werfe eine Nachricht
}
setTimeout(function() { // nächste Prüfung in 5 Sekunden
checkConnection(1); // der Status offline wird mit übertragen
}, 5000);
},
success: function(data) { // Verbindung iO
setTimeout(function() { // nächste Prüfung in 5 Sekunden
checkConnection();
}, 5000);
if (data == 'true') { // der Rückgabewert ist true, alles IO
$(".navigation-footer").html('<div class="connection-indicator true"></div>Verbunden<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
if (last == 1) { // war der letzte Status noch "offline", wird der Nutzer benachrichtigt
message("Verbindung wiederhergestellt!");
}
} else { // außergewöhnlicher Fehler, aber möglich
message("Keine Verbindung"); // Verbindung dennoch unbrauchbar, wenn keine Daten geladen werden
$(".navigation-footer").html('<div class="connection-indicator false"></div>Keine Verbindung<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
}
}
});
}
function secondaryNavigation(animation, content) {
if (animation == "open") {
$.get("pagecontent/plug-select.php", {
},
function(data, status) {
if (status == "success") {
if (data != 'false') {
$("#navigation-secondary").css("left", "250px");
$(".navigation-header").attr("onclick", "secondaryNavigation('close','plug-select')");
setTimeout(function() {
$("#navigation-secondary").html(data);
console.log(data);
}, 200);
} else {
window.location.href = "#";
alert("Die Inhalte konnten nicht abgerufen werden. Vergewissern Sie sich, dass Sie mit dem WLAN-Netzwerk verbunden sind und laden Sie bei Bedarf die Seite neu.");
}
}
});
} else {
$("#navigation-secondary").css("left", "-250px");
$(".navigation-header").attr("onclick", "secondaryNavigation('open','plug-select')");
}
}
function message(content) {
$("#message-box").html(content);
$("#message-box").css("display", "block");
setTimeout(function() {
$("#message-box").css("transform", "translateY(0)");
}, 50);
setTimeout(function() {
setTimeout(function() {
$("#message-box").css("display", "none");
}, 500);
$("#message-box").css("transform", "translateY(calc(100% + 5px))");
}, 3000);
}
$(window).bind('beforeunload', function() {
$("#warning").css("display", "block");
return 'Are you sure you want to leave?';
});

120
alt/stecker.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
// Setze den Content-Type-Header für SVG
header('Content-Type: image/svg+xml');
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="100">
<defs>
<style>
@font-face {
font-family: LexendDecaBold;
src: url(http://192.168.37.103/vendor/fonts/LexendDeca-Bold.ttf);
}
* {
font-family: LexendDecaBold;
font-size: 14px;
}
</style>
</defs>
<text x="30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">30</text>
<text x="-30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">10</text>
<text x="30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">31</text>
<text x="-30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">11</text>
<path d="M500 0 L50 0" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M50 0 L0 25" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M0 25 L0 75" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M0 75 L50 100" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M50 100 L500 100" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M500 100 L500 0" style="stroke:black;stroke-width:5; fill:none;" />
<rect width="35" height="35" x="452" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="470" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="35" height="35" x="13" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="30" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="380" height="80" x="60" y="10" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<?php
$pin_coordinates = [
10 => [70, 68],
20 => [70, 44],
30 => [70, 20],
11 => [405, 68],
12 => [370, 68],
13 => [335, 68],
14 => [300, 68],
15 => [265, 68],
16 => [230, 68],
17 => [195, 68],
18 => [160, 68],
19 => [125, 68],
21 => [405, 44],
22 => [370, 44],
23 => [335, 44],
24 => [300, 44],
25 => [265, 44],
26 => [220, 44],
27 => [185, 44],
28 => [150, 44],
29 => [115, 44],
31 => [405, 20],
32 => [370, 20],
33 => [335, 20],
34 => [300, 20],
35 => [265, 20],
36 => [230, 20],
37 => [195, 20],
38 => [160, 20],
39 => [125, 20],
];
foreach ($pin_coordinates as $coordinates) {
echo '<rect width="25" height="10" x="' . $coordinates[0] . '" y="' . $coordinates[1] . '" rx="1" ry="1" style="stroke:black;stroke-width:3;fill:none" />';
}
if (empty($_GET['data'])) {
print "</svg>";
die();
}
$inputArray = json_decode($_GET['data'], true); // JSON in ein PHP-Array umwandeln
// Neues Array mit übersetzten Koordinaten
$translatedArray = array_map(function ($pair) use ($pin_coordinates) {
return [
$pin_coordinates[$pair[0]], // Übersetze den ersten Wert
$pin_coordinates[$pair[1]], // Übersetze den zweiten Wert
];
}, $inputArray);
$colors = ["#1a5fb4", "#26a269", "#e5a50a", "#c64600", "#a51d2d", "#613583", "#63452c", "#3d3846"];
// Schleife durch das übersetzte Array
$colorNumber = 0;
foreach ($translatedArray as $line) {
$point1 = $line[0]; // Erster Punkt [x, y]
$point2 = $line[1]; // Zweiter Punkt [x, y]
$point1x = $point1[0] + 10;
$point1y = $point1[1] + 5;
$point2x = $point2[0] + 10;
$point2y = $point2[1] + 5;
// Ausgabe eines Pfades (Linie zwischen zwei Punkten)
echo '<path d="M' . $point1x . " " . $point1y . " L" . $point2x . " " . $point2y . '" style="stroke:' . $colors[$colorNumber] . ';stroke-width:3; fill:none;" />';
$colorNumber++;
if ($colorNumber > 7) {
$colorNumber = 0;
}
}
?>
</svg>

241
alt/style.css Normal file
View File

@@ -0,0 +1,241 @@
@font-face {
font-family: LexendDecaBold;
src: url(/vendor/fonts/LexendDeca-Bold.ttf);
}
@font-face {
font-family: LexendDecaMedium;
src: url(/vendor/fonts/LexendDeca-Medium.ttf);
}
body {
height: 100%;
width: 100%;
padding: 20px;
margin: 0;
box-sizing: border-box;
font-family: LexendDecaBold;
font-weight: light;
color: #2e3436;
}
div.main {
width: 100%;
max-width: 900px;
height: auto;
border-radius: 15px;
overflow: hidden;
margin: 0 auto;
border: 5px solid #c3c3c3;
position: relative;
}
div.navigation {
width: 250px;
height: 600px;
background-color: #ebebeb;
float: left;
display: inline-block;
position: relative;
z-index: 501;
}
div.navigation.secondary {
position: absolute;
left: -250px;
top: 0;
overflow-y: auto;
z-index: 500;
transition-duration: .6s;
width: 500px;
backdrop-filter: blur(10px);
background-color: #ebebeb8c;
}
div.navigation.secondary img {
width: 100px !important;
}
div.navigation>div, div.save-button {
padding: 5px;
margin: 10px;
line-height: 30px;
transition-duration: .4s;
cursor: pointer;
border-radius: 10px;
}
div.navigation>div:hover,div.navigation>div.active,div.navigation-footer,div.navigation-header,div.save-button:hover {
background-color: #d2d2d2;
}
div.navigation div.navigation-header {
text-align: center;
}
div.navigation div.navigation-header>div {
background-color: #fff;
border-radius: 0 0 7px 7px;
color: grey;
line-height: 20px;
font-size: 12px;
}
div.navigation>div>img:not(.infrago-logo),div.content div.save-button>img {
width: 30px;
max-height: 30px;
margin-right:20px;
vertical-align:middle;
filter: contrast(0.3);
}
div.navigation>div>img.infrago-logo {
width: 100%;
display:block;
margin-top: 15px;
}
div.content {
width: calc(100% - 250px);
float: right;
display: inline-block;
padding: 15px 20px;
height: 600px;
box-sizing: border-box;
overflow-y: auto;
text-align: center;
}
div.content div.content-header {
text-align: center;
line-height: 30px;
padding: 0;
margin-bottom: 20px;
display: block;
font-size: 2em;
margin-block-start: 0.67em;
margin-block-end: 0.67em;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-weight: bold;
unicode-bidi: isolate;
}
div.content img {
width: 100%;
margin: 0 auto;
display: block;
}
div.navigation-footer {
position: absolute;
bottom:0;
width: calc(100% - 40px);
padding: 10px !important;
cursor: auto !important;
}
div.connection-indicator {
height: 15px;
width: 15px;
border-radius: 100%;
float: left;
vertical-align:middle;
margin: 7.5px;
}
div.connection-indicator.true {
background-color: #2ec27e;
}
div.connection-indicator.unknown {
background-color: #f6d32d;
}
div.connection-indicator.false {
background-color: #ed333b;
}
div.save-button img {
display: inline-block;
}
div.save-button {
background-color: #ebebeb
}
::-webkit-scrollbar {
width: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: #ebebeb;
}
/* Handle */
::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover {
background: #d2d2d2;
}
div.toggle-switch {
width: 100%;
height: 50px;
margin-bottom: 10px;
display: flex;
}
div.toggle-switch div {
display: inline-block;
}
div.toggle-switch div:nth-child(1), div.toggle-switch div:nth-child(3) {
width: 39%;
}
div.toggle-switch div:nth-child(2) {
width: 20%;
padding: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 100%;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
border-radius: 25px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 100%;
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
table.simple-devider th,table.simple-devider td {
padding: 15px;
}
table.simple-devider * {
margin: 0;
vertical-align: top;
}

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="DB_InfraGO_logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 316.5 70"><desc>Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</desc>
<metadata><?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 9.1-c001 79.a8d4753, 2023/03/23-08:56:37 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<dc:creator>
<rdf:Seq>
<rdf:li>Deutsche Bahn</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</dc:description>
<Iptc4xmpCore:AltTextAccessibility>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</Iptc4xmpCore:AltTextAccessibility>
<Iptc4xmpCore:ExtDescrAccessibility>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</Iptc4xmpCore:ExtDescrAccessibility>
<xmp:MetadataDate>2023-12-20T16:18:38+01:00</xmp:MetadataDate>
<xmpMM:InstanceID>xmp.iid:f4896118-a5fc-4ec6-884b-265e34b1ca9a</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:8feca862-1f36-49d9-a283-4022945ab9e2</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:8feca862-1f36-49d9-a283-4022945ab9e2</xmpMM:OriginalDocumentID>
<xmpMM:History>
<rdf:Seq>
<rdf:li>
<rdf:Description>
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:8feca862-1f36-49d9-a283-4022945ab9e2</stEvt:instanceID>
<stEvt:when>2023-12-20T16:13:32+01:00</stEvt:when>
<stEvt:softwareAgent>Adobe Bridge 2024</stEvt:softwareAgent>
<stEvt:changed>/metadata</stEvt:changed>
</rdf:Description>
</rdf:li>
<rdf:li>
<rdf:Description>
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:f4896118-a5fc-4ec6-884b-265e34b1ca9a</stEvt:instanceID>
<stEvt:when>2023-12-20T16:18:38+01:00</stEvt:when>
<stEvt:softwareAgent>Adobe Bridge 2024</stEvt:softwareAgent>
<stEvt:changed>/metadata</stEvt:changed>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
<photoshop:CaptionWriter>Deutsche Bahn</photoshop:CaptionWriter>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?></metadata>
<g id="DB_Logo"><path d="M90,0H10C4.6,0,0,4.3,0,10v50c0,5.5,4.6,10,10,10h80c5.5,0,10-4.5,10-10.1V10c0-5.7-4.6-10-10-10ZM92.7,59.9c0,1.6-1.2,2.9-2.7,2.9H10c-1.6,0-2.7-1.3-2.7-2.9V10c0-1.6,1.2-2.9,2.7-2.9h80c1.6,0,2.7,1.3,2.7,2.9v49.9Z" style="fill:#ec0016; stroke-width:0px;"/><path d="M30.6,57.9H13.8V12.1h16.8c11.9,0,18.4,7.4,18.4,22.7,0,13.2-4.5,23-18.4,23.1ZM37.9,35.7c0-9.2-1-16.4-10.9-16.4h-2.2v31.2h3.9c5.8,0,9.2-4.7,9.2-14.8Z" style="fill:#ec0016; stroke-width:0px;"/><path d="M78,33.9c3.2-.9,8-4.6,8-10.7,0-1-.2-11.2-13.1-11.2h-19.1v45.8h16.2c4.3,0,17.7,0,17.7-12.9,0-3.1-1.4-9.1-9.7-11ZM64.7,19.2h3.5c4.9,0,6.9,1.7,6.9,5.5,0,3-2.2,5.5-6,5.5h-4.4v-11ZM69.2,50.5h-4.5v-11.8h4.8c5.7,0,7.1,3.3,7.1,5.9,0,5.9-5.6,5.9-7.4,5.9Z" style="fill:#ec0016; stroke-width:0px;"/></g><g id="InfraGo"><path d="M125,19.1h10v38.9h-10V19.1Z" style="stroke-width:0px;"/><path d="M148.3,28.5h2.2v5.2c3.1-3.5,6.2-5.1,9.9-5.1,4.9,0,7.7,3.3,7.7,9v20.3h-9.3v-18.2c0-2.4-1.1-3.5-3.4-3.5-1.6,0-2.8.4-4.9,1.5v20.2h-9.3v-27.6c3.7-.1,6-.7,7.1-1.8Z" style="stroke-width:0px;"/><path d="M175,37.2h-3.6v-6.6h3.6v-2c0-6.3,3.8-10.2,11.3-10.2,1.9,0,3.3.2,4.6.3v6.4c-.7-.1-1.3-.2-2-.2-3.2,0-4.5,1.2-4.5,4v1.7h4.8v6.6h-4.8v20.7h-9.4v-20.7Z" style="stroke-width:0px;"/><path d="M199.3,28.6h2.2v5.7c2.4-3.8,4.9-5.6,7.6-5.6.3,0,.5.1.7.1v8.3l-.6.1c-2.5.2-5.3.8-7.7,1.9v18.9h-9.3v-27.6c3.6-.1,5.9-.7,7.1-1.8Z" style="stroke-width:0px;"/><path d="M226.7,54.2c-2.1,3-4.9,4.5-8.4,4.5-5.3,0-8.4-3-8.4-8.6,0-8.1,7-8.6,16.3-9v-1.5c0-3-1.4-4.1-4.2-4.1s-5.9.5-9.8,1.2v-7.2c4.1-.9,7.2-1.3,10.8-1.3,8.9,0,12.5,3.3,12.5,11.2v11.7c0,3.2.3,4.3,1.2,5v1.6h-9l-1-3.5ZM222.1,52.6c1.5,0,2.9-.5,4.2-1.6v-5.7c-5.3.2-7.2.5-7.2,4-.1,2.3.8,3.3,3,3.3Z" style="stroke-width:0px;"/><path d="M240.1,39v-.2c0-13.6,7.2-20.3,20.4-20.3,4.3,0,7.6.5,11.6,1.7v7.9c-3.9-1.3-7.3-1.9-11.1-1.9-7.9,0-10,4-10,12.6v.3c0,8.8,2.4,12.2,9.4,12.2,1.6,0,2.7-.2,4.2-.6v-6.5h-6.2v-7.4h15.6v19c-4.8,1.8-9,2.8-13.9,2.8-13.6,0-20-5-20-19.6Z" style="stroke-width:0px;"/><path d="M278.7,38.6v-.2c0-12.5,7.1-20,18.9-20,11.9,0,18.9,7.6,18.9,20.1v.2c0,12.5-7,19.9-18.9,19.9-12,0-18.9-7.5-18.9-20ZM305.6,38.7v-.3c0-9-2.4-12.7-8-12.7-5.7,0-8,3.7-8,12.8v.3c0,8.8,2.4,12.4,8,12.4,5.7,0,8-3.6,8-12.5Z" style="stroke-width:0px;"/></g></svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
alt/vendor/fonts/LexendDeca-Bold.ttf vendored Normal file

Binary file not shown.

BIN
alt/vendor/fonts/LexendDeca-Medium.ttf vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 99.15 123.38" style="enable-background:new 0 0 99.15 123.38" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-miterlimit:2.6131;}</style><g><path class="st0" d="M5.44,40.34h15.84V8.47c0-4.52,3.7-8.22,8.22-8.22l0,0c4.52,0,8.22,3.7,8.22,8.22v31.87h24.13V8.47 c0-4.52,3.7-8.22,8.22-8.22l0,0c4.52,0,8.22,3.7,8.22,8.22v31.87h15.42c2.86,0,5.19,2.34,5.19,5.19v6.02 c0,2.86-2.34,5.19-5.19,5.19l-88.27,0c-2.86,0-5.19-2.34-5.19-5.19v-6.02C0.25,42.67,2.59,40.34,5.44,40.34L5.44,40.34z M9.52,56.84l0,12.58c-0.01,22.06,9.79,32.85,32.27,35.56v18.15h16.44v-18.35c22.25-2.14,32.27-15.22,32.27-36.59V56.84H9.52 L9.52,56.84z M28.11,61.42H71.9v6.51H28.11V61.42L28.11,61.42z"/></g></svg>

After

Width:  |  Height:  |  Size: 919 B

1
alt/vendor/icons/fiber-cable-icon.svg vendored Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 85.74 122.88" style="enable-background:new 0 0 85.74 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g><path class="st0" d="M78.27,38.12c3.55-0.53,6.86,1.91,7.4,5.46c0.53,3.55-1.91,6.86-5.46,7.39c-2.5,0.38-4.89-0.73-6.26-2.66 c-3.57,0.93-6.69,2.33-9.49,4.22c-2.45,1.65-4.67,3.69-6.77,6.13c0.49,0.44,0.97,0.92,1.42,1.43c-0.01,0.55-0.42,1.34-0.8,1.79 c-10.22,12.67-19.42,32.79-22.49,54.84c-0.28,2.04-0.51,4.09-0.69,6.15h-5.2c0.19-2.3,0.44-4.58,0.76-6.85 c3-21.46,11.58-41.2,21.41-54.56l-0.14-0.1c-0.43-0.33-0.74-0.75-0.92-1.22l-4.97-2.97c-0.27-0.09-0.54-0.23-0.78-0.4 c-0.1-0.07-0.19-0.15-0.27-0.23l-13.19-7.89C18.9,63.78,11.24,82.96,7.6,102.58c-1.26,6.77-2.05,13.58-2.41,20.3H0 c0.37-7.03,1.18-14.16,2.5-21.24C6.43,80.5,14.85,59.81,29.27,43.69c0.81-0.99,2.23-1.27,3.35-0.6l0.35,0.21 c1.31-2.91,2.24-5.92,2.85-9c0.58-2.95,0.87-5.96,0.89-9.02c-2.35-0.79-4.18-2.83-4.58-5.45c-0.56-3.72,2-7.2,5.73-7.76 c3.72-0.56,7.2,2,7.76,5.73c0.42,2.81-0.94,5.48-3.23,6.86c0.02,3.67-0.29,7.25-0.98,10.73c-0.73,3.71-1.89,7.33-3.54,10.86 l1.64,0.99c3.86-4.82,7.05-10.17,9.59-16c2.5-5.73,4.39-11.93,5.71-18.58c-1.67-1.01-2.9-2.73-3.21-4.82 c-0.56-3.72,2-7.2,5.73-7.76c3.72-0.56,7.2,2,7.76,5.73c0.5,3.34-1.51,6.48-4.62,7.5c-1.41,7.23-3.44,13.98-6.16,20.2 c-2.65,6.07-5.93,11.65-9.88,16.69l0.87,0.52l0.62,0.37c3.14-4.22,6.38-8.37,9.69-12.45c3.6-4.42,7.29-8.77,11.06-13.05 c-0.05-0.22-0.1-0.43-0.13-0.66c-0.55-3.66,1.97-7.08,5.63-7.63c3.66-0.55,7.08,1.97,7.63,5.63c0.55,3.66-1.97,7.08-5.63,7.63 c-1.35,0.2-2.67-0.01-3.82-0.55c-3.48,3.96-6.92,8.03-10.32,12.21c-3.11,3.82-6.17,7.75-9.18,11.78l1.21,0.72l1,0.6 c2.52-2.99,5.21-5.49,8.22-7.51c3.46-2.33,7.31-4.03,11.75-5.13C73.71,40.35,75.7,38.5,78.27,38.12L78.27,38.12z M19.21,122.88 c0.13-1.7,0.29-3.39,0.48-5.07c2.53-21.67,10.92-41.79,20.71-55.55c0.52-0.72,1.53-0.9,2.27-0.38c0.72,0.52,0.9,1.53,0.38,2.27 c-9.51,13.37-17.65,32.94-20.12,54.03c-0.18,1.56-0.33,3.12-0.45,4.69H19.21L19.21,122.88z M78.86,42 c1.41-0.21,2.72,0.76,2.93,2.17c0.21,1.41-0.76,2.72-2.17,2.93c-1.41,0.21-2.72-0.76-2.93-2.17C76.48,43.52,77.45,42.21,78.86,42 L78.86,42z M57.97,4.27c1.41-0.21,2.72,0.76,2.93,2.17c0.21,1.41-0.76,2.72-2.17,2.93c-1.41,0.21-2.72-0.76-2.93-2.17 C55.59,5.8,56.56,4.48,57.97,4.27L57.97,4.27z M38.5,16.26c1.41-0.21,2.72,0.76,2.93,2.17c0.21,1.41-0.76,2.72-2.17,2.93 c-1.41,0.21-2.72-0.76-2.93-2.17C36.12,17.79,37.09,16.48,38.5,16.26L38.5,16.26z M72.79,21.38c1.41-0.21,2.72,0.76,2.93,2.17 c0.21,1.41-0.76,2.72-2.17,2.93c-1.41,0.21-2.72-0.76-2.93-2.17C70.41,22.9,71.38,21.59,72.79,21.38L72.79,21.38z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

1
alt/vendor/icons/play.svg vendored Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M6.90588 4.53682C6.50592 4.2998 6 4.58808 6 5.05299V18.947C6 19.4119 6.50592 19.7002 6.90588 19.4632L18.629 12.5162C19.0211 12.2838 19.0211 11.7162 18.629 11.4838L6.90588 4.53682Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 458 B

2
alt/vendor/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

75
db-test/defaults.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
$data = '10.97.109.112 - - [04/Aug/2025:08:46:17 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B18%2C28%5D%5D%2C%22name%22%3A%2232%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:46:45 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B28%2C38%5D%5D%2C%22name%22%3A%2233%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:47:03 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B39%2C28%5D%5D%2C%22name%22%3A%2234%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:47:22 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B19%2C28%5D%5D%2C%22name%22%3A%2235%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:47:49 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B22%2C29%5D%5D%2C%22name%22%3A%2236%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:48:15 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B13%2C14%5D%5D%2C%22name%22%3A%2237%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:48:31 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B10%2C20%5D%5D%2C%22name%22%3A%2238%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:48:42 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B30%2C20%5D%5D%2C%22name%22%3A%2239%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:49:37 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B30%2C39%5D%5D%2C%22name%22%3A%2290%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:51:15 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B20%2C29%5D%2C%5B39%2C19%5D%5D%2C%22name%22%3A%2293%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:51:50 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B30%2C20%5D%2C%5B35%2C36%5D%5D%2C%22name%22%3A%2245%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:52:22 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B15%2C16%5D%2C%5B20%2C10%5D%5D%2C%22name%22%3A%2255%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:52:52 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B18%2C27%5D%5D%2C%22name%22%3A%2241%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:53:06 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B31%2C32%5D%5D%2C%22name%22%3A%2242%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:53:20 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B32%2C33%5D%5D%2C%22name%22%3A%2243%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:54:00 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B28%2C18%5D%5D%2C%22name%22%3A%2240%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:54:38 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B37%2C36%5D%5D%2C%22name%22%3A%2244%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:55:14 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B34%2C24%5D%5D%2C%22name%22%3A%2246%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:55:33 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B34%2C25%5D%5D%2C%22name%22%3A%2247%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:55:47 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B34%2C23%5D%5D%2C%22name%22%3A%2248%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:56:23 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B22%2C34%5D%5D%2C%22name%22%3A%2249%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:56:38 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B38%2C28%5D%5D%2C%22name%22%3A%2250%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:56:54 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B38%2C27%5D%5D%2C%22name%22%3A%2251%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:57:29 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B11%2C12%5D%5D%2C%22name%22%3A%2252%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:57:48 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B13%2C14%5D%5D%2C%22name%22%3A%2253%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:58:25 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B17%2C16%5D%5D%2C%22name%22%3A%2254%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:59:07 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B24%2C14%5D%5D%2C%22name%22%3A%2256%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:59:34 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B14%2C25%5D%5D%2C%22name%22%3A%2257%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:08:59:56 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B15%2C24%5D%5D%2C%22name%22%3A%2258%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:00:30 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B22%2C14%5D%5D%2C%22name%22%3A%2259%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:01:34 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B21%2C26%5D%5D%2C%22name%22%3A%2294%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:01:56 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B35%2C16%5D%5D%2C%22name%22%3A%2295%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:07:39 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B30%2C20%5D%2C%5B35%2C36%5D%5D%2C%22name%22%3A%2268%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:08:12 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B37%2C26%5D%2C%5B25%2C34%5D%5D%2C%22name%22%3A%2271%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:08:41 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B25%2C14%5D%2C%5B26%2C17%5D%5D%2C%22name%22%3A%2273%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:08:56 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B38%2C28%5D%5D%2C%22name%22%3A%2260%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:09:08 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B28%2C18%5D%5D%2C%22name%22%3A%2261%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:09:31 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B33%2C23%5D%5D%2C%22name%22%3A%2262%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:09:57 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B32%2C33%5D%5D%2C%22name%22%3A%2263%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:10:12 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B13%2C23%5D%5D%2C%22name%22%3A%2264%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:10:46 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B12%2C13%5D%5D%2C%22name%22%3A%2265%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:11:05 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B29%2C19%5D%5D%2C%22name%22%3A%2266%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:11:17 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B29%2C10%5D%5D%2C%22name%22%3A%2267%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:11:37 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B20%2C23%5D%5D%2C%22name%22%3A%2269%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:11:56 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B34%2C24%5D%5D%2C%22name%22%3A%2270%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:12:31 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B24%2C14%5D%5D%2C%22name%22%3A%2272%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:12:48 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B37%2C27%5D%5D%2C%22name%22%3A%2274%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:13:04 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B17%2C27%5D%5D%2C%22name%22%3A%2275%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:13:24 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B21%2C22%5D%5D%2C%22name%22%3A%2276%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:13:35 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B21%2C11%5D%5D%2C%22name%22%3A%2277%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:13:53 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B11%2C22%5D%5D%2C%22name%22%3A%2278%22%7D HTTP/1.1" 200 367 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:14:02 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B31%2C21%5D%5D%2C%22name%22%3A%2279%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:14:12 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B31%2C22%5D%5D%2C%22name%22%3A%2280%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:14:32 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B15%2C16%5D%5D%2C%22name%22%3A%2281%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:14:43 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B39%2C38%5D%5D%2C%22name%22%3A%2282%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
10.97.109.112 - - [04/Aug/2025:09:15:22 +0200] "GET /actions/bridge-editor.php?action=add&meta=%7B%22pins%22%3A%5B%5B39%2C18%5D%5D%2C%22name%22%3A%2283%22%7D HTTP/1.1" 200 366 "http://10.97.109.103/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"';
// Alle passenden URLs extrahieren
preg_match_all('/GET (\/actions\/bridge-editor\.php\?action=(add|edit)&meta=[^ ]+)/', $data, $matches);
// URLs in ein Array
$urls = $matches[1];
// Wiederholen der Requests
foreach ($urls as $url) {
$fullUrl = "http://localhost" . $url;
echo "▶️ Sende: $fullUrl\n";
@file_get_contents($fullUrl); // @ unterdrückt Warnungen bei Fehlern
}
echo "✅ Wiederherstellung abgeschlossen.\n";
?>

97
db-test/index.php Executable file
View File

@@ -0,0 +1,97 @@
<?php
$db = new SQLite3('test.db');
/*
$db->exec('CREATE TABLE "bridges" (
"plug_id" INTEGER,
"id" INTEGER UNIQUE,
PRIMARY KEY("id"),
FOREIGN KEY("plug_id") REFERENCES "plugs"("id")
)');
$db->exec('CREATE TABLE "measurement_nodes" (
"required_by" INTEGER,
"node_from" INTEGER,
"node_to" INTEGER,
UNIQUE("required_by","node_from","node_to"),
FOREIGN KEY("required_by") REFERENCES ""
)');
$db->exec('CREATE TABLE "measurement_program_id_bits" (
"value" TEXT,
"position" INTEGER,
"measurement_id" INTEGER,
UNIQUE("position","measurement_id"),
FOREIGN KEY("measurement_id") REFERENCES ""
)');
$db->exec('CREATE TABLE "measurements" (
"id" INTEGER UNIQUE,
"place_name" TEXT,
"comment" TEXT,
"timestamp" INTEGER,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("place_name") REFERENCES ""
)');
$db->exec('CREATE TABLE "nodes" (
"plug_id" INTEGER,
"required_by" INTEGER,
"node_from" INTEGER,
"node_to" INTEGER,
UNIQUE("plug_id","required_by","node_from","node_to"),
FOREIGN KEY("required_by") REFERENCES "bridges"("id")
)');
$db->exec('CREATE TABLE "places" (
"plug_id" INTEGER,
"name" TEXT UNIQUE,
PRIMARY KEY("name"),
FOREIGN KEY("plug_id") REFERENCES "plugs"("id")
)');
$db->exec('CREATE TABLE "plugs" (
"id" INTEGER UNIQUE,
"name" TEXT,
"char_number" INTEGER,
PRIMARY KEY("id")
)');
$db->exec('CREATE TABLE "program_bridge_relation" (
"program_id" INTEGER,
"bridge_id" INTEGER,
"plug_id" INTEGER,
UNIQUE("program_id","bridge_id","plug_id"),
FOREIGN KEY("bridge_id") REFERENCES "bridges"("id"),
FOREIGN KEY("plug_id") REFERENCES "plugs"("id"),
FOREIGN KEY("program_id") REFERENCES "programs"("id")
)');
$db->exec('CREATE TABLE "program_id_bits" (
"value" TEXT,
"position" INTEGER,
"program_id" INTEGER,
UNIQUE("position","program_id"),
FOREIGN KEY("program_id") REFERENCES "programs"("id")
)');
$db->exec('CREATE TABLE "programs" (
"id" INTEGER UNIQUE,
"plug_id" INTEGER,
"program_description" TEXT,
PRIMARY KEY("id"),
FOREIGN KEY("plug_id") REFERENCES "plugs"("id")
)');
*/
$db->exec('DELETE FROM "places" WHERE 1');
?>

BIN
db-test/test.db Normal file

Binary file not shown.

2
errorcodes.php Normal file
View File

@@ -0,0 +1,2 @@
<hr><h3>Fehlercodes:</h3>
<table id="errorcodes" style="min-width: 75px"><colgroup><col style="min-width: 25px"><col style="min-width: 25px"><col style="min-width: 25px"></colgroup><tbody><tr><th colspan="1" rowspan="1">Gruppe</th><th colspan="1" rowspan="1"></th><th colspan="1" rowspan="1">Beschreibung</th></tr><tr><td colspan="1" rowspan="5"><p style="text-align: center">Eine einzelne Prüfung betreffend</td><td colspan="1" rowspan="1">00</td><td colspan="1" rowspan="1">Fehlerfrei</td></tr><tr><td colspan="1" rowspan="1">01</td><td colspan="1" rowspan="1">Brücke angeblich erkannt, vom redundanten Multiplexer jedoch nicht bestätigt (ungleichen Pin betreffend)</td></tr><tr><td colspan="1" rowspan="1">02</td><td colspan="1" rowspan="1">Unterschiedliche Ergebnisse: Prüfer misst LOW, Redundanz misst HIGH</td></tr><tr><td colspan="1" rowspan="1">03</td><td colspan="1" rowspan="1">Unterschiedliche Ergebnisse: Prüfer misst HIGH, Redundanz misst LOW</td></tr><tr><td colspan="1" rowspan="1">04</td><td colspan="1" rowspan="1">Prüfspannung angeblich gesetzt, von der Redundanz jedoch nicht bestätigt</td></tr><tr><td colspan="1" rowspan="3"><p style="text-align: center">Die gesamte Prüfung betreffend</td><td colspan="1" rowspan="1">10</td><td colspan="1" rowspan="1">Fehlerfrei</td></tr><tr><td colspan="1" rowspan="1">11</td><td colspan="1" rowspan="1">Ein/mehrere Multiplexer nicht ansprechbar, i2c ordnungsgemäß gestartet</td></tr><tr><td colspan="1" rowspan="1">12</td><td colspan="1" rowspan="1">i2c-Bus nicht startfähig, keine Kommunikation möglich</td></tr><tr><td colspan="1" rowspan="1"></td><td colspan="1" rowspan="1">13</td><td colspan="1" rowspan="1">Fehlerursache nicht weiter spezifizierbar</td></tr></tbody></table><style>table#errorcodes td {border: 1px solid black;}</style>

198
functions.php Normal file
View File

@@ -0,0 +1,198 @@
<?php
// ===========================================================================================
// Analyse-Funktionen für die Spurkabelprüfung, Federleistenprüfung und Selbstidiagnose
// ===========================================================================================
//Übsersetzungstabelle (Intern -> Steckerpin, gilt für Programmsteckerprüfung)
$translationMap = [
"0" => "18",
"1" => "19",
"2" => "20",
"3" => "21",
"4" => "22",
"5" => "23",
"6" => "24",
"7" => "25",
"8" => "10",
"9" => "11",
"10" => "12",
"11" => "13",
"12" => "14",
"13" => "15",
"14" => "16",
"15" => "17",
"16" => "34",
"17" => "35",
"18" => "36",
"19" => "37",
"20" => "38",
"21" => "39",
"22" => "40",
"23" => "41",
"24" => "26",
"25" => "27",
"26" => "28",
"27" => "29",
"28" => "30",
"29" => "31",
"30" => "32",
"31" => "33"
];
//Übsersetzungstabelle (Intern -> Steckerpin, gilt für Spurkabelprüfung)
$translationMap2 = [
"0" => "25",
"1" => "24",
"2" => "23",
"3" => "22",
"4" => "21",
"5" => "20",
"6" => "19",
"7" => "18",
"8" => "17",
"9" => "16",
"10" => "15",
"11" => "14",
"12" => "13",
"13" => "12",
"14" => "11",
"15" => "10",
"16" => "41",
"17" => "40",
"18" => "39",
"19" => "38",
"20" => "37",
"21" => "36",
"22" => "35",
"23" => "34",
"24" => "33",
"25" => "32",
"26" => "31",
"27" => "30",
"28" => "29",
"29" => "28",
"30" => "27",
"31" => "26"
];
// Diese Funktion übersetzt die interne Pin-Bezeichnung in die des Herstellers gem. translationMap
// weil die Hardware der Spurkabelprüfer anders verschaltet ist, als die des Programmsetckerprüfers, wird nach zwei Übersetzungstabellen bei der Spurkabelprüfung gearbeitet
// nodes[0] wird gem Standardtabelle gehandhabt, nodes[1] nach translationMap2 - unsauber, aber nur durhc Software lösbar
function translateArray($inputArray, $translationMap, $translationMap2 = null) {
return array_map(function($subArray) use ($translationMap, $translationMap2) {
$map1 = $translationMap;
$map2 = $translationMap2 ?? $translationMap; // Wenn keine zweite Map, nimm die erste
return [
$map1[$subArray[0]] ?? $subArray[0],
$map2[$subArray[1]] ?? $subArray[1]
];
}, $inputArray);
}
function measurementsDiffer($bridges1, $bridges2) {
//Isoliere die Brücken
$bridges1 = array_map(function($entry) {
return $entry[0];
}, $bridges1);
$bridges2 = array_map(function($entry) {
return $entry[0];
}, $bridges2);
//Zeichne die Steckergrafik
$url1 = "../stecker.php?data=" . urlencode(json_encode(translateArray($bridges1, $translationMap)));
$url2 = "../stecker.php?data=" . urlencode(json_encode(translateArray($bridges2, $translationMap)));
?>
<p>
Es gab einen Fehler bei der Messung!</br>Messung 1 und Messung 2 lieferten keine übereinstimmenden Ergebnisse.<br>Bitte prüfen Sie, ob alle Steckverbindungen fest sitzen und vermeiden Sie Bewegungen, welche die Messung beeinträchtigen können.</br>Stellen Sie sicher, dass keine der Verbindungen gegen Erde kurzgeschlossen ist, da dies die Messung beeinträchtigt.<br>Erwägen Sie, eine erneute Messung durchzuführen.
</p>
<br>
<p>Ergebnis Messung 1:</p>
<i><?php print_r($output1); ?><i>
<img src="<?php echo $url1; ?>" />
<p>Ergebnis Messung 2:</p>
<i><?php print_r($output2); ?><i>
<img src="<?php echo $url2; ?>" />
<div class="save-button" onclick="window.location.href='#index'; pageload();$('.save-button').html('<img src=\'load.gif\' \>Bitte warten...');"><img src="/vendor/icons/nav-arrow-left.svg" \>Zurück</div>
<?php
die();
}
function measurementsContainErrors($bridges) {
?>
<h3>Eine oder mehrere Prüfungen versagten. Vollständige Fehlerausgabe:</h3>
<table>
<tr><td>Brücke von</td><td>nach</td><td>Aufgetretene Fehler</td>
<?php
foreach($bridges as $bridge) {
echo "<tr><td>" . $bridge[0][0] . "</td><td>" . $bridge[0][1] . "</td><td>" . $bridge[1] . "</td></tr>";
}
?>
</table>
<hr>
<h3>Fehlercodes:</h3>
<?php
include("../errorcodes.php");
die();
}
/*
Ootionale Funktion, die implizite Verbindungen herleitet (KI-generiert)
function completeCycleEdges(array $edges): array {
// 1. Alle einzigartigen Knoten extrahieren
$nodes = [];
foreach ($edges as $edge) {
foreach ($edge as $node) {
$nodes[$node] = true;
}
}
$nodes = array_keys($nodes);
// 2. Sortiere Knoten (optional, für konsistente Reihenfolge)
sort($nodes);
// 3. Erstelle vollständigen Kreis (Verbindung i -> i+1, letzter -> erster)
$completeEdges = [];
$numNodes = count($nodes);
for ($i = 0; $i < $numNodes; $i++) {
$from = $nodes[$i];
$to = $nodes[($i + 1) % $numNodes]; // Kreis schließen
$completeEdges[] = [$from, $to];
}
return $completeEdges;
}
$edges = [[0,1], [2,0]];
$result = completeCycleEdges($edges);
print_r($result);
*/
?>

38
index.php Normal file
View File

@@ -0,0 +1,38 @@
<html>
<head>
<script src="/vendor/jquery.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style3.css">
<meta name="viewport" content="width=device-width, initial-scale=0.9">
</head>
<body onload="pageload(), checkConnection(), applySettings('plug')" onhashchange="pageload()">
<div class="main">
<div class="navigation">
<div class="navigation-header" onclick="secondaryNavigation('open','plug-select')">
<span>Steckerauswahl</span>
<div>Wird geladen...</div>
</div>
<div id="index" id="start" onclick="checkForReload('index');window.location.href='#index';"><img src="/vendor/icons/plug-type-l.svg" /><span>Federleiste prüfen</span></div>
<div id="cable-check" onclick="checkForReload('cable-check');window.location.href='#cable-check';"><img src="/vendor/icons/fiber-cable-icon.svg" /><span>Spurkabel prüfen</span></div>
<div id="inventory" onclick="checkForReload('inventory');window.location.href='#inventory';"><img src="/vendor/icons/product-delivery-tracking-icon.svg" /><span>Bestand</span></div>
<div id="bridge-editor" onclick="checkForReload('bridge-editor');window.location.href='#bridge-editor';"><img src="/vendor/icons/share-line-icon.svg" /><span>Brücken-Editor</span></div>
<div id="program-editor" onclick="checkForReload('program-editor');window.location.href='#program-editor';"><img src="/vendor/icons/plug-type-a.svg" /><span>Programm-Editor</span></div>
<div id="system" onclick="checkForReload('system');window.location.href='#system';"><img src="/vendor/icons/cpu.svg" /><span>System</span></div>
<div id="index" class="navigation-footer">
<div class="connection-indicator unknown"></div>Verbinde...<br>
<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />
</div>
</div>
<div class="navigation secondary" id="navigation-secondary">
</div>
<div id="message-box" style="display: none;position: absolute; bottom: 10px; left: 260px; right: 10px; background-color:grey;height:auto;padding:20px;box-sizing:border-box;transform: translateY(calc(100% + 5px));transition-duration:.5s; border-radius: 10px;background-color: #d2d2d2;z-index: 502;"></div>
<div class="content" id="content">
</div>
</div>
</body>
</html>

410
pagecontent/bridge-editor.php Executable file
View File

@@ -0,0 +1,410 @@
<?php
// Diese Seite ist für den Brücken-Editor
session_start();
$db = new SQLite3('../db-test/test.db');
$view = $_GET["view"];
$meta = $_GET["meta"];
$_SESSION["meta"]=$meta;
switch($view) {
case "add": // Neue Brücke anlegen
?>
<div class="content-header">Brücken-Editor - Neu anlegen</div>
<div class="plug-table">
<?php
$url = '../stecker.php?data=';
?>
<canvas id="bridge-editor-img" meta="<?php echo urlencode('[]'); ?>" style="background-image: url('<?php echo $url; ?>');" width="500" height="100"></canvas>
<script>
// Dieses Skript ist zum Großteil aus dem Internet kopiert, jedoch dann an die eigenen Bedürfnisse angepasst worden - Quelle: keine Ahnung
var canvas = document.getElementById('bridge-editor-img');
var ctx = canvas.getContext('2d');
var offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
var offCtx = offscreenCanvas.getContext('2d');
var rects = [
// Obere Zeile
{id: 31, x: 405, y: 20}, {id: 32, x: 370, y: 20}, {id: 33, x: 335, y: 20},
{id: 34, x: 300, y: 20}, {id: 35, x: 265, y: 20}, {id: 36, x: 230, y: 20},
{id: 37, x: 195, y: 20}, {id: 38, x: 160, y: 20}, {id: 39, x: 125, y: 20},
{id: 30, x: 70, y: 20},
// Mittlere Zeile
{id: 21, x: 405, y: 44}, {id: 22, x: 370, y: 44}, {id: 23, x: 335, y: 44},
{id: 24, x: 300, y: 44}, {id: 25, x: 265, y: 44}, {id: 26, x: 220, y: 44},
{id: 27, x: 185, y: 44}, {id: 28, x: 150, y: 44}, {id: 29, x: 115, y: 44},
{id: 20, x: 70, y: 44},
// Untere Zeile
{id: 11, x: 405, y: 68}, {id: 12, x: 370, y: 68}, {id: 13, x: 335, y: 68},
{id: 14, x: 300, y: 68}, {id: 15, x: 265, y: 68}, {id: 16, x: 230, y: 68},
{id: 17, x: 195, y: 68}, {id: 18, x: 160, y: 68}, {id: 19, x: 125, y: 68},
{id: 10, x: 70, y: 68}
];
var colorID = 0;
var RECT_WIDTH = 25;
var RECT_HEIGHT = 10;
var drawing = false;
var startPoint = null;
function getCenterIfInRect(x, y) {
for (var rect of rects) {
if (
x >= rect.x &&
x <= rect.x + RECT_WIDTH &&
y >= rect.y &&
y <= rect.y + RECT_HEIGHT
) {
return {
x: rect.x + RECT_WIDTH / 2,
y: rect.y + RECT_HEIGHT / 2,
id: rect.id
};
}
}
return null;
}
canvas.addEventListener('mousedown', (e) => {
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
var center = getCenterIfInRect(mouseX, mouseY);
if (center) {
startPoint = center;
drawing = true;
}
});
canvas.addEventListener('mousemove', (e) => {
if (!drawing || !startPoint) return;
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(offscreenCanvas, 0, 0);
ctx.beginPath();
ctx.moveTo(startPoint.x, startPoint.y);
ctx.lineTo(mouseX, mouseY);
ctx.strokeStyle = "red";
offCtx.lineWidth = 3;
ctx.stroke();
});
canvas.addEventListener('mouseup', (e) => {
if (!drawing || !startPoint) return;
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
var endPoint = getCenterIfInRect(mouseX, mouseY);
drawing = false;
if (endPoint && startPoint.id !== endPoint.id) {
connectPins(startPoint.id, endPoint.id, "canvas");
ctx.clearRect(0, 0, canvas.width, canvas.height);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(offscreenCanvas, 0, 0);
}
startPoint = null;
});
</script>
<?php
echo '
</div>
<div class="pin-table">'; // Hier folgt die eigentliche Bearbeitungs-Fläche
$pinTable_row = 0;
foreach ($nodes as $nodeItem) { // Anzahl an Gesamt-Brücken-Elementen (Dies ist Wichtig, da appliedPinRowNumber ein Funktionsparameter ist, damit JS weiß, an welcher Stelle ein neues Element mit welchem Index erstellt werden muss!)
$pinTable_row++;
}
$appliedPinRowNumber = $pinTable_row;
// im Folgenden die Eingabefelder anzeigen (mit aufsteigender ID)
$pinTable_row = 0;
foreach ($nodes as $nodeItem) {
echo '
<div class="row" id="pinRow_' . $pinTable_row . '">
<span>' . $nodeItem[0] . '</span>
<img src="/vendor/icons/data-transfer-both.svg" height="30px" />
<span>' . $nodeItem[1] . '</span>
<img style="transform: rotate(0deg); padding-top: 17.5px;" src="/vendor/icons/trash.svg" onclick="removePin(' . $pinTable_row . ')" height="30px"/>
</div>';
$pinTable_row++;
}
?>
</div>
<button hidden id="bridge-editor-edit" value=""></button>
<div class="save-button" onclick="save('bridge-editor','add')"><img src="/vendor/icons/floppy-disk.svg" \>Speichern</div>
<?php
break;
case "edit": // Bestehende Brücke bearbeiten
?>
<div class="content-header">Brücken-Editor - Bearbeiten</div>
<div class="plug-table">
<?php
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"]; //$plug ist die plug-ID!
$result = $db->query("
SELECT * FROM bridges WHERE plug_id = '" . $plug . "' AND id = '" . $meta . "';
");
$result = $result->fetchArray(SQLITE3_ASSOC);
// Skelettstruktur: Die "Row" sorgt hier für das typische Layout, insbesodere für die Darstellung der Brückennummer
?>
<div class="row">
<?php
print('
<div class="label">
<span>' . $result["id"] . '</span>
</div>'); // Brückennummer
$nodes_url = [];
$nodes = $db->query("
SELECT node_from,node_to
FROM nodes WHERE required_by = '". $result['id'] ."';
");
while ($node = $nodes->fetchArray(SQLITE3_ASSOC)) {
$nodes_url[] = [$node['node_from'], $node['node_to']];
}
$url = '../stecker.php?data=' . urlencode(json_encode($nodes_url)); // URL parsen
$nodes = $nodes_url; // die Nodes speichern (hier stecken alle Daten drin)
?>
</div>
<canvas id="bridge-editor-img" meta="<?php echo urlencode(json_encode($nodes_url)); ?>" style="background-image: url('<?php echo $url; ?>');" width="500" height="100"></canvas>
<script>
// Dieses Skript ist zum Großteil aus dem Internet kopiert, jedoch dann an die eigenen Bedürfnisse angepasst worden - Quelle: keine Ahnung
var canvas = document.getElementById('bridge-editor-img');
var ctx = canvas.getContext('2d');
var offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
var offCtx = offscreenCanvas.getContext('2d');
var rects = [
// Obere Zeile
{id: 31, x: 405, y: 20}, {id: 32, x: 370, y: 20}, {id: 33, x: 335, y: 20},
{id: 34, x: 300, y: 20}, {id: 35, x: 265, y: 20}, {id: 36, x: 230, y: 20},
{id: 37, x: 195, y: 20}, {id: 38, x: 160, y: 20}, {id: 39, x: 125, y: 20},
{id: 30, x: 70, y: 20},
// Mittlere Zeile
{id: 21, x: 405, y: 44}, {id: 22, x: 370, y: 44}, {id: 23, x: 335, y: 44},
{id: 24, x: 300, y: 44}, {id: 25, x: 265, y: 44}, {id: 26, x: 220, y: 44},
{id: 27, x: 185, y: 44}, {id: 28, x: 150, y: 44}, {id: 29, x: 115, y: 44},
{id: 20, x: 70, y: 44},
// Untere Zeile
{id: 11, x: 405, y: 68}, {id: 12, x: 370, y: 68}, {id: 13, x: 335, y: 68},
{id: 14, x: 300, y: 68}, {id: 15, x: 265, y: 68}, {id: 16, x: 230, y: 68},
{id: 17, x: 195, y: 68}, {id: 18, x: 160, y: 68}, {id: 19, x: 125, y: 68},
{id: 10, x: 70, y: 68}
];
var colorID = 0;
var RECT_WIDTH = 25;
var RECT_HEIGHT = 10;
var drawing = false;
var startPoint = null;
function getCenterIfInRect(x, y) {
for (var rect of rects) {
if (
x >= rect.x &&
x <= rect.x + RECT_WIDTH &&
y >= rect.y &&
y <= rect.y + RECT_HEIGHT
) {
return {
x: rect.x + RECT_WIDTH / 2,
y: rect.y + RECT_HEIGHT / 2,
id: rect.id
};
}
}
return null;
}
canvas.addEventListener('mousedown', (e) => {
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
var center = getCenterIfInRect(mouseX, mouseY);
if (center) {
startPoint = center;
drawing = true;
}
});
canvas.addEventListener('mousemove', (e) => {
if (!drawing || !startPoint) return;
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(offscreenCanvas, 0, 0);
ctx.beginPath();
ctx.moveTo(startPoint.x, startPoint.y);
ctx.lineTo(mouseX, mouseY);
ctx.strokeStyle = "red";
offCtx.lineWidth = 3;
ctx.stroke();
});
canvas.addEventListener('mouseup', (e) => {
if (!drawing || !startPoint) return;
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
var endPoint = getCenterIfInRect(mouseX, mouseY);
drawing = false;
if (endPoint && startPoint.id !== endPoint.id) {
connectPins(startPoint.id, endPoint.id, "canvas");
ctx.clearRect(0, 0, canvas.width, canvas.height);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(offscreenCanvas, 0, 0);
}
startPoint = null;
});
</script>
<?php
echo '
</div>
<div class="pin-table">'; // Hier folgt die eigentliche Bearbeitungs-Fläche
$pinTable_row = 0;
foreach ($nodes as $nodeItem) { // Anzahl an Gesamt-Brücken-Elementen (Dies ist Wichtig, da appliedPinRowNumber ein Funktionsparameter ist, damit JS weiß, an welcher Stelle ein neues Element mit welchem Index erstellt werden muss!)
$pinTable_row++;
}
$appliedPinRowNumber = $pinTable_row;
// im Folgenden die Eingabefelder anzeigen (mit aufsteigender ID)
$pinTable_row = 0;
foreach ($nodes as $nodeItem) {
echo '
<div class="row" id="pinRow_' . $pinTable_row . '">
<span>' . $nodeItem[0] . '</span>
<img src="/vendor/icons/data-transfer-both.svg" height="30px" />
<span>' . $nodeItem[1] . '</span>
<img style="transform: rotate(0deg); padding-top: 17.5px;" src="/vendor/icons/trash.svg" onclick="removePin(' . $pinTable_row . ')" height="30px"/>
</div>';
$pinTable_row++;
}
?>
</div>
<button id="bridge-editor-edit" value="" onclick="save('bridge-editor','edit','<?php echo $meta; ?>')">Speichern</button>
<?php
break;
default: // Standard: Übersicht
?>
<div class="content-header">Brücken-Editor - Übersicht</div>
<div class="action-menu">
<img src="/vendor/icons/plus.svg" onclick="window.location.href='#bridge-editor'; pageload('add');" />
</div>
<div class="plug-table">
<?php
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"]; //$plug ist die plug-ID!
$result = $db->query("SELECT * FROM bridges WHERE plug_id = '" . $plug . "';");
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
?>
<div class="row">
<?php
print('
<div class="label">
<span>' . $row['id'] . '</span>
</div>');
$nodes_url = [];
$nodes = $db->query("
SELECT node_from,node_to
FROM nodes WHERE required_by = ". $row['id'] .";
");
while ($node = $nodes->fetchArray(SQLITE3_ASSOC)) {
$nodes_url[] = [$node['node_from'], $node['node_to']];
}
$url = '../stecker.php?data=' . urlencode(json_encode($nodes_url)); // URL parsen
print('<img id="bridge-editor-img" src="' . $url . '" />');
print('
<div class="options">
<img src="/vendor/icons/edit-pencil.svg" onclick="window.location.href=\'#bridge-editor\'; pageload(\'edit\',\'' . $row["id"] . '\');"/>
<img src="/vendor/icons/trash.svg" onclick="if(confirm(\'Sicher? Diese Änderung kann nicht rückgängig gemacht werden!\') == true){save(\'bridge-editor\',\'remove\',\'' . $row["id"] . '\')}" />
</div>
');
?>
</div>
<?php
}
$result = $db->query("SELECT COUNT(*) as anzahl FROM bridges WHERE plug_id = '" . $plug . "';");
$row = $result->fetchArray(SQLITE3_ASSOC);
$number = $row['anzahl'];
if($number <= 0) {
print("<p align='center'>Es sind keine Brücken für diesen Stecker gespeichert.<br>Eventuell muss ein anderer Stecker gewählt werden.</p>");
}
?>
</div>
<?php
break;
}

13
pagecontent/cable-check.php Executable file
View File

@@ -0,0 +1,13 @@
<?php
session_start();
?>
<div class="content-header">Spurkabel prüfen</div>
<hr />
<p>Automatische Erkennung</p>
<i>Verbinden Sie die Enden des Spurkabels mit dem Gerät. Vermeiden Sie Bewegungen während der Messung.</i>
<div class="save-button" onclick="window.location.href='#start-cable';$('.save-button').html('<img src=\'/vendor/icons/load.gif\' \>Initialisieren...'); "><img src="/vendor/icons/play.svg" \>Jetzt prüfen</div>

322
pagecontent/database-search.php Executable file
View File

@@ -0,0 +1,322 @@
<?php
// Diese Seite durchsucht die Datenbank nach gespeicherten Werten (keine Brücken, Programme, etc..)
$meta = $_GET["meta"];
$view = $_GET["view"];
$db = new SQLite3('../db-test/test.db');
// Die Suche, auf welche nach der Durchführung einer Messung zugegriffen wird (ganz unten: möchten Sie die Messung in die Datenbank aufnehmen...)
if($view == "results") {
?>
<div class="inventory-table">
<?php
$result = 0;
// Hole die Orte
$result = $db->query(" SELECT * FROM places WHERE name LIKE '%" . strtolower($meta) . "%' ");
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
// Wenn die suche leer ist, sollen auch keine Ergebnisse kommen (siehe Vorschläge ignorieren)
if($meta == "") {
return;
}
?>
<div class="row" style="cursor: pointer" onclick="$('#database-search-term').val('<?php echo $row["name"]; ?>'); search('results');">
<img src="/vendor/icons/map-pin.svg" class="map" /><span><?php echo $row["name"]; ?></span>
</div>
<?php
}
// Wenn ein Ergebnis gefunden wird...
if($result == 1) {
// ... biete an, alle Vorschläge zu ignorieren
?>
<div class="row" style="cursor: pointer; width:50%;margin:15px auto;background-color: #f6615173" onclick="$('#database-search-term').attr('onkeyup',''); search('results','x');">
<img src="/vendor/icons/xmark.svg" class="" /><span align="center">Vorschläge ignorieren</span>
</div>
<?php
}
// Ende der Sucherergebnisse
?>
</div>
<?php
}
// Hier wird der Bestand angezeigt, der in der Bestandsuche nach einem Tastenanschlag erscheint
if($view == "program" && isset($meta) && !empty($meta)) {
// weil die Bestandssuche (im Gegensatz zur Ortssuche) die Programmbezeichnung durhcsucht, ist hier etwas mehr Aufwand erforderlich
// hole die Programmbezeichnung via 2x JOIN aus der Datenbank
$result = $db->query("
SELECT
m.id AS measurement_id,
m.comment AS comment,
m.timestamp AS timestamp,
m.place_name AS place_name,
b.value AS bit_value,
b.position AS bit_position
FROM
measurements m
JOIN places p ON p.name = m.place_name
JOIN measurement_program_id_bits b ON b.measurement_id = m.id
ORDER BY m.id
");
/*
Ausgabe sieht z.B. so aus:
| measurement_id | comment | timestamp | place_name | bit_value | bit_position |
| -------------- | ------- | --------- | ------------ | --------- | ------------ |
| 1 | xyz.. | 34535454 | Dudweiler Df | S | 0 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 1 |
| 1 | xyz.. | 34535454 | Dudweiler Df | 4 | 2 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 3 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 4 |
| 7 | abc.. | 48548574 | Mannheim Hbf | A | 0 |
| 7 | abc.. | 48548574 | Mannheim Hbf | 6 | 1 |
| 7 | abc.. | 48548574 | Mannheim Hbf | 4 | 2 |
| 7 | abc.. | 48548574 | Mannheim Hbf | - | 3 |
| 7 | abc.. | 48548574 | Mannheim Hbf | S | 4 |
*/
// Die Datenbank wird als dreifach indexiertes Array gespeichert
$database_db = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$database_db[$row["place_name"]][$row["measurement_id"]]["program_id_bits"][$row["bit_position"]] = $row["bit_value"];
$database_db[$row["place_name"]][$row["measurement_id"]]["comment"] = $row["comment"];
$database_db[$row["place_name"]][$row["measurement_id"]]["timestamp"] = $row["timestamp"];
}
/*
$database_db
└── $row["place_name"]
└── $row["measurement_id"]
├── "program_id_bits"
│ └── $row["bit_position"]
│ └── $row["bit_value"]
├── "program_id_string"
│ └── implode('', $bits)
├── "comment"
│ └── $row["comment"]
└── "timestamp"
└── $row["timestamp"]
*/
// speichere die Bits als String, damit diese einfacher durchsuchbar sind
foreach ($database_db as $place => $entries) {
foreach ($entries as $index => $info) {
$bits = $info['program_id_bits'];
$database_db[$place][$index]['program_id_string'] = implode('', $bits);
}
}
// durchsuche nach der Query im Programm-ID-String
foreach($database_db as $place_name => $measurements) {
$found = false;
foreach ($measurements as $measurement_id => $measurement) {
if(str_contains(strtolower($measurement["program_id_string"]), strtolower($meta))) {
$found = true;
}
}
if($found != true) {
continue;
}
?>
<div class="row">
<img src="/vendor/icons/map-pin.svg" class="map" />
<span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <?php echo $place_name; ?>
</span>
<div class="plug-table" style="display: block;" id="<?php echo $database_db[$place_name]; ?>">
<?php
foreach ($measurements as $measurement_id => $measurement) {
if(!str_contains(strtolower($measurement["program_id_string"]), strtolower($meta))) {
continue;
}
print('
<div class="row">
<div class="label" style="width: 180px;">
<span>');
$i = 0;
foreach($measurement["program_id_bits"] as $programIDChar) {
echo '<span class="label-char">' . $programIDChar . '</span>';
$i++;
}
print('
</span>
</div>
<div>
<div class="label" style="width: 50%;">
<span>' . date("d.m.Y H:i:s", $measurement["timestamp"]) . '</span>
</div>');
?>
<div class="options">
<img src="/vendor/icons/trash.svg" onclick="if(confirm('Sicher? Diese Änderung kann nicht rückgängig gemacht werden! )') == true){save('inventory','remove','<?php echo $measurement_id; ?>')}">
</div>
<?php
print('
</div>
</div>');
if(!empty($measurement["comment"])) {
print('
<div class="row" style="position: relative; height: auto;padding: 0 15;padding-left: 40px;box-sizing:border-box;">
<img src="/vendor/icons/info-circle.svg" style=" top: 2.5px;left: 10px;" />');
echo $measurement["comment"];
print('
</div>');
}
}
?>
</div>
</div>
<?php
}
}
// Hier wird der Bestand angezeigt, der in der Bestandssuche nach dem Klick auf "Alle Einträge holen" angezeigt wird
elseif ($view == "program") {
/*
Ausgabe sieht z.B. so aus:
| measurement_id | comment | timestamp | place_name | bit_value | bit_position |
| -------------- | ------- | --------- | ------------ | --------- | ------------ |
| 1 | xyz.. | 34535454 | Dudweiler Df | S | 0 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 1 |
| 1 | xyz.. | 34535454 | Dudweiler Df | 4 | 2 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 3 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 4 |
| 7 | abc.. | 48548574 | Mannheim Hbf | A | 0 |
| 7 | abc.. | 48548574 | Mannheim Hbf | 6 | 1 |
| 7 | abc.. | 48548574 | Mannheim Hbf | 4 | 2 |
| 7 | abc.. | 48548574 | Mannheim Hbf | - | 3 |
| 7 | abc.. | 48548574 | Mannheim Hbf | S | 4 |
*/
$result = $db->query("
SELECT
m.id AS measurement_id,
m.comment AS comment,
m.timestamp AS timestamp,
m.place_name AS place_name,
b.value AS bit_value,
b.position AS bit_position
FROM
measurements m
JOIN places p ON p.name = m.place_name
JOIN measurement_program_id_bits b ON b.measurement_id = m.id
ORDER BY m.id
");
// Die Datenbank wird als dreifach indexiertes Array gespeichert
$database_db = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$database_db[$row["place_name"]][$row["measurement_id"]]["program_id_bits"][$row["bit_position"]] = $row["bit_value"];
$database_db[$row["place_name"]][$row["measurement_id"]]["comment"] = $row["comment"];
$database_db[$row["place_name"]][$row["measurement_id"]]["timestamp"] = $row["timestamp"];
}
/*
$database_db
└── $row["place_name"]
└── $row["measurement_id"]
├── "program_id_bits"
│ └── $row["bit_position"]
│ └── $row["bit_value"]
├── "program_id_string"
│ └── implode('', $bits)
├── "comment"
│ └── $row["comment"]
└── "timestamp"
└── $row["timestamp"]
*/
foreach($database_db as $place_name => $measurements) {
?>
<div class="row">
<img src="/vendor/icons/map-pin.svg" class="map" /><span>[<?php echo count($database_db[$place_name]); ?>] <?php echo $place_name; ?></span>
<img src="/vendor/icons/arrow-down-tag.svg" class="down" onclick="$('#<?php echo preg_replace('/[^a-zA-Z0-9]/', '',$place_name); ?>').css('display','block');"/>
<div class="plug-table" style="display: none;" id="<?php echo preg_replace('/[^a-zA-Z0-9]/', '',$place_name); ?>">
<?php
foreach ($measurements as $measurement_id => $measurement) {
print('
<div class="row" style="margin-bottom: 0;">
<div class="label" style="width: 180px;">
<span>');
foreach($measurement["program_id_bits"] as $programIDChar) {
echo '
<span class="label-char">' . $programIDChar . '</span>';
}
print('
</span>
</div>
<div>
<div class="label" style="width: 50%;">
<span>' . date("d.m.Y H:i:s", $measurement["timestamp"]) . '</span>
</div>');
?>
<div class="options"><img src="/vendor/icons/trash.svg" onclick="if(confirm('Sicher? Diese Änderung kann nicht rückgängig gemacht werden! )') == true){save('inventory','remove','<?php echo $measurement_id; ?>')}"></div>
<?php
print('
</div>
</div>');
if(!empty($measurement["comment"])) {
print('
<div class="row" style="position: relative; height: auto;padding: 0 15;padding-left: 40px;box-sizing:border-box;"><img src="/vendor/icons/info-circle.svg" style=" top: 2.5px;left: 10px;" />');
echo $measurement["comment"];
print('
</div>');
}
}
?>
</div>
</div>
<?php
}
}

220
pagecontent/export.php Executable file
View File

@@ -0,0 +1,220 @@
<?php
session_start();
ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL);
$_SESSION["meta"]=$meta;
error_reporting(E_ALL);
ini_set('display_errors', 1);
$db = new SQLite3('../db-test/test.db');
// weil die Bestandssuche (im Gegensatz zur Ortssuche) die Programmbezeichnung durhcsucht, ist hier etwas mehr Aufwand erforderlich
// hole die Programmbezeichnung via 2x JOIN aus der Datenbank
$result = $db->query("
SELECT
m.id AS measurement_id,
m.comment AS comment,
m.timestamp AS timestamp,
m.place_name AS place_name,
b.value AS bit_value,
b.position AS bit_position,
n.node_from AS node_from,
n.node_to AS node_to
FROM
measurements m
JOIN places p ON p.name = m.place_name
JOIN measurement_program_id_bits b ON b.measurement_id = m.id
JOIN measurement_nodes n ON n.required_by = m.id
");
/*
Ausgabe sieht z.B. so aus:
| measurement_id | comment | timestamp | place_name | bit_value | bit_position | node_from | node_to |
| -------------- | ------- | --------- | ------------ | --------- | ------------ | --------- | ------- |
| 1 | xyz.. | 34535454 | Dudweiler Df | S | 0 | 20 | 28 |
| 1 | xyz.. | 34535454 | Dudweiler Df | S | 0 | 28 | 30 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 1 | 20 | 28 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 1 | 28 | 30 |
| 1 | xyz.. | 34535454 | Dudweiler Df | 4 | 2 | 20 | 28 |
| 1 | xyz.. | 34535454 | Dudweiler Df | 4 | 2 | 28 | 30 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 3 | 20 | 28 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 3 | 28 | 30 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 4 | 20 | 28 |
| 1 | xyz.. | 34535454 | Dudweiler Df | - | 4 | 28 | 30 |
*/
// Die Datenbank wird als dreifach indexiertes Array gespeichert
$database_db = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$id = $row["measurement_id"];
// Basisdaten setzen (überschreiben macht nichts)
$database_db[$id]["comment"] = $row["comment"];
$database_db[$id]["timestamp"] = $row["timestamp"];
$database_db[$id]["place_name"] = $row["place_name"];
// Programmbits: nur setzen, wenn nicht schon vorhanden
if (!isset($database_db[$id]["program_id_bits"][$row["bit_position"]])) {
$database_db[$id]["program_id_bits"][$row["bit_position"]] = $row["bit_value"];
}
// Nodes: nur hinzufügen, wenn noch nicht vorhanden
$node_pair = [$row["node_from"], $row["node_to"]];
if (
!isset($database_db[$id]["nodes"]) ||
!in_array($node_pair, $database_db[$id]["nodes"])
) {
$database_db[$id]["nodes"][] = $node_pair;
}
}
// Ersetzen der $database_db[$id]["nodes"] zu einem String
$bitLength = 5;
$placeholder = '?';
foreach ($database_db as $index => $row) {
$programIdBits = isset($row['program_id_bits']) ? $row['program_id_bits'] : [];
$programIdString = '';
// Baue ein Array mit fester Länge ($bitLength), fülle fehlende Positionen mit Platzhalter
for ($i = 0; $i < $bitLength; $i++) {
$programIdString .= isset($programIdBits[$i]) ? $programIdBits[$i] : $placeholder;
}
$database_db[$index]['program_id_string'] = $programIdString;
}
// $list ist die CSV-Datei
//$list[] = array("Gemessen am / um", "Stw-Name / Ort" ,"Erkannte Brücken (Pin-zu-Pin)", "(automatisch) ermittelte Programm-Nummer", "Freitext-Besonderheiten");
// gehe jeden Datenwankeintrag durch
foreach($database_db as $database_entry) {
// leere dne node_string zu Beginn
$node_string = "";
// erstelle einen Brücken-String, der erweitert wird
foreach($database_entry["nodes"] as $node) {
$node_string .= $node[0] . "<->" . $node[1] . ", ";
}
$imagePath = 'http://localhost/stecker.php?data=' . urlencode(json_encode($database_entry["nodes"])); // URL parsen
// Schreibe ins Array
$list[] = array(date('d.m.Y H:i:s',$database_entry["timestamp"]), $database_entry["place_name"] ,$node_string, $database_entry["program_id_string"], $database_entry["comment"], $imagePath);
}
function svgUrlToPngViaRsvg(string $svgUrl, string $outputPath): bool {
$svgData = file_get_contents($svgUrl);
if ($svgData === false) return false;
$tempSvg = '../tmp/stecker_' . uniqid() . '.svg';
file_put_contents($tempSvg, $svgData);
$cmd = escapeshellcmd("rsvg-convert -o " . escapeshellarg($outputPath) . " " . escapeshellarg($tempSvg));
$result = shell_exec($cmd);
unlink($tempSvg);
return file_exists($outputPath);
}
require '../vendor/composer/vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
// Neue Excel-Datei erzeugen
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Spaltenüberschriften
$sheet->setCellValue('A1', 'Zeitstempel');
$sheet->setCellValue('B1', 'Ort');
$sheet->setCellValue('C1', 'Brücken');
$sheet->setCellValue('D1', 'Programmbezeichnung');
$sheet->setCellValue('E1', 'Notizen');
// Durch das Array iterieren und Daten + Bild einfügen
$row = 2; // Start in Zeile 2
foreach ($list as $entry) {
[$timestamp,$place,$bridges,$program_number, $extra, $imagePath] = $entry;
// Namen einfügen
$sheet->setCellValue("A$row", $timestamp);
$sheet->setCellValue("B$row", $place);
$sheet->setCellValue("C$row", $bridges);
$sheet->setCellValue("D$row", $program_number);
$sheet->setCellValue("E$row", $extra);
$svgUrl = $imagePath;
$tmpPng = '../tmp/stecker_' . uniqid() . '.png';
// Bild einfügen, wenn Datei existiert
if (svgUrlToPngViaRsvg($svgUrl, $tmpPng)) {
$drawing = new Drawing();
$drawing->setPath($tmpPng);
$drawing->setCoordinates("F$row");
$drawing->setHeight(80);
$sheet->getRowDimension($row)->setRowHeight(80 * 0.75);
$drawing->setWorksheet($sheet);
} else {
$sheet->setCellValue("F$row", 'Bildkonvertierung fehlgeschlagen');
}
$row++;
}
// Spaltenbreite anpassen
$sheet->getColumnDimension('A')->setAutoSize(true);
$sheet->getColumnDimension('B')->setWidth(20); // Platz fürs Bild
// Datei speichern
$writer = new Xlsx($spreadsheet);
$writer->save('../temp.xlsx');
shell_exec("rm ../tmp/*");
?>
<div class="content-header">Export - Fertig</div>
<hr />
<p>
Ihr Datenexport ist fertig vorbereitet.
</p>
<div class="save-button" onclick="window.location.href='../temp.xlsx'"); "><img src="/vendor/icons/play.svg" \>Herunterladen</div>

15
pagecontent/index.php Executable file
View File

@@ -0,0 +1,15 @@
<?php
// Hauptseite
session_start();
?>
<div class="content-header">Federleiste prüfen</div>
<hr />
<p>Automatische Erkennung</p>
<i>Verbinden Sie den zu messenden Stecker mit dem Gerät. Vermeiden Sie Bewegungen während der Messung.</i>
<div class="save-button" onclick="window.location.href='#start';$('.save-button').html('<img src=\'/vendor/icons/load.gif\' \>Prüfung läuft...'); "><img src="/vendor/icons/play.svg" \>Jetzt prüfen</div>

18
pagecontent/inventory.php Executable file
View File

@@ -0,0 +1,18 @@
<div class="content-header">Bestand</div>
<div class="action-menu">
<img src="/vendor/icons/download.svg" onclick="window.location.href='#export'; $(this).attr('src','/vendor/icons/load.gif'); message('Der Export wird vorbereitet. Das kann etwas dauern...');" />
</div>
<hr />
<div class="search-container">
<input type="text" id="database-search-term" onkeyup="search('program')" placeholder="Programm durchsuchen..." />
<div class="results">
</div>
</div>
<hr />
<div class="inventory-table" id="database-program-search" onload="search('program')">
<div class="save-button" onclick="search('program')"><img src="/vendor/icons/restart.svg" \>Alle Datensätze holen</div>
</div>

1
pagecontent/plug-select.php Executable file
View File

@@ -0,0 +1 @@
Aktuell werden keine verschiedenen Steckerarten unterstützt.

230
pagecontent/program-editor.php Executable file
View File

@@ -0,0 +1,230 @@
<?php
// Diese Seite lädt alle (Unter)Seiten des Programmeditors
session_start();
$db = new SQLite3('../db-test/test.db');
$view = $_GET["view"];
$meta = $_GET["meta"];
$_SESSION["meta"]=$meta;
$plug = json_decode(file_get_contents("../settings.json"), true)["plug"];
switch($view) {
default: // Standardseite (Übersicht)
?>
<div class="content-header">Programm-Editor - Übersicht</div>
<div class="action-menu">
<img src="/vendor/icons/plus.svg" onclick="window.location.href='#program-editor'; pageload('add');" />
</div>
<div class="plug-table">
<?php
// Hole die Programme aus der Datenbank
$result = $db->query("SELECT * FROM programs WHERE plug_id = '" . $plug . "';");
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$results = true;
?>
<div class="row">
<?php
print('
<div class="label" style="width: 30%">
<span>');
// Stelle die einzelnen Programmziffern dar
$program_identifiers = $db->query("SELECT * FROM program_id_bits WHERE program_id = '" . $row['id'] . "';");
while ($program_identifier = $program_identifiers->fetchArray(SQLITE3_ASSOC)) {
echo '<span class="label-char">' . $program_identifier["value"] . '</span>';
}
print('
</span>
</div>');
// Die erforderlichen Brücken werden aus der Datenbank geholt (via program_bridge_relation)
$bridges_url = [];
$bridges = $db->query("SELECT * FROM program_bridge_relation WHERE program_id = '" . $row['id'] . "';");
while ($bridge = $bridges->fetchArray(SQLITE3_ASSOC)) {
$bridges_url[] = $bridge["bridge_id"];
}
$url = '../stecker.php?translate=true&data=' . urlencode(json_encode($bridges_url)); // URL parsen; Modus: Programmanzeige
print('<img id="bridge-editor-img" style="width: 55%" src="' . $url . '" />');
// Aktionsmenü (mit hässlichem Inline-Code)
print('
<div class="options">
<img src="/vendor/icons/edit-pencil.svg" onclick="window.location.href=\'#program-editor\'; pageload(\'edit\',\'' . $row['id'] . '\');"/>
<img src="/vendor/icons/trash.svg"onclick="
if(confirm(\'Sicher? Diese Änderung kann nicht rückgängig gemacht werden! (Die darin enthaltenen Brücken bleiben erhalten und werden nicht gelöscht )\') == true){
save(\'program-editor\',\'remove\',\'' . $row['id'] . '\')
}" />
</div>
');
?>
</div>
<?php
}
?>
</div>
<?php
// Wenn Ergebnisliste leer
if($results != true) {
print("<p align='center'>Es sind keine Programme für diesen Stecker gespeichert.<br>Eventuell muss ein anderer Stecker gewählt werden.</p>");
}
break;
// Neues Hinzufügen eines Programms
case "add":
$meta = json_decode(urldecode($meta), true);
?>
<div class="content-header">Programm-Editor - Neu anlegen</div>
<img id="program-editor-img" src="../stecker.php" />
<hr />
<p>Zugehörige Brücken</p>
<i>Wählen Sie die Brücken, welche von diesem Programm gefordert werden</i>
<div class="program-field">
<?php
//zählen der Ergebnisse
$result = $db->query("SELECT COUNT(*) as anzahl FROM bridges WHERE plug_id = '" . $plug . "';");
$row = $result->fetchArray(SQLITE3_ASSOC);
$results_full = $row['anzahl'];
$count = 0;
// Holen der Ergebnisse
$result = $db->query("SELECT * FROM bridges WHERE plug_id = '" . $plug . "';");
// Zeigt ein horizonales Auswhl-Menü an, das alle Brücken als auswahl bietet
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
?>
<label class="checkbox-container">
<input onchange="programBridge(<?php echo $results_full; ?>)" type="checkbox" id="programSelect_<?php echo $count; ?>" value="<?php echo $row['id']; ?>">
<span class="checkbox-text">
<?php echo $row['id']; ?>
</span>
</label>
<?php
$count++;
}
?>
</div>
<hr />
<p>Name für dieses Programm</p>
<i>
Hinweis: Der Name kann aus Sicherheitsgründen im Nachhinein nicht mehr geändert werden. Bei dieser Stellwerksart ist die ID fünfstellig<br>Ein '-' kennzeichnet ein "Don't-Care". Darüber hinaus sind nur Großbuchstaben und Zahlen zugelassen
</i>
<div class="distinctly-input">
<input oninput="distinctlyInput(1)" type="text" maxlength="1" class="code-input" id="distinctlyInput1">
<input oninput="distinctlyInput(2)" type="text" maxlength="1" class="code-input" id="distinctlyInput2">
<input oninput="distinctlyInput(3)" type="text" maxlength="1" class="code-input" id="distinctlyInput3">
<input oninput="distinctlyInput(4)" type="text" maxlength="1" class="code-input" id="distinctlyInput4">
<input oninput="distinctlyInput(5)" type="text" maxlength="1" class="code-input" id="distinctlyInput5">
</div>
<hr />
<p>Beschreibung für dieses Programm</p>
<textarea id="program-description" placeholder="Wählen Sie eine möglichst präzise Beschreibung. Zum Beispiel: 'Relaisgruppe für eine nicht isolierte Weiche, die eine einfache Kreuzungsweiche mit Bogenfahrt über den Plusstrang ist'"></textarea>
<hr />
<div class="save-button" onclick="save('program-editor','add',<?php echo $results_full; ?>)"><img src="/vendor/icons/floppy-disk.svg" \>Speichern</div>
<?php
break;
case "edit":
$meta = json_decode(urldecode($meta), true);
?>
<div class="content-header">Programm-Editor - Bearbeiten</div>
<?php
$bridges_url = [];
$bridges = $db->query("SELECT * FROM program_bridge_relation WHERE program_id = '" . $meta . "';");
while ($bridge = $bridges->fetchArray(SQLITE3_ASSOC)) {
$bridges_url[] = $bridge["bridge_id"];
}
$url = '../stecker.php?translate=true&data=' . urlencode(json_encode($bridges_url)); // URL parsen
print('<img id="program-editor-img" src="../stecker.php?translate=true&data=' . $url . '" />');
?>
<hr />
<p>Zugehörige Brücken</p>
<i>Wählen Sie die Brücken, welche von diesem Programm gefordert werden</i>
<div class="program-field">
<?php
// Hole Anzahl der Brücken aus der DB
$number_bridges = $db->query("SELECT COUNT(*) as anzahl FROM bridges WHERE plug_id = '" . $plug . "';");
$number_bridges = $number_bridges->fetchArray(SQLITE3_ASSOC);
$number_bridges = $number_bridges['anzahl'];
$number_joined = 0;
// Hole zunächst ALLE Brücken aus der DB
$bridges = $db->query("SELECT * FROM bridges WHERE plug_id = '" . $plug . "';");
while ($bridge = $bridges->fetchArray(SQLITE3_ASSOC)) {
// schaue nun, ob diese Brücke Teil des Programms ist
$bridges_contained = $db->query("SELECT COUNT(*) as anzahl FROM program_bridge_relation WHERE plug_id = '" . $plug . "' and bridge_id = '" . $bridge["id"] . "' and program_id = '" . $meta . "';");
$row = $bridges_contained->fetchArray(SQLITE3_ASSOC);
$number = $row['anzahl'];
// Die Anzahl gibt Aufschlus darüber, ob Brücke von Programm gefordert ist -> wenn schon vorhanden, markiere diese Brücke schon vor ("checked"-Attribut)
?>
<label class="checkbox-container">
<input onchange="programBridge(<?php echo $number_bridges; ?>)" type="checkbox" <?php if($number > 0) { echo "checked"; } ?> id="programSelect_<?php echo $number_joined; ?>" value="<?php echo $bridge["id"]; ?>">
<span class="checkbox-text">
<?php echo $bridge["id"]; ?>
</span>
</label>
<?php
$number_joined++;
}
?>
</div>
<hr />
<p>Name für dieses Programm</p>
<i>Darf auf Sicherheitsgründen nicht mehr geändert werden</i>
<div class="distinctly-input">
<?php
// Hole die ganzen Programmbezeichner raus aus der DB
$program_identifiers = $db->query("SELECT * FROM program_id_bits WHERE program_id = '" . $meta . "' ORDER BY position;");
while ($program_identifier = $program_identifiers->fetchArray(SQLITE3_ASSOC)) {
echo '<input type="text" maxlength="1" value="' . $program_identifier["value"] . '" disabled class="code-input">';
}
// Vorausgefüllte Programmbezeichnung
$description = ($row = $db->querySingle("SELECT program_description FROM programs WHERE id = '$meta' and plug_id = '$plug'", true)) ? $row['program_description'] : null;
?>
</div>
<hr />
<p>Beschreibung für dieses Programm</p>
<textarea id="program-description" value="Value" placeholder="Wählen Sie eine möglichst präzise Beschreibung. Zum Beispiel: 'Relaisgruppe für eine nicht isolierte Weiche, die eine einfache Kreuzungsweiche mit Bogenfahrt über den Plusstrang ist'"><?php echo $description; ?></textarea>
<hr />
<div class="save-button" onclick="save('program-editor','edit',<?php echo $number_bridges; ?>)"><img src="/vendor/icons/floppy-disk.svg" \>Speichern</div>
<?php
break;
}

131
pagecontent/selfcheck.php Executable file
View File

@@ -0,0 +1,131 @@
<?php
session_start();
error_reporting(E_ALL);
ini_set('display_errors', 1);
require("../functions.php");
$view = $_GET["view"];
$meta = $_GET["meta"];
$_SESSION["meta"] = $meta;
if ($view == "results") {
//Befehl zum Starten des Skripts
$command = escapeshellcmd('python3 /var/www/html/python/selfcheck.py');
//output und bridges sind die Ergebnisse des Durchlaufs
$output = json_decode(shell_exec($command), true);
$bridges = $output["data"];
//Zeigt schon der Erste durchlauf Fehler auf, kann abgebrochen werden (1. Fehlerfall)
if($output["logs"]["errorcode"] != 10) {
?>
<h3>
Die Prüfung konnte nicht abgeschlossen werden. <i>Fehler <?php echo $output["logs"]["errorcode"]; ?></i>
</h3>
<?php
include("../errorcodes.php");
die();
}
// Isoliere die Brücken und speichere sie als $bridges (hiermit wird später alles weitere verarbeitet)
$bridges = array_map(function($entry) {
return $entry[0];
}, $output["data"]);
//Isoliere die Fehlercodes der ersten Messung; da Messungen identisch reicht erste Messung
$errors = array_filter($output["data"], function($entry) {
return $entry[1] !== 0;
});
//Wenn Fehlerspeicher gefüllt: Ausgabe und Abbruch
if (!empty($errors)) {
measurementsContainErrors($output["data"]);
}
?>
<div class="content-header">Selbstdiagnose - Ergebnisse</div>
<hr />
<style>
table {
font-size: 12px;
line-height: 10px;
}
td {
width: 2.5%;
border: 1px solid black;
}
</style>
<hr>
<h2>Detailansicht</h2>
<p>Der Pin der jeweiligen Spalte wird eingeschaltet; Pins, welche einen Eingang registrieren, werden in der entsprechenden Zeile grün dargestellt.</p>
<table>
<?php
// Kopfzeile
echo "<tr>";
for ($j = 0; $j <= 32; $j++) {
$index = $j - 1;
if ($index == -1) {
echo "<td></td>";
} else {
echo "<td>$index</td>";
}
}
echo "</tr>";
// Matrix
for ($i = 0; $i <= 31; $i++) {
echo "<tr>";
echo "<td>$i</td>";
for ($j = 0; $j <= 31; $j++) {
$search = [$j, $i];
if (in_array($search, $bridges)) {
echo "<td style='color: green;'>&#10004;</td>";
} else {
echo "<td style='color: gray;'>-</td>";
}
}
echo "</tr>";
}
?>
</table>
<hr />
<?php
print("<p>Erklärung:<br>
Jede Verbindung darf nur einmal aufgeführt sein. Gibt es Doppelungen, so besteht eine unzulässige Verbindung zu einem anderen Pin.
Kann sich ein Pin nicht selbst auslesen, so weist dies auf einen internen Kabelbruch oder ein defektes Bauteil hin.
Ist der Fehler spiegelsymmetrisch (z.B. 10-20 und 20-10), deutet dies auf einen Kurzschluss im System hin.
Tritt der Fehler nur einseitig auf, ist ein Bauteil defekt.</p>");
?>
<div class="save-button" onclick="window.location.href='#selfcheck'; pageload();$('.save-button').html('<img src=\'/vendor/icons/load.gif\' \>Bitte warten...');">
<img src="/vendor/icons/nav-arrow-left.svg">Zurück
</div>
<?php
} else {
?>
<div class="content-header">Selbstdiagnose</div>
<p>Sollten während Messungen Fehler aufgetreten sein, kann hier eine interne Diagnose durchgeführt werden. Dabei wird jeder "Pin" einmalig als Ausgang gesetzt und gleichzeitig der Wert ausgelesen. Nur, wenn der Pin selbst gelesen werden kann und keine unzulässigen Verbindungen zu anderen Pins bestehen, wird die Prüfung erfolgreich abgeschlossen.</p>
<hr />
<p><i>Bitte trennen Sie zuvor alle Verbindungen, sodass sich das Gerät im Leerlauf befindet. <b>Angeschlossene Kabel und Stecker verfälschen das Ergebnis!</b></i></p>
<hr />
<div class="save-button" onclick="window.location.href='#selfcheck'; pageload('results');$('.save-button').html('<img src=\'/vendor/icons/load.gif\' \>Bitte warten...');">
<img src="/vendor/icons/ecg-monitoring-icon.svg">Selbstdiagnose ausführen
</div>
<?php
}
?>

122
pagecontent/start-cable.php Executable file
View File

@@ -0,0 +1,122 @@
<?php
session_start();
require("../functions.php");
error_reporting(E_ALL);
ini_set('display_errors', 1);
$db = new SQLite3('../db-test/test.db');
//Befehl zum Starten des Skripts
$command = escapeshellcmd('python3 /var/www/html/python/cable-check.py');
//output1 und bridges1 sind die Ergebnisse des ersten Durchlaufs
$output1 = json_decode(shell_exec($command), true);
$bridges1 = $output1["data"];
//Zeigt schon der Erste durchlauf Fehler auf, kann abgebrochen werden (1. Fehlerfall)
if($output1["logs"]["errorcode"] != 10) {
?>
<h3>
Die Prüfung konnte nicht abgeschlossen werden. <i>Fehler <?php echo $output1["logs"]["errorcode"]; ?></i>
</h3>
<?php
include("../errorcodes.php");
die();
}
//wenn bis hierhin ok: weitere Prüfung
$output2 = json_decode(shell_exec($command), true);
$bridges2 = $output2["data"];
//Die Messungen sind nicht identisch (egal ob wg Fehlercodes oder Daten, ungleiche Messungen sind immer schlecht) -> 2. Fehlerfall
//es wäre schon sehr eigenartig, wenn verschiedene Fehlercodes nacheinander ausgespuckt würden (Wackelkontakt?)
if($bridges1 !== $bridges2) {
measurementsDiffer($bridges1, $bridges2);
}
// Isoliere die Brücken und speichere sie als $bridges (hiermit wird später alles weitere verarbeitet)
$bridges = array_map(function($entry) {
return $entry[0];
}, $bridges1);
//Isoliere die Fehlercodes der ersten Messung; da Messungen identisch reicht erste Messung
$errors = array_filter($bridges1, function($entry) {
return $entry[1] !== 0;
});
//Wenn Fehlerspeicher gefüllt: Ausgabe und Abbruch
if (!empty($errors)) {
measurementsContainErrors($bridges1);
}
// ----------
//Ab hier wird die Logik geprüft, die Messung ist bis hierhin äußerlich in Ordnung
// ----------
$bridges = translateArray($bridges, $translationMap, $translationMap2);
$cleaned_output = [];
$errors = [];
$ok = true;
// iteriere durch die Messung und prüfe, ob stets gilt [[20,20],[22,22]]
//wenn nein: Kurzschluss ($errors)
foreach($bridges as $bridge) {
if($bridge[0] == $bridge[1]) {
$cleaned_output[] = $bridge[0];
}
else {
$ok = false;
$errors[] = $bridge;
}
}
//Haupt-Steckergrafik
$url = 'stecker.php?mode=fill&data=' . urlencode(json_encode($cleaned_output));
?>
<div class="content-header">Ergebnis</div>
<hr />
<h2>Vollständigkeit</h2>
<p><i>Die folgende Grafik zeigt Ihnen, auf welchen Adern eine durchgängige Verbindung erkannt wurde. Nur, wenn alle Pins grün gefüllt sind, ist das Spurkabel in Ordnung!</i></p>
<img src="<?php echo $url; ?>" />
<?php
$url = 'stecker.php?data=' . urlencode(json_encode($errors)); // Kodiert es für die URL
?>
<h2>Unzulässigkeiten</h2>
<p><i>Die folgende Grafik zeigt Ihnen, auf welchen Adern eine Verbindung zu einer anderen Ader erkannt wurde. Ist die Grafik leer, sind alle Adern korrekt isoliert.</i></p>
<?php
if($ok != true) {
?>
<p style='color: red !important'>
Es wurde mindestens ein Kurzschluss gefunden!
</p>
<?php
}
?>
<img src="<?php echo $url; ?>" />
<div class="save-button" onclick="window.location.href='#cable-check';"><img src="/vendor/icons/nav-arrow-left.svg" \>Zurück</div>

625
pagecontent/start.php Executable file
View File

@@ -0,0 +1,625 @@
<?php
session_start();
require("../functions.php");
error_reporting(E_ALL);
ini_set('display_errors', 1);
$db = new SQLite3('../db-test/test.db');
//Befehl zum Starten des Skripts
$command = escapeshellcmd('python3 /var/www/html/python/observer-full.py');
//output1 und bridges1 sind die Ergebnisse des ersten Durchlaufs
$output1 = json_decode(shell_exec($command), true);
$bridges1 = $output1["data"];
//Zeigt schon der Erste durchlauf Fehler auf, kann abgebrochen werden (1. Fehlerfall)
if($output1["logs"]["errorcode"] != 10) {
?>
<h3>
Die Prüfung konnte nicht abgeschlossen werden. <i>Fehler <?php echo $output1["logs"]["errorcode"]; ?></i>
</h3>
<?php
include("../errorcodes.php");
die();
}
//wenn bis hierhin ok: weitere Prüfung
$output2 = json_decode(shell_exec($command), true);
$bridges2 = $output2["data"];
//Die Messungen sind nicht identisch (egal ob wg Fehlercodes oder Daten, ungleiche Messungen sind immer schlecht) -> 2. Fehlerfall
//es wäre schon sehr eigenartig, wenn verschiedene Fehlercodes nacheinander ausgespuckt würden (Wackelkontakt?)
if($bridges1 !== $bridges2) {
measurementsDiffer($bridges1, $bridges2);
}
// Isoliere die Brücken und speichere sie als $bridges (hiermit wird später alles weitere verarbeitet)
$bridges = array_map(function($entry) {
return $entry[0];
}, $bridges1);
//Isoliere die Fehlercodes der ersten Messung; da Messungen identisch reicht erste Messung
$errors = array_filter($bridges1, function($entry) {
return $entry[1] !== 0;
});
//Wenn Fehlerspeicher gefüllt: Ausgabe und Abbruch
if (!empty($errors)) {
measurementsContainErrors($bridges1);
}
// ----------
//Ab hier wird die Logik geprüft, die Messung ist bis hierhin äußerlich in Ordnung
// ----------
//Hier wird jetzt geprüft, dass jede Messung bidirektional aufgenommen wurde, also 10<>20 UND 20<>10
$doubles = []; // Array zur Verfolgung von Doppelungen
$errors = []; // Array für fehlende Doppelungen
// Untersuche die einzelnen aufgenommenen Messpaare
foreach ($bridges as $pair) {
// Jedes Paar MUSS aus zwei Elementen bestehen
if (count($pair) !== 2) {
$errors[] = $pair; // speichere diesen fehlerhaften Eintrag
continue;
}
// Sortiere die Paare, um [23,14] und [14,23] gleich zu behandeln
sort($pair);
// Setze dieses Paar als "Index", um es gleich wieder zu finden
$key = implode("-", $pair);
// Prüfe, ob dieser Eintrag schon einmal da war
if (isset($doubles[$key])) {
$doubles[$key]++; // Verbindung erneut gefunden
}
else {
$doubles[$key] = 1; // Erstmaliges Auftreten
}
}
// Überprüfe, ob jede Verbindung genau zweimal vorhanden ist
foreach ($doubles as $key => $count) {
if ($count !== 2) {
$errors[] = $key;
}
}
// Abbruch bei fehlenden Doppelungen
if (!empty($errors)) {
echo "Die Messung wurde zwar erfolgreich abgeschlossen, die Messergebnisse überzeugten jedoch nicht.<br>Es ist notwendig, dass Kabelbrücken aus beiden Richtungen erkannt werden.<br>Erwägen Sie, eine erneute Messung durchzuführen. Sollte der Fehler erneut auftreten, ist ein interner Defekt des Prüfgeräts wahrscheinlich.<br><br>Die folgende(n) Kabelbrücke(n) konnte(n) nur one-way gemessen werden: <br>";
print_r($errors);
?>
<div class="save-button" onclick="window.location.href='#index'; pageload();$('.save-button').html('<img src=\'load.gif\' \>Bitte warten...');"><img src="/vendor/icons/nav-arrow-left.svg" \>Zurück</div>
<?php
die(); // Skript abbrechen
}
// ----------
//Die Messung ist valide. Es kann ausgegeben werden
// ----------
// Bereinigung der doppelten Einträge
$unique = []; // Array für eindeutige Verbindungen
$seen = []; // Array zur Verfolgung bereits bearbeiteter Verbindungen
foreach ($bridges as $pair) {
// Sortiere das Paar
sort($pair);
$key = implode("-", $pair);
// Füge nur ein Exemplar hinzu
if (!isset($seen[$key])) {
$unique[] = $pair;
$seen[$key] = true; // Markiere als verarbeitet
}
}
//Wichtigste Variable - sieht z.b. so aus: [[20,30],[10,15]] - redundanzfrei und aufsteigend sortiert
$measurement_result = translateArray($unique, $translationMap);
$measurement_result_number = count($measurement_result); // Anzahl gemessener Brücken
//Haupt-Steckergrafik
$url = 'stecker.php?data=' . urlencode(json_encode($measurement_result));
?>
<div class="content-header">Ergebnis</div>
<hr />
<div class="toggle-switch">
<div align="right"><p>Gesamtergebnis</p></div>
<div>
<label class="switch">
<input type="checkbox" onchange='$("#op-1").toggle(this.unchecked);$("#op-2").toggle(this.checked);'>
<span class="slider"></span>
</label>
</div>
<div align="left">
<p>Einzelbrücken</p>
</div>
</div>
<div id="op-1">
<img src="<?php echo $url; ?>" />
</div>
<div id="op-2" style="display: none;">
<?php
foreach($measurement_result as $bridge) {
// Hier muss der übertragene Wert unschön manuell in [] gesetzt werden, da ein äußeres, indexiertes Array erwartet wird...
?>
<img style="margin-bottom: 5px;" src="<?php echo 'stecker.php?data=' . urlencode("[" . json_encode($bridge) . "]"); ?>" />
<?php
}
?>
</div>
<hr />
<h2>Identifizierte Brücken</h2>
<?php
//Hole aktuellen Stecker aus der JSON-Datei
$settings = json_decode(file_get_contents("../settings.json"), true);
$plug = $settings["plug"];
$identified_bridges = [];
//Hole alle Nodes via JOIN aus der Dankebak
$nodes = $db->query("
SELECT
b.id AS bridge_id,
n.node_from,
n.node_to
FROM
bridges b
JOIN
nodes n ON b.id = n.required_by
ORDER BY
b.id
");
/*
Ausgabe sieht z.B. so aus:
| bridge_id | node_from | node_to |
| --------- | --------- | ------- |
| 1 | 20 | 30 |
| 1 | 13 | 17 |
| 2 | 10 | 15 |
*/
//die DB-ausgbe wird als indexiertes Array gespeichert, um einfacher durchsucht werden zu können
$bridges_db = [];
while ($row = $nodes->fetchArray(SQLITE3_ASSOC)) {
//Die Bridge-ID ist der Index, die geforderten Brücken werden im Standardformat eingefügt, z.B.: 1 => [[20,30],[13,17]]
$bridge_id = $row['bridge_id'];
$bridges_db[$bridge_id][] = [$row['node_from'], $row['node_to']];
}
//Vergleiche Datenbank mit gemessenen Werten
//iteriere durch alle Datenabnk-Brücken
foreach($bridges_db as $bridge_db => $required_nodes) {
$satisfied = true;
//iteriere durch alle von einer Brücke geforderten Node-Paare
foreach($required_nodes as $required_nodes_pairs) {
$innerMatch = 0;
// vergleiche die aktuelle Datenbank-Node mit allen gemessenen Nodes (+überkreuz)
for($i=0;$i<$measurement_result_number;$i++) {
if(($required_nodes_pairs[0] == $measurement_result[$i][0] && $required_nodes_pairs[1] == $measurement_result[$i][1] )||($required_nodes_pairs[1] == $measurement_result[$i][0] && $required_nodes_pairs[0] == $measurement_result[$i][1])){
// innerhalb der Node-zu-Node-Verbidnung wurde die Bedingung erfüllt
$innerMatch = 1;
}
}
if($innerMatch != 1) {
// An dieser Stelle wird satisfied direkt negiert, da mind. eine Abhängigkeit, die in der Datenbank gefordert ist, nicht gefunden wurde
$satisfied = false;
}
}
// Kommen alle Nodes vor, wird die Brücke mit ihrem entsprechenden Namen dargestellt
if($satisfied == true) {
//Brücke vollständig enthalten -> speichern für später (Programm-Erkennung)
$identified_bridges[] = $bridge_db;
//Erinnerung: es gilt bridge_db=>bridges_db
$url = '../stecker.php?data=' . urlencode(json_encode($bridges_db[$bridge_db])); // URL parsen
?>
<div class="row">
<?php
print('<div class="label"><span>' . $bridge_db . '</span></div>');
print('<img id="bridge-editor-img" src="' . $url . '" />');
?>
</div>
<?php
}
}
//Speicher für identifizierte Programme
$identified_programs = [];
//Hole Programme aus der Datenbank (via program_bridge_relation)
$programs = $db->query("
SELECT
p.id AS program_id,
p.program_description,
b.id AS bridge_id,
r.plug_id
FROM
program_bridge_relation r
JOIN programs p ON p.id = r.program_id
JOIN bridges b ON b.id = r.bridge_id
WHERE r.plug_id = '" . $plug . "'
");
/*
Ausgabe sieht z.B. so aus:
| program_id | program_description | bridge_id |
| ---------- | ------------------- | --------- |
| 1 | Weiche, die... | 1 |
| 2 | Gs, die... | 1 |
| 2 | Gs, die... | 10 |
*/
$bridges_db = [];
while ($row = $programs->fetchArray(SQLITE3_ASSOC)) {
// Gleiches vorgehen wie bei den Brücken: Programm-ID ist Index, Brücken sind Inhalt (2 => [1,10])
$program_id = $row['program_id'];
$programs_db[$program_id][] = $row['bridge_id'];
$program_descriptions[$program_id] = $row['program_description'];
}
//Ist eine Stelle der Kennung des Programmsteckers zwischendrin doppelt beschrieben woren, wird idse Variable wahr
$double_program_identifier = false;
?>
<hr />
<h2>Identifizierte Programme</h2>
<div class="plug-table">
<?php
$program_cache = ["?","?","?","?","?"]; // Die Steckerbezeichnung ist zu Beginn noch unbestimmt
//iteriere durch alle DB-Programme
foreach($programs_db as $program_db => $required_bridges) {
if(!empty($identified_bridges)) { //Abscicherung, sonst würde ein leeres Programm immer als vollständig enthalten markiert werden
$ok = 1;
}
else {
$ok = 0;
}
// Iteriere durch alle geforderten Brücken
foreach ($required_bridges as $bridge_id) {
// sollte sich eine Programmbrücke nicht unter den identifizierten Messwerte befinden -> ok=0
if (!in_array($bridge_id, $identified_bridges)) {
$ok = 0;
}
}
// Programm ist mit allen Brücken vollständig vertreten
if($ok == 1) {
?>
<div class="row">
<div class="label" style="width: 30%">
<span>
<?php
// Stelle die einzelnen Programmziffern dar, indemdiese aus der DB geladen werden
$program_identifiers = $db->query("SELECT * FROM program_id_bits WHERE program_id = '" . $program_db . "' ORDER BY position;");
while ($program_identifier = $program_identifiers->fetchArray(SQLITE3_ASSOC)) {
echo '<span class="label-char">' . $program_identifier["value"] . '</span>';
if($program_identifier["value"] != "" && $program_identifier["value"] != "-") { // wenn noch nicht anders beschrieben oder leer -> Einfügen in großen "Gesamtspeicher" für später
if($program_cache[$program_identifier["position"]] == "?") {
$program_cache[$program_identifier["position"]] = $program_identifier["value"];
}
else {
// Wenn Stelle schon gesetzt war -> Warnung speichern
$double_program_identifier = true;
}
}
$i++;
}
?>
</span>
</div>
<?php
$url = '../stecker.php?translate=true&data=' . urlencode(json_encode($required_bridges)); // URL parsen
print('<img id="bridge-editor-img" style="width: 55%" src="' . $url . '" />');
?>
</div>
<div class="row" style="position: relative;box-sizing: border-box;padding-left: 45px;">
<img style="max-height: 100%;width: auto;display: inline-block;filter: opacity(.5);position: absolute;left:10px" src="/vendor/icons/info-circle.svg" style=" top: 2.5px;left: 10px;">
<?php echo $program_descriptions[$program_db]; ?>
</div>
<?php
}
}
?>
</div>
<hr />
<h2>Kennungs-Nummer</h2>
<div class="plug-table" style="margin-bottom: 0;">
<div class="row">
<div class="label" style="width: 180px;float: none">
<span>
<?php
// Die Gesamt-Kennung-Nummer wurde zuvor von den identifizierten Programmen bestimmt und wird hier ausgegeben
$ok = 1;
foreach($program_cache as $cacheChar) {
echo '<span class="label-char">' . $cacheChar . '</span>';
if($cacheChar == "?") {
$ok = 0;
}
}
?>
</span>
</div>
</div>
</div>
<?php
if($ok != 1) {
?>
<p style='color: red !important'>
Wichtiger Hinweis: Es sind nicht alle Stellen der Kennungsnummer definiert. Dies deutet darauf hin, dass möglicherweise nicht alle Programme des Steckers korrekt erkannt wurden. Andererseits kann auch die Programm-Datenbank unvollständig sein, sodass eine Erkennung nicht möglich ist.<br>Betrachten Sie daher das Ergebnis mit Vorsicht!
</p>
<?php
}
if ($double_program_identifier == true) {
?>
<p style='color: red !important'>
Wichtiger Hinweis: Im Laufe der Programmabgleiche wurde versucht, eine Stelle der Gesamtkennungsnummer zu übeschreiben, da diese bereits durch ein vorheriges Programm festgelegt wurde. Details dazu finden Sie unter "Indentifizierte Programme". Bitte gleichen Sie die Spalten ab, ob eine doppelte Stellendefinition vorliegt!<br>Betrachten Sie daher das Ergebnis mit Vorsicht!
</p>
<?php
}
?>
<hr />
<h2>Identische Messungen</h2>
<div class="inventory-table">
<?php
//Hole vorherige Messungen aus der DB
$measurement_nodes_db = $db->query("
SELECT
m.id AS measurement_id,
m.comment AS comment,
m.timestamp AS timestamp,
m.place_name AS place_name,
n.node_from AS node_from,
n.node_to AS node_to
FROM
measurements m
JOIN places p ON p.name = m.place_name
JOIN measurement_nodes n ON m.id = n.required_by
ORDER BY m.id
");
/*
Ausgabe sieht z.B. so aus:
| measurement_id | comment | timestamp | place_name | node_from | node_to |
| -------------- | ------- | ---------- | -------------------- | --------- | ------- |
| 1 | abc | 1753357680 | Mannheim-Waldhof Wf | 20 | 26 |
| 1 | abc | 1753357680 | Mannheim-Waldhof Wf | 13 | 16 |
| 1 | abc | 1753357680 | Mannheim-Waldhof Wf | 30 | 35 |
| 2 | def | 1753357548 | Dudweiler Df | 30 | 35 |
| 2 | def | 1753357548 | Dudweiler Df | 10 | 12 |
*/
// Auch hier werden alle Messungen erstmal komplex indexiert (also zwei ineinander indexierte Arrays): measurement_id=>(nodes=>[[30,35],[10,12]], place_name, timestamp, comment)
$measurements_db = [];
while ($row = $measurement_nodes_db->fetchArray(SQLITE3_ASSOC)) {
$measurement_id = $row['measurement_id'];
$measurements_db[$measurement_id]['nodes'][] = [$row['node_from'],$row['node_to']];
$measurements_db[$measurement_id]['place_name'] = $row['place_name'];
$measurements_db[$measurement_id]['timestamp'] = $row['timestamp'];
$measurements_db[$measurement_id]['comment'] = $row['comment'];
}
//Indikator, dass alle Brücken gefunden wurden
$ok = 0;
foreach ($measurements_db as $measurement_db) {
$satisfied = true;
foreach ($measurement_result as $bridge_measure) {
$found = false;
foreach ($measurement_db["nodes"] as $measurement_db_node) {
// Prüfe, ob Brücke identisch (+ überkreuz)
if (
($bridge_measure[0] == $measurement_db_node[0] && $bridge_measure[1] == $measurement_db_node[1]) ||
($bridge_measure[0] == $measurement_db_node[1] && $bridge_measure[1] == $measurement_db_node[0])
) {
$found = true; // Brücke gefunden
break; // Nächste Brücke prüfen
}
}
if (!$found) {
// Diese Brücke nicht in DB gefunden -> Messung nicht erfüllt
$satisfied = false;
break; // weitere Brücken prüfen sinnlos
}
}
if ($satisfied) {
$ok = 1;
?>
<div class="row">
<img src="/vendor/icons/map-pin.svg" class="map">
<span>
<?php
echo $measurement_db["place_name"];
?>
</span>
<?php
if(!empty($measurement_db["comment"])) {
?>
<img src="/vendor/icons/info-circle.svg" style="top: 30px;" class="map">
<span>
<?php
echo $measurement_db["comment"];
?>
</span>
<?php
}
?>
<div class="plug-table" style="display: block;">
<div class="row" style="margin-bottom:0; height: 57px;">
<div>
<div class="label" style="width: 100%;height:auto;">
<span>
<?php
echo "Messung vom/um <b>" . date('d.m.Y H:i:s',$measurement_db["timestamp"]) . "</b>";
?>
</span>
</div>
</div>
</div>
</div>
</div>
<?php
}
}
?>
</div>
<?php
//Wenn keine Messungen gefunden:
if($ok == 0) {
?>
<p>Keine bekannt.</p>
<?php
}
?>
<hr />
<h2>Anpassen der Ergebnisse</h2>
<table class="simple-devider">
<tr>
<td>
<h3>Kennungs-Nr.</h3>
</td>
<td>
<div class="distinctly-input" style="width: 100%;">
<input oninput="distinctlyInput(1)" type="text" maxlength="1" value="<?php echo $program_cache[0]; ?>" class="code-input" id="distinctlyInput1">
<input oninput="distinctlyInput(2)" type="text" maxlength="1" value="<?php echo $program_cache[1]; ?>" class="code-input" id="distinctlyInput2">
<input oninput="distinctlyInput(3)" type="text" maxlength="1" value="<?php echo $program_cache[2]; ?>" class="code-input" id="distinctlyInput3">
<input oninput="distinctlyInput(4)" type="text" maxlength="1" value="<?php echo $program_cache[3]; ?>" class="code-input" id="distinctlyInput4">
<input oninput="distinctlyInput(5)" type="text" maxlength="1" value="<?php echo $program_cache[4]; ?>" class="code-input" id="distinctlyInput5">
</div>
</td>
</tr>
<tr>
<td>
<h3>Sekundärbrücken abwählen</h3>
</td>
<td>
<div class="program-field">
<?php
$i = 0;
$count = count($measurement_result);
foreach ( $measurement_result as $bridge) {
?>
<label class="checkbox-container">
<input checked onchange="excludeBridge(<?php echo $count; ?>)" type="checkbox" id="programSelect_<?php echo $i; ?>" value='[<?php echo $bridge[0] . "," . $bridge[1]; ?>]'>
<span class="checkbox-text">
<?php echo $bridge[0] . "-" . $bridge[1]; ?>
</span>
</label>
<?php
$i++;
} ?>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<img id="result-editor-result" src="../stecker.php" />
</td>
</tr>
<tr>
<td>
<h3>Besonderheiten</h3>
</td>
<td>
<textarea id="database-special" style="height: 80px;" placeholder="z.B. + Brücke 45, - Brücke 13"></textarea>
</td>
</tr>
</table>
<hr />
<h2>Speichern unter...?</h2>
<div class="search-container">
<input type="text" id="database-search-term" onkeyup="search('results')" placeholder="Beginnen Sie zu tippen..." />
<div class="save-button" onclick="save('inventory','add');"><img src="/vendor/icons/floppy-disk.svg" \></div>
<?php
// Speichern für "action/inventory.php"
$_SESSION["bridges"] = $measurement_result;
?>
<input type="hidden" id="database-bridges" value="<?php echo urlencode(json_encode($measurement_result)); ?>" />
<input type="hidden" id="database-bridges-hidden" value="" />
<div class="results"></div>
</div>
<p>
<i>Die dargestellten Vorschläge befinden sich bereits in der Datenbank. Geben Sie einen Ort sein, der noch nicht in der Datenbank vorhanden ist, so wird dieser automatisch erstellt</i>
</p>
<hr />
<div class="save-button" onclick="if (window.confirm('Das Messergebnis wird verworfen, sollte es nicht gespeichert worden sein. Fortfahren?')) { window.location.href='#index'; pageload();$('.save-button').html('<img src=\'load.gif\' \>Bitte warten...');}"><img src="/vendor/icons/nav-arrow-left.svg" \>Zurück</div>

124
pagecontent/stecker.php Executable file
View File

@@ -0,0 +1,124 @@
<?php
// Setze den Content-Type-Header für SVG
header('Content-Type: image/svg+xml');
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="500" height="100">
<defs>
<style>
@font-face {
font-family: LexendDecaBold;
src: url(http://192.168.37.103/vendor/fonts/LexendDeca-Bold.ttf);
}
* {
font-family: LexendDecaBold;
font-size: 14px;
}
</style>
</defs>
<text x="30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">30</text>
<text x="-30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">10</text>
<text x="30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">31</text>
<text x="-30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">11</text>
<path d="M500 0 L50 0" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M50 0 L0 25" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M0 25 L0 75" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M0 75 L50 100" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M50 100 L500 100" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M500 100 L500 0" style="stroke:black;stroke-width:5; fill:none;" />
<rect width="35" height="35" x="452" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="470" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="35" height="35" x="13" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="30" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="380" height="80" x="60" y="10" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<?php
$pin_coordinates = [
10 => [70, 68], 20 => [70, 44], 30 => [70, 20],
11 => [405, 68], 12 => [370, 68], 13 => [335, 68], 14 => [300, 68],
15 => [265, 68], 16 => [230, 68], 17 => [195, 68], 18 => [160, 68], 19 => [125, 68],
21 => [405, 44], 22 => [370, 44], 23 => [335, 44], 24 => [300, 44],
25 => [265, 44], 26 => [220, 44], 27 => [185, 44], 28 => [150, 44], 29 => [115, 44],
31 => [405, 20], 32 => [370, 20], 33 => [335, 20], 34 => [300, 20],
35 => [265, 20], 36 => [230, 20], 37 => [195, 20], 38 => [160, 20], 39 => [125, 20],
];
// Rechtecke einfärben, wenn "fill"-Modus aktiv ist
if ($_GET["mode"] == "fill") {
$data = $_GET["data"];
$data = json_decode(urldecode($data), true);
foreach ($pin_coordinates as $key => $coordinates) {
$fill = in_array($key, $data) ? "green" : "red";
echo '<rect width="25" height="10" x="' . $coordinates[0] . '" y="' . $coordinates[1] . '" rx="1" ry="1" style="stroke:black;stroke-width:3;fill:' . $fill . '" />';
}
} else {
// Nur Umrisse
foreach ($pin_coordinates as $coordinates) {
echo '<rect width="25" height="10" x="' . $coordinates[0] . '" y="' . $coordinates[1] . '" rx="1" ry="1" style="stroke:black;stroke-width:3;fill:none" />';
}
}
// SVG frühzeitig beenden, wenn kein Data vorhanden oder im fill-Modus
if (empty($_GET['data']) || $_GET["mode"] == "fill") {
print "</svg>";
die();
}
// Brückenverbindungen übersetzen, falls aktiviert
if ($_GET["translate"] == "true") {
$db = new SQLite3('db-test/test.db');
$data = json_decode($_GET["data"], true);
$plug = json_decode(file_get_contents("settings.json"), true)["plug"];
$nodes_raw = [];
foreach ($data as $bridge) {
$nodes = $db->query("SELECT node_from, node_to FROM nodes WHERE required_by = " . $bridge);
while ($node = $nodes->fetchArray(SQLITE3_ASSOC)) {
$nodes_raw[] = [$node['node_from'], $node['node_to']];
}
}
$inputArray = $nodes_raw;
} else {
$inputArray = json_decode($_GET['data'], true);
}
// Koordinaten übersetzen
$translatedArray = array_map(function ($pair) use ($pin_coordinates) {
return [
$pin_coordinates[$pair[0]],
$pin_coordinates[$pair[1]],
];
}, $inputArray);
$colors = ["#1a5fb4", "#26a269", "#e5a50a", "#c64600", "#a51d2d", "#613583", "#63452c", "#3d3846"];
// Linien ausgeben
$colorNumber = 0;
foreach ($translatedArray as $line) {
$point1 = $line[0];
$point2 = $line[1];
$point1x = $point1[0] + 10;
$point1y = $point1[1] + 5;
$point2x = $point2[0] + 10;
$point2y = $point2[1] + 5;
echo '<path d="M' . $point1x . ' ' . $point1y . ' L' . $point2x . ' ' . $point2y .
'" style="stroke:' . $colors[$colorNumber] . ';stroke-width:3; fill:none;" />';
$colorNumber = ($colorNumber + 1) % count($colors);
}
?>
</svg>

139
pagecontent/system.php Executable file
View File

@@ -0,0 +1,139 @@
<?php
$view = $_GET["view"];
switch($view) {
default: // Standardseite (Übersicht)
?>
<div class="content-header">System-Aktionen</div>
<hr />
<div class="save-button" style="background-color: #ffadad" onclick="if(confirm('Das System wird heruntergefahren. Fortfahren?') == true){pageload('shutdown')}"><img src="/vendor/icons/system-shut.svg" \>Herunterfahren</div>
<div class="save-button" style="background-color: #adcfff" onclick="if(confirm('Das System wird neu gestartet. Fortfahren?') == true){pageload('reboot')}"><img src="/vendor/icons/restart.svg" \>Neu starten</div>
<div class="save-button" onclick="pageload('info'); $(this).html('<img src=\'/vendor/icons/load.gif\' \>Sammle Systeminfos...');"><img src="/vendor/icons/info-circle.svg" \>Systeminfo</div>
<div class="save-button" onclick="pageload('wifi')"><img src="/vendor/icons/wifi.svg" \>WLAN-/DHCP-Konfig anzeigen</div>
<div class="save-button" onclick="window.location.href='#selfcheck';"><img src="/vendor/icons/ecg-monitoring-icon.svg" \>Selbstdiagnose</div>
<?php
break;
case "info":
?>
<div class="content-header">System-Aktionen: Sys-Info</div>
<hr />
<p>Kernel-Version:</p>
<?php
$output = shell_exec('uname -r');
echo "<pre align='left'>$output</pre>";
?>
<p>Betriebssystem:</p>
<?php
$output = shell_exec('cat /etc/os-release');
echo "<pre align='left'>$output</pre>";
?>
<p>System-Uptime:</p>
<?php
$output = shell_exec('uptime -p');
echo "<pre align='left'>$output</pre>";
?>
<p>Letzter Boot:</p>
<?php
$output = shell_exec('who -b');
echo "<pre align='left'>$output</pre>";
?>
<p>CPU-Infos:</p>
<?php
$output = shell_exec('cat /proc/cpuinfo');
echo "<pre align='left'>$output</pre>";
?>
<p>Arbeitsspeicher:</p>
<?php
$output = shell_exec('free -h');
echo "<pre align='left'>$output</pre>";
?>
<p>Speicherplatz:</p>
<?php
$output = shell_exec('df -h');
echo "<pre align='left'>$output</pre>";
?>
<p>Python-Version:</p>
<?php
$output = shell_exec('python3 --version');
echo "<pre align='left'>$output</pre>";
?>
<p>Top 5 Prozesse nach CPU-Auslastung:</p>
<?php
$output = shell_exec('ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 6');
echo "<pre align='left'>$output</pre>";
?>
<p>CPU-Temperatur:</p>
<?php
$output = shell_exec('vcgencmd measure_temp');
echo "<pre align='left'>$output</pre>";
?>
<p>System-Uhrzeit (PHP):</p>
<?php
setlocale(LC_TIME, 'de_DE.UTF-8'); // optional für deutsche Darstellung
date_default_timezone_set("Europe/Berlin"); // falls nötig
echo "<pre align='left'>" . strftime("%A, %e. %B %Y %H:%M:%S") . "</pre>";
break;
case "wifi":
?>
<div class="content-header">System-Aktionen: WLAN/DHCP</div>
<hr />
<p>Netzwerkverbindungen:</p>
<?php
$output = shell_exec('nmcli connection show');
echo "<pre align='left'>$output</pre>";
?>
<p>Verbundene Geräte:</p>
<?php
$output = shell_exec('ip neigh');
echo "<pre align='left'>$output</pre>";
?>
<p>Netzwerkschnittstellen:</p>
<?php
$output = shell_exec('nmcli device show');
echo "<pre align='left'>$output</pre>";
?>
<p>Ifconfig:</p>
<?php
$output = shell_exec('ifconfig');
echo "<pre align='left'>$output</pre>";
break;
case "reboot":
?>
<div class="content-header">System-Aktionen: Neustart</div>
<hr />
<p>Das Prüfgerät wird nun neu gestartet. Die Verbindung wird in Kürze getrennt.</p>
<p>Stellen Sie sicher, dass Sie sich im Anschluss wieder mit dem Geräte-WLAN verbinden.</p>
<?php
$output = shell_exec('sudo /sbin/reboot 2>&1');
echo "------- Fehlerprotokoll nach dieser Zeile -------<br> $output";
break;
case "shutdown":
?>
<div class="content-header">System-Aktionen: Herunterfahren</div>
<hr />
<p>Das Prüfgerät wird nun heruntergefahren. Die Verbindung wird in Kürze getrennt.</p>
<p>Auf Wiedersehen!</p>
<?php
$output = shell_exec('sudo /sbin/shutdown -h now 2>&1');
echo "------- Fehlerprotokoll nach dieser Zeile -------<br> $output";
break;
}

0
pagecontent/temp.json Executable file
View File

BIN
python/.plug-check.py.swp Normal file

Binary file not shown.

BIN
python/.selfcheck.py.swp Normal file

Binary file not shown.

230
python/cable-check.py Normal file
View File

@@ -0,0 +1,230 @@
import smbus
import time
import json
import sys
pruefer1 = 0x20
pruefer2 = 0x21
observer1 = 0x22
observer2 = 0x23
spurkabel1 = 0x24
spurkabel2 = 0x25
adressen = [pruefer1, pruefer2, observer1, observer2, spurkabel1, spurkabel2]
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPPUA = 0x0C # Eingang / Ausgang A
GPPUB = 0x0D # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
array = {}
logs = {}
data = []
try:
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
except:
logs["errorcode"] = 12
array["logs"] = logs
json_output = json.dumps(array)
print(json_output)
sys.exit(0)
def default():
#Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
for adresse in adressen:
#print("Setze " + str(adresse) + " in Register " + str(register) + " auf Wert 0x00")
bus.write_byte_data(adresse, register, 0x00)
#print("Hallo! Es wird damit begonnen, alle Register zurpckzusetzen...")
#Aktiviere alle Pullups und setze alle als Eingang
time.sleep(0.05)
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
#print("============= Reset erfolgreich! =============")
def main():
try:
default()
except:
logs["errorcode"] = 11
return
for pin in range(32): # 0 bis 31
aktuellAn = pin
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
time.sleep(0.05)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-A7
wert = 0xFF & ~(1 << pin) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUA, wert)
bus.write_byte_data(pruefer1, IODIRA, wert)
bus.write_byte_data(pruefer1, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 8) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUB, wert)
bus.write_byte_data(pruefer1, IODIRB, wert)
bus.write_byte_data(pruefer1, GPIOB, 0x00)
else:
# Setze den gewählten Pin auf HIGH
if pin < 24: # Pins A0-A7
wert = 0xFF & ~(1 << pin - 16) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUA, wert)
bus.write_byte_data(pruefer2, IODIRA, wert)
bus.write_byte_data(pruefer2, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 24) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUB, wert)
bus.write_byte_data(pruefer2, IODIRB, wert)
bus.write_byte_data(pruefer2, GPIOB, 0x00)
time.sleep(0.05)
##print(f"i2cget -y 1 0x20 0x12")
pruefer1_A_value = bus.read_byte_data(pruefer1, GPIOA)
pruefer1_B_value = bus.read_byte_data(pruefer1, GPIOB)
pruefer2_A_value = bus.read_byte_data(pruefer2, GPIOA)
pruefer2_B_value = bus.read_byte_data(pruefer2, GPIOB)
observer1_A_value = bus.read_byte_data(observer1, GPIOA)
observer1_B_value = bus.read_byte_data(observer1, GPIOB)
observer2_A_value = bus.read_byte_data(observer2, GPIOA)
observer2_B_value = bus.read_byte_data(observer2, GPIOB)
spurkabel1_A_value = bus.read_byte_data(spurkabel1, GPIOA)
spurkabel1_B_value = bus.read_byte_data(spurkabel1, GPIOB)
spurkabel2_A_value = bus.read_byte_data(spurkabel2, GPIOA)
spurkabel2_B_value = bus.read_byte_data(spurkabel2, GPIOB)
#print(f"------>>>>Es konnte vom pruefer1_A_value der Wert {format(pruefer1_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom pruefer1_B_value der Wert {format(pruefer1_B_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom pruefer2_A_value der Wert {format(pruefer2_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom pruefer2_B_value der Wert {format(pruefer2_B_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom observer1_A_value der Wert {format(observer1_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom observer1_B_value der Wert {format(observer1_B_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom observer2_A_value der Wert {format(observer2_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom observer2_B_value der Wert {format(observer2_B_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom spurkabel1_A_value der Wert {format(spurkabel1_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom spurkabel1_B_value der Wert {format(spurkabel1_B_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom spurkabel2_A_value der Wert {format(spurkabel2_A_value, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom spurkabel2_B_value der Wert {format(spurkabel2_B_value, '08b')} festgetsellt werden")
#print("===========================================================================")
for j in range(8): # Verarbeite 4*8=32 Pins + Jeweils 32 Observer- und Spurkabel-Daten
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
pruefer1_A_value_bit = bool(pruefer1_A_value & bitmaske)
pruefer1_B_value_bit = bool(pruefer1_B_value & bitmaske)
pruefer2_A_value_bit = bool(pruefer2_A_value & bitmaske)
pruefer2_B_value_bit = bool(pruefer2_B_value & bitmaske)
observer1_A_value_bit = bool(observer1_A_value & bitmaske)
observer1_B_value_bit = bool(observer1_B_value & bitmaske)
observer2_A_value_bit = bool(observer2_A_value & bitmaske)
observer2_B_value_bit = bool(observer2_B_value & bitmaske)
spurkabel1_A_value_bit = bool(spurkabel1_A_value & bitmaske)
spurkabel1_B_value_bit = bool(spurkabel1_B_value & bitmaske)
spurkabel2_A_value_bit = bool(spurkabel2_A_value & bitmaske)
spurkabel2_B_value_bit = bool(spurkabel2_B_value & bitmaske)
i = j
if spurkabel1_A_value_bit == False:
data.append([[pin ,i],0])
if pruefer1_A_value_bit == False and observer1_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_A_value_bit == True and observer1_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 8
if spurkabel1_B_value_bit == False:
data.append([[pin ,i],0])
if pruefer1_B_value_bit == False and observer1_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_B_value_bit == True and observer1_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 16
if spurkabel2_A_value_bit == False:
data.append([[pin ,i],0])
if pruefer2_A_value_bit == False and observer2_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_A_value_bit == True and observer2_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 24
if spurkabel2_B_value_bit == False:
data.append([[pin ,i],0])
if pruefer2_B_value_bit == False and observer2_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_B_value_bit == True and observer2_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
logs["errorcode"] = 10
if __name__ == "__main__":
try:
main()
except:
logs["errorcode"] = 13
array["logs"] = logs
array["data"] = data
json_output = json.dumps(array)
print(json_output)

219
python/observer-full.py Normal file
View File

@@ -0,0 +1,219 @@
import smbus
import time
import json
import sys
pruefer1 = 0x20
pruefer2 = 0x21
observer1 = 0x22
observer2 = 0x23
adressen = [pruefer1, pruefer2, observer1, observer2]
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPPUA = 0x0C # Eingang / Ausgang A
GPPUB = 0x0D # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
array = {}
logs = {}
data = []
try:
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
except:
logs["errorcode"] = 12
array["logs"] = logs
json_output = json.dumps(array)
print(json_output)
sys.exit(0)
def default():
#Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
for adresse in adressen:
#print("Setze " + str(adresse) + " in Register " + str(register) + " auf Wert 0x00")
bus.write_byte_data(adresse, register, 0x00)
#print("Hallo! Es wird damit begonnen, alle Register zurpckzusetzen...")
#Aktiviere alle Pullups und setze alle als Eingang
time.sleep(0.05)
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
#print("============= Reset erfolgreich! =============")
def main():
try:
default()
except:
logs["errorcode"] = 11
return
time.sleep(0.05)
for pin in range(32): # 0 bis 31
aktuellAn = pin
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
time.sleep(0.05)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-A7
wert = 0xFF & ~(1 << pin) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUA, wert)
bus.write_byte_data(pruefer1, IODIRA, wert)
bus.write_byte_data(pruefer1, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 8) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUB, wert)
bus.write_byte_data(pruefer1, IODIRB, wert)
bus.write_byte_data(pruefer1, GPIOB, 0x00)
else:
# Setze den gewählten Pin auf HIGH
if pin < 24: # Pins A0-A7
wert = 0xFF & ~(1 << pin - 16) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUA, wert)
bus.write_byte_data(pruefer2, IODIRA, wert)
bus.write_byte_data(pruefer2, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 24) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUB, wert)
bus.write_byte_data(pruefer2, IODIRB, wert)
bus.write_byte_data(pruefer2, GPIOB, 0x00)
time.sleep(0.05)
##print(f"i2cget -y 1 0x20 0x12")
pruefer1_A_value = bus.read_byte_data(pruefer1, GPIOA)
pruefer1_B_value = bus.read_byte_data(pruefer1, GPIOB)
pruefer2_A_value = bus.read_byte_data(pruefer2, GPIOA)
pruefer2_B_value = bus.read_byte_data(pruefer2, GPIOB)
observer1_A_value = bus.read_byte_data(observer1, GPIOA)
observer1_B_value = bus.read_byte_data(observer1, GPIOB)
observer2_A_value = bus.read_byte_data(observer2, GPIOA)
observer2_B_value = bus.read_byte_data(observer2, GPIOB)
#print(f"------>>>>Es konnte vom Pruefer der Wert {format(Wert_pruefer, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom Observer der Wert {format(Wert_observer, '08b')} festgetsellt werden")
for j in range(8): # Verarbeite 4*8=32 Pins + Jeweils 32 Observer-Daten
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
pruefer1_A_value_bit = bool(pruefer1_A_value & bitmaske)
pruefer1_B_value_bit = bool(pruefer1_B_value & bitmaske)
pruefer2_A_value_bit = bool(pruefer2_A_value & bitmaske)
pruefer2_B_value_bit = bool(pruefer2_B_value & bitmaske)
observer1_A_value_bit = bool(observer1_A_value & bitmaske)
observer1_B_value_bit = bool(observer1_B_value & bitmaske)
observer2_A_value_bit = bool(observer2_A_value & bitmaske)
observer2_B_value_bit = bool(observer2_B_value & bitmaske)
i = j
if pruefer1_A_value_bit == False and i != pin:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer1_A_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer1_A_value_bit == False and observer1_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_A_value_bit == True and observer1_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 8
if pruefer1_B_value_bit == False and i != pin:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer1_B_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer1_B_value_bit == False and observer1_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_B_value_bit == True and observer1_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 16
if pruefer2_A_value_bit == False and i != pin:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer2_A_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer2_A_value_bit == False and observer2_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_A_value_bit == True and observer2_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 24
if pruefer2_B_value_bit == False and i != pin:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer2_B_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer2_B_value_bit == False and observer2_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_B_value_bit == True and observer2_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
logs["errorcode"] = 10
if __name__ == "__main__":
try:
main()
except:
logs["errorcode"] = 13
array["logs"] = logs
array["data"] = data
json_output = json.dumps(array)
print(json_output)

127
python/observer.py Normal file
View File

@@ -0,0 +1,127 @@
import smbus
import time
import json
import sys
pruefer = 0x20
observer = 0x22
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPPUA = 0x0C # Eingang / Ausgang A
GPPUB = 0x0D # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
array = {}
logs = {}
data = []
try:
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
except:
logs["errorcode"] = 12
array["logs"] = logs
json_output = json.dumps(array)
print(json_output)
sys.exit(0)
def default():
#adressen = [pruefer]
# Schleife für alle Registeradressen (0x00 bis 0x15)
#for register in range(0x00, 0x16):
# for adresse in adressen:
# time.sleep(0.05)
# #print("Setze " + str(adresse) + " in Register " + str(register) + " auf Wert 0x00")
# bus.write_byte_data(adresse, register, 0x00)
#print("Hallo! Es wird damit begonnen, alle Register zurpckzusetzen...")
time.sleep(0.05)
bus.write_byte_data(pruefer, IODIRA, 0xFF)
bus.write_byte_data(observer, IODIRA, 0xFF)
bus.write_byte_data(pruefer, GPPUA, 0xFF)
bus.write_byte_data(observer, GPPUA, 0xFF)
#print("============= Reset erfolgreich! =============")
def main():
try:
default()
except:
logs["errorcode"] = 11
return
time.sleep(0.05)
for pin in range(8): # 0 bis 7
wert = 0xFF & ~(1 << pin) # Alle Bits auf 1, außer das eine gewünschte auf 0
#print("======== Setze Pin " + str(pin) + " auf den Wert " + format(wert, '08b'));
bus.write_byte_data(pruefer, GPPUA, wert)
bus.write_byte_data(pruefer, IODIRA, wert) # Setze einzeln als Ausgang 111011111 usw...
bus.write_byte_data(pruefer, GPIOA, 0x00) # Deaktiviere alle pins (es reagiert sowieso nur der Ausgang, dh keine Bitmaske erforderlich) 00000000 usw..
time.sleep(0.05)
##print(f"i2cget -y 1 0x20 0x12")
Wert_pruefer = bus.read_byte_data(pruefer, GPIOA)
Wert_observer = bus.read_byte_data(observer, GPIOA)
#print(f"------>>>>Es konnte vom Pruefer der Wert {format(Wert_pruefer, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom Observer der Wert {format(Wert_observer, '08b')} festgetsellt werden")
ok = 0
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
Bit_pruefer = bool(Wert_pruefer & bitmaske)
Bit_observer = bool(Wert_observer & bitmaske)
if Bit_observer == False: # Prüfe, ob aktueller Pin wirklich 0 sein darf
if j == pin:
ok = 1
if Bit_pruefer == False and j != pin:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if Bit_observer == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,j],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,j],1])
if Bit_pruefer == False and Bit_observer == True:
data.append([[pin ,j],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if Bit_pruefer == True and Bit_observer == False:
data.append([[pin ,j],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
if ok != 1:
#print("Fehler beim Aufruf von Pin " + str(pin) + "! Es konnte nicht bestätigt werden, dass dieser Pin tatsächlich LOW ist!");
data.append([[pin ,"???"],4])
logs["errorcode"] = 10
if __name__ == "__main__":
try:
main()
except:
logs["errorcode"] = 13
array["logs"] = logs
array["data"] = data
json_output = json.dumps(array)
print(json_output)

165
python/plug-check.py Normal file
View File

@@ -0,0 +1,165 @@
import smbus
import time
import json
# Adressen der beiden MCP23017 Chips
AusgangRechts = 0x20 # Ausgang rechte Hälfte
AusgangLinks = 0x21 # Ausgang linke Hälfte
EingangRechts = 0x24 # Eingang rechte Hälfte
EingangLinks = 0x23 # Eingang linke Hälfte
# Register-Adressen für den MCP23017
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
RechtsA_ausgang = ["Z", "AA", "W", "AB", "AC", "AD", "AE", "AF"]
RechtsB_ausgang = ["Q", "U", "V", "T", "S", "R", "Y", "X"]
RechtsA_eingang = ["R", "T", "V", "X", "Z", "AB", "AD", "AE"]
RechtsB_eingang = ["Q", "S", "W", "U", "AA", "Y", "AC", "AF"]
LinksA_ausgang = ["C", "D", "E", "J", "F", "G", "H", "I"]
LinksB_ausgang = ["A", "B", "P", "O", "K", "L", "M", "N"]
LinksA_eingang = ["B", "D", "F", "H", "J", "K", "M", "I"]
LinksB_eingang = ["A", "C", "E", "G", "L", "N", "O", "P"]
array = []
def default():
adressen = [AusgangRechts, AusgangLinks, EingangRechts, EingangLinks]
# Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
for adresse in adressen:
time.sleep(0.05)
print("Setze " + str(adresse) + " in Register " + str(register) + " auf Wert 0x00")
bus.write_byte_data(adresse, register, 0x00)
# Konfiguriere alle Pins auf Chip 1 als Ausgang (A und B)
def configure_chip1_as_output():
bus.write_byte_data(AusgangRechts, IODIRA, 0x00) # Setze alle Pins von A als Ausgang
bus.write_byte_data(AusgangRechts, IODIRB, 0x00) # Setze alle Pins von B als Ausgang
bus.write_byte_data(AusgangLinks, IODIRA, 0x00) # Setze alle Pins von A als Ausgang
bus.write_byte_data(AusgangLinks, IODIRB, 0x00) # Setze alle Pins von B als Ausgang
# Konfiguriere alle Pins auf Chip 2 als Eingang (A und B)
def configure_chip2_as_input():
bus.write_byte_data(EingangRechts, IODIRA, 0xFF) # Setze alle Pins von A als Eingang
bus.write_byte_data(EingangRechts, IODIRB, 0xFF) # Setze alle Pins von B als Eingang
bus.write_byte_data(EingangLinks, IODIRA, 0xFF) # Setze alle Pins von A als Eingang
bus.write_byte_data(EingangLinks, IODIRB, 0xFF) # Setze alle Pins von B als Eingang
# Hauptprogramm
def main():
default()
time.sleep(0.05)
configure_chip1_as_output()
time.sleep(0.05)
configure_chip2_as_input()
# Teste alle Pins auf Chip 1 (A0-A7, B0-B7)
for pin in range(32): # 0 bis 31
#print(f"Setze Pin {pin} auf HIGH auf Chip 1")
print("setze AusgangRechts GPIOA")
bus.write_byte_data(AusgangRechts, GPIOA, 0x00)
print("setze AusgangRechts GPIOB")
bus.write_byte_data(AusgangRechts, GPIOB, 0x00)
print("setze AusgangLinks GPIOA")
bus.write_byte_data(AusgangLinks, GPIOA, 0x00)
print("setze AusgangLinks GPIOB")
bus.write_byte_data(AusgangLinks, GPIOB, 0x00)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-A7
print("setze RechtsA_ausgang" + RechtsA_ausgang[pin])
bus.write_byte_data(AusgangRechts, GPIOA, 1 << pin)
aktuellAn = RechtsA_ausgang[pin]
else: # Pins B0-B7
print("setze RechtsB_ausgang" + RechtsB_ausgang[pin-8])
bus.write_byte_data(AusgangRechts, GPIOB, 1 << (pin - 8))
aktuellAn = RechtsB_ausgang[pin - 8]
else:
# Setze den gewählten Pin auf HIGH
if pin < 24: # Pins A0-A7
print("setze LinksA_ausgang" + LinksA_ausgang[pin-16])
bus.write_byte_data(AusgangLinks, GPIOA, 1 << pin - 16)
aktuellAn = LinksA_ausgang[pin - 16]
else: # Pins B0-B7
print("setze LinksB_ausgang" + LinksB_ausgang[pin-24])
bus.write_byte_data(AusgangLinks, GPIOB, 1 << (pin - 24))
aktuellAn = LinksB_ausgang[pin - 24]
print("====================" + aktuellAn + "==========================")
time.sleep(0.2) # Kurze Pause, damit die Änderung sichtbar wird
print("Lese Wert_Rechts_A")
Wert_Rechts_A = bus.read_byte_data(EingangRechts, GPIOA)
print(Wert_Rechts_A)
print("Lese Wert_Links_A")
Wert_Links_A = bus.read_byte_data(EingangLinks, GPIOA)
print(Wert_Links_A)
print("Lese Wert_Rechts_B")
Wert_Rechts_B = bus.read_byte_data(EingangRechts, GPIOB)
print(Wert_Rechts_B)
print("Lese Wert_Links_B")
Wert_Links_B = bus.read_byte_data(EingangLinks, GPIOB)
print(Wert_Links_B)
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
Bit_Rechts_A = bool(Wert_Rechts_A & bitmaske) # Isoliere das entsprechende Bit
Bit_Links_A = bool(Wert_Links_A & bitmaske) # Isoliere das entsprechende Bit
Bit_Rechts_B = bool(Wert_Rechts_B & bitmaske) # Isoliere das entsprechende Bit
Bit_Links_B = bool(Wert_Links_B & bitmaske) # Isoliere das entsprechende Bit
if Bit_Rechts_A == True:
if aktuellAn != RechtsA_eingang[j]:
array.append([aktuellAn ,RechtsA_eingang[j]])
print("Gefunden: " + RechtsA_eingang[j])
if Bit_Links_A == True:
if aktuellAn != LinksA_eingang[j]:
array.append([aktuellAn ,LinksA_eingang[j]])
print("Gefunden: " + LinksA_eingang[j])
if Bit_Rechts_B == True:
if aktuellAn != RechtsB_eingang[j]:
array.append([aktuellAn ,RechtsB_eingang[j]])
print("Gefunden: " + RechtsB_eingang[j])
if Bit_Links_B == True:
if aktuellAn != LinksB_eingang[j]:
array.append([aktuellAn ,LinksB_eingang[j]])
print("Gefunden: " + LinksB_eingang[j])
json_output = json.dumps(array)
print(json_output)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,75 @@
import smbus
import time
import json
# Adressen der beiden MCP23017 Chips
MP = 0x20 # Ausgang rechte Hälfte
# Register-Adressen für den MCP23017
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
def default():
# Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
bus.write_byte_data(MP, register, 0x00)
# Konfiguriere alle Pins auf Chip 1 als Ausgang (A und B)
def configure_chip1_as_output():
bus.write_byte_data(MP, IODIRA, 0x00) # Setze alle Pins von A als Ausgang
bus.write_byte_data(MP, IODIRB, 0x00) # Setze alle Pins von B als Ausgang
# Hauptprogramm
def main():
default()
time.sleep(0.5)
configure_chip1_as_output()
# Teste alle Pins auf Chip 1 (A0-A7, B0-B7)
for pin in range(32): # 0 bis 31
#print(f"Setze Pin {pin} auf HIGH auf Chip 1")
bus.write_byte_data(MP, GPIOA, 0x00)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-
bus.write_byte_data(MP, IODIRA, ~(1 << pin))
bus.write_byte_data(MP, GPIOA, 1 << pin)
print("====================" + str(pin) + "==========================")
time.sleep(0.2) # Kurze Pause, damit die Änderung sichtbar wird
wert = bus.read_byte_data(MP, GPIOA)
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
wert_bitweise = bool(wert & bitmaske) # Isoliere das entsprechende Bit
if wert_bitweise == True:
print("Gefunden: " + str(j))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,53 @@
import smbus
import time
import json
# Adressen der beiden MCP23017 Chips
MP = 0x20 # Ausgang rechte Hälfte
# Register-Adressen für den MCP23017
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
PULLUPA = 0x0C
PULLUPB = 0x0D
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
# Hauptprogramm
def main():
time.sleep(0.5)
for pin in range(8):
bus.write_byte_data(MP, IODIRA, ~(1 << pin))
time.sleep(0.02)
bus.write_byte_data(MP, PULLUPA, ~(1 << pin))
time.sleep(0.02)
bus.write_byte_data(MP, GPIOA, ~(1 << pin))
time.sleep(0.02)
print("====================" + str(pin) + "==========================")
wert = bus.read_byte_data(MP, GPIOA)
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j
wert_bitweise = bool(wert & bitmaske)
if wert_bitweise == False and j != pin:
print(str(j))
time.sleep(0.02) # Kurze Pause, damit die Änderung sichtbar wird
if __name__ == "__main__":
main()

219
python/selfcheck.py Normal file
View File

@@ -0,0 +1,219 @@
import smbus
import time
import json
import sys
pruefer1 = 0x20
pruefer2 = 0x21
observer1 = 0x22
observer2 = 0x23
adressen = [pruefer1, pruefer2, observer1, observer2]
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPPUA = 0x0C # Eingang / Ausgang A
GPPUB = 0x0D # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
array = {}
logs = {}
data = []
try:
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
except:
logs["errorcode"] = 12
array["logs"] = logs
json_output = json.dumps(array)
print(json_output)
sys.exit(0)
def default():
#Schleife für alle Registeradressen (0x00 bis 0x15)
for register in range(0x00, 0x16):
for adresse in adressen:
#print("Setze " + str(adresse) + " in Register " + str(register) + " auf Wert 0x00")
bus.write_byte_data(adresse, register, 0x00)
#print("Hallo! Es wird damit begonnen, alle Register zurpckzusetzen...")
#Aktiviere alle Pullups und setze alle als Eingang
time.sleep(0.05)
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
#print("============= Reset erfolgreich! =============")
def main():
try:
default()
except:
logs["errorcode"] = 11
return
time.sleep(0.05)
for pin in range(32): # 0 bis 31
aktuellAn = pin
for adresse in adressen:
bus.write_byte_data(adresse, IODIRA, 0xFF)
bus.write_byte_data(adresse, GPPUA, 0xFF)
bus.write_byte_data(adresse, IODIRB, 0xFF)
bus.write_byte_data(adresse, GPPUB, 0xFF)
time.sleep(0.05)
if pin < 16:
# Setze den gewählten Pin auf HIGH
if pin < 8: # Pins A0-A7
wert = 0xFF & ~(1 << pin) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUA, wert)
bus.write_byte_data(pruefer1, IODIRA, wert)
bus.write_byte_data(pruefer1, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 8) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer1, GPPUB, wert)
bus.write_byte_data(pruefer1, IODIRB, wert)
bus.write_byte_data(pruefer1, GPIOB, 0x00)
else:
# Setze den gewählten Pin auf HIGH
if pin < 24: # Pins A0-A7
wert = 0xFF & ~(1 << pin - 16) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUA, wert)
bus.write_byte_data(pruefer2, IODIRA, wert)
bus.write_byte_data(pruefer2, GPIOA, 0x00)
else: # Pins B0-B7
wert = 0xFF & ~(1 << pin - 24) # Alle Bits auf 1, außer das eine gewünschte auf 0
bus.write_byte_data(pruefer2, GPPUB, wert)
bus.write_byte_data(pruefer2, IODIRB, wert)
bus.write_byte_data(pruefer2, GPIOB, 0x00)
time.sleep(0.05)
##print(f"i2cget -y 1 0x20 0x12")
pruefer1_A_value = bus.read_byte_data(pruefer1, GPIOA)
pruefer1_B_value = bus.read_byte_data(pruefer1, GPIOB)
pruefer2_A_value = bus.read_byte_data(pruefer2, GPIOA)
pruefer2_B_value = bus.read_byte_data(pruefer2, GPIOB)
observer1_A_value = bus.read_byte_data(observer1, GPIOA)
observer1_B_value = bus.read_byte_data(observer1, GPIOB)
observer2_A_value = bus.read_byte_data(observer2, GPIOA)
observer2_B_value = bus.read_byte_data(observer2, GPIOB)
#print(f"------>>>>Es konnte vom Pruefer der Wert {format(Wert_pruefer, '08b')} festgetsellt werden")
#print(f"------>>>>Es konnte vom Observer der Wert {format(Wert_observer, '08b')} festgetsellt werden")
for j in range(8): # Verarbeite 4*8=32 Pins + Jeweils 32 Observer-Daten
bitmaske = 1 << j # Erstelle eine Maske: 1, 2, 4, 8, 16, ...
pruefer1_A_value_bit = bool(pruefer1_A_value & bitmaske)
pruefer1_B_value_bit = bool(pruefer1_B_value & bitmaske)
pruefer2_A_value_bit = bool(pruefer2_A_value & bitmaske)
pruefer2_B_value_bit = bool(pruefer2_B_value & bitmaske)
observer1_A_value_bit = bool(observer1_A_value & bitmaske)
observer1_B_value_bit = bool(observer1_B_value & bitmaske)
observer2_A_value_bit = bool(observer2_A_value & bitmaske)
observer2_B_value_bit = bool(observer2_B_value & bitmaske)
i = j
if pruefer1_A_value_bit == False:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer1_A_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer1_A_value_bit == False and observer1_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_A_value_bit == True and observer1_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 8
if pruefer1_B_value_bit == False:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer1_B_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer1_B_value_bit == False and observer1_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer1_B_value_bit == True and observer1_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 16
if pruefer2_A_value_bit == False:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer2_A_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer2_A_value_bit == False and observer2_A_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_A_value_bit == True and observer2_A_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
i = j + 24
if pruefer2_B_value_bit == False:
#print("Gefunden: " + str(pin) + " <> " + str(j));
if observer2_B_value_bit == False:
#print("Dies wurde vom Observer bestätigt!");
data.append([[pin ,i],0])
else:
#print("Konnte NICHT bestätigen!");
data.append([[pin ,i],1])
if pruefer2_B_value_bit == False and observer2_B_value_bit == True:
data.append([[pin ,i],2])
#print("Prüfer sagt NEIN, Observer sagt JA");
if pruefer2_B_value_bit == True and observer2_B_value_bit == False:
data.append([[pin ,i],3])
#print("Prüfer sagt JA, Observer sagt NEIN");
logs["errorcode"] = 10
if __name__ == "__main__":
try:
main()
except:
logs["errorcode"] = 13
array["logs"] = logs
array["data"] = data
json_output = json.dumps(array)
print(json_output)

53
python/test.py Normal file
View File

@@ -0,0 +1,53 @@
import smbus
import time
import json
# Adressen der beiden MCP23017 Chips
MP = 0x20 # Ausgang rechte Hälfte
# Register-Adressen für den MCP23017
IODIRA = 0x00 # Eingang / Ausgang A
IODIRB = 0x01 # Eingang / Ausgang B
GPIOA = 0x12 # GPIO A
GPIOB = 0x13 # GPIO B
PULLUPA = 0x0C
PULLUPB = 0x0D
# Initialisiere den I2C Bus
bus = smbus.SMBus(1)
# Hauptprogramm
def main():
time.sleep(0.5)
for pin in range(8):
bus.write_byte_data(MP, IODIRA, ~(1 << pin))
time.sleep(0.02)
bus.write_byte_data(MP, PULLUPA, ~(1 << pin))
time.sleep(0.02)
bus.write_byte_data(MP, GPIOA, ~(1 << pin))
time.sleep(0.02)
print("====================" + str(pin) + "==========================")
wert = bus.read_byte_data(MP, GPIOA)
for j in range(8): # Lese 4*8=32 Pins
bitmaske = 1 << j
wert_bitweise = bool(wert & bitmaske)
if wert_bitweise == False and j != pin:
print(str(j))
time.sleep(0.02) # Kurze Pause, damit die Änderung sichtbar wird
if __name__ == "__main__":
main()

364
reset.php Normal file
View File

@@ -0,0 +1,364 @@
<?php
$data = '{
"plugs": [
{
"name": "SpDr L30",
"id": "0",
"programCharNumber": "5"
},
{
"name": "SpDr S60",
"id": "1",
"programCharNumber": "6"
}
],
"bridges": [
{
"plugID": null,
"bridgeName": "3",
"nodes": [
[
"14",
"27"
]
]
},
{
"plugID": null,
"bridgeName": "4",
"nodes": [
[
"16",
"15"
],
null,
[
"15",
"23"
]
]
},
{
"plugID": null,
"bridgeName": "40",
"nodes": [
[
"15",
"13"
],
null,
null,
null,
null,
[
"30",
"33"
]
]
},
{
"plugID": "0",
"bridgeName": "68",
"nodes": [
[
"20",
"30"
],
[
"35",
"36"
]
]
},
{
"plugID": "0",
"bridgeName": "70",
"nodes": [
[
"34",
"24"
]
]
},
{
"plugID": "0",
"bridgeName": "72",
"nodes": [
[
"24",
"14"
]
]
},
{
"plugID": "0",
"bridgeName": "62",
"nodes": [
[
"33",
"23"
]
]
},
{
"plugID": "0",
"bridgeName": "65",
"nodes": [
[
"12",
"13"
]
]
},
{
"plugID": "0",
"bridgeName": "78",
"nodes": [
[
"31",
"21"
]
]
},
{
"plugID": "0",
"bridgeName": "80",
"nodes": [
[
"31",
"22"
]
]
}
],
"programs": [
{
"plugID": "0",
"programID": [
"V",
"-",
"-",
"-",
"-"
],
"bridges": [
"68"
],
"programDescription": "verschlossen werden"
},
{
"plugID": "0",
"programID": [
"-",
"1",
"-",
"-",
"0"
],
"bridges": [
"70",
"72"
],
"programDescription": "weder Kreuzungs- noch Zwieschutzweiche"
},
{
"plugID": "0",
"programID": [
"-",
"-",
"3",
"-",
"-"
],
"bridges": [
"62",
"65"
],
"programDescription": "Zugfahrt auf Hp1 nur \u00fcber Minusstrang stellbar"
},
{
"plugID": "0",
"programID": [
"-",
"-",
"-",
"5",
"-"
],
"bridges": [
"78",
"80"
],
"programDescription": "Zugfahrten auf Sh1 \u00fcber Weiche stellbar"
}
],
"places": [
{
"name": "Osterburken Of",
"placeID": "g7FhxA"
},
{
"name": "M\u00fchlacker Mf",
"placeID": "tFu7hD"
},
{
"name": "Mannheim-Waldhof Wf (Mwf)",
"placeID": "cqGe8D"
},
{
"name": "V\u00f6lklingen Vf",
"placeID": "2d9jzL"
},
{
"name": "G\u00f6ppingen Gf",
"placeID": "Hpg65B"
},
{
"name": "Geislingen (Steige) Gf",
"placeID": "sUfr3C"
},
{
"name": "B\u00f6blingen Bf",
"placeID": "mxgPY2"
},
{
"name": "Amstetten (W\u00fcrtt) Af",
"placeID": "SwZK2B"
},
{
"name": "Bierbach Bf",
"placeID": "u8FTYx"
},
{
"name": "Bondorf (b Herrenberg) Bf",
"placeID": "FKR5A9"
},
{
"name": "Gingen (Fils)",
"placeID": "5J6Zge"
},
{
"name": "Illingen (W\u00fcrtt) If",
"placeID": "rWCN6b"
},
{
"name": "Laupheim West Lf",
"placeID": "JUwSbY"
},
{
"name": "Orschweier Of (Orf)",
"placeID": "ha88hL"
},
{
"name": "T\u00fcrkism\u00fchle Tf",
"placeID": "63sKQa"
},
{
"name": "V\u00f6lklingen Saarstahl Walzwerk Ngf",
"placeID": "a9tSAR"
},
{
"name": "Dudweiler Df",
"placeID": "Fmk3fT"
},
{
"name": "Beckingen (Saar) Bf",
"placeID": "eQ7CAB"
},
{
"name": "G\u00e4rtringen Gf",
"placeID": "G9FBxL"
},
{
"name": null,
"placeID": "sxQjTF"
},
{
"name": "geislingen",
"placeID": "Xkp3Ak"
},
{
"name": "Meine Tasche",
"placeID": "PUEtdp"
},
{
"name": "Mein B\u00fcro",
"placeID": "vcY225"
},
{
"name": "Karlsruhe B\u00fcro",
"placeID": "Qf4lI8"
}
],
"measurements": [
{
"plugID": [
"0",
"1"
],
"place": "cqGe8D",
"programID": [
"V",
"1",
"3",
"5",
"0"
],
"bridges": [
[
"22",
"31"
],
[
"21",
"22"
],
[
"12",
"13"
],
[
"23",
"33"
],
[
"21",
"31"
],
[
"14",
"24"
],
[
"14",
"34"
],
[
"24",
"34"
],
[
"20",
"30"
],
[
"35",
"36"
],
[
"17",
"36"
]
],
"special": "+ Br\u00fccke 56",
"timestamp": 1740400487
}
]
}';
file_put_contents("database.json", $data)
?>

502
script.js Normal file
View File

@@ -0,0 +1,502 @@
function distinctlyInput(input) {
// Wenn das aktuell gewählte Feld gefüllt wird
if ($('#distinctlyInput' + input).val().length >= 1) {
input++; // Fahre fort mit dem nächsten Feld
if ($('#distinctlyInput' + input).length) {
$('#distinctlyInput' + input).focus().select(); // Fokus auf das nächste Feld setzen
}
}
// Wenn das aktuell gewählte Feld geleert wird
else if ($('#distinctlyInput' + input).val().length === 0 && input > 1) {
input--; // Gehe zum vorherigen Feld
$('#distinctlyInput' + input).focus().select(); // Fokus auf das vorherige Feld setzen
}
}
function pageload(view, meta) { // Funktion zum Laden des Inhalts
secondaryNavigation("close"); // Schließe das Stecker-Menü
page = window.location.hash.substr(1); // Hole die aufzurufende Seite über den URL-Hash (#xy)
if(page.length === 0) {
page = "index";
}
var loadingTimer = setTimeout(function() {
$("#content").addClass("loading");
message("Die Seite befindet sich im Aufbau...");
}, 2000); // 2 Sekunden warten
$.ajax({
url: "/pagecontent/" + page + ".php?view=" + view + "&meta=" + meta, // Pfad der zu ladenden Seite aufbauen
error: function(jqXHR, textStatus, errorThrown) { // Wenn ein Fehler bei der Anfrage auftritt (z.B. Netzwerkprobleme)
message("Es ist ein unerwarteter Fehler aufgetreten. Der Seiteninhalt konnte nicht geladen werden!");
},
success: function(result){ // Abfrage erfolgreich
clearTimeout(loadingTimer);
$("#content").removeClass("loading");
$("#content").html(result);
$("div.navigation>div").removeClass("active"); // Alle Buttons inaktiv
$("#" + page).addClass("active"); // Button mit Klasse "page" wird aktiv geschaltet (im Menü)
}});
}
function checkForReload(page) {
page_hash = window.location.hash.substr(1);
if(page_hash == page) {
pageload();
}
}
function checkConnection(last) { // Verbindunsstatus überwachen // Last ist der letzte gemessene Netzwerkzustand
$.ajax({
url: "/actions/connection-test.php?a=" + Date.now(), // verändere idr URL leicht, damit der Server nicht cached
timeout: 5000, // maximale Wartezeit sind 5 Sekunden; alles darüber wird als zu unzuverlässig angesehen
error: function(jqXHR, textStatus, errorThrown) { // Wenn ein Fehler bei der Anfrage auftritt (z.B. Netzwerkprobleme)
$(".navigation-footer").html('<div class="connection-indicator false"></div>Keine Verbindung<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
console.error("Fehler bei der Anfrage:", textStatus, errorThrown);
if (last != 1) { // letzter Zustand war "online" // Vermeiden von ständigen Nachrichten
message("Keine Verbindung"); // Werfe eine Nachricht
}
setTimeout(function() { // nächste Prüfung in 5 Sekunden
checkConnection(1); // der Status offline wird mit übertragen
}, 5000);
},
success: function(data) { // Verbindung iO
setTimeout(function() { // nächste Prüfung in 5 Sekunden
checkConnection();
}, 5000);
if (data == 'true') { // der Rückgabewert ist true, alles IO
$(".navigation-footer").html('<div class="connection-indicator true"></div>Verbunden<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
if (last == 1) { // war der letzte Status noch "offline", wird der Nutzer benachrichtigt
message("Verbindung wiederhergestellt!");
}
} else { // außergewöhnlicher Fehler, aber möglich
message("Keine Verbindung"); // Verbindung dennoch unbrauchbar, wenn keine Daten geladen werden
$(".navigation-footer").html('<div class="connection-indicator false"></div>Keine Verbindung<img class="infrago-logo" src="/vendor/DB_InfraGo_logo_red_black_100px_rgb.svg" width="100%" />');
}
}
});
}
var pinRowNumber = 0;
function connectPins(startPoint, endPoint, origin) {
const encoded = $('#bridge-editor-img').attr("meta");
const decoded = decodeURIComponent(encoded);
let myArray = JSON.parse(decoded);
console.log(myArray);
length = myArray.length;
lengthNext = length + 1;
lengthPrev = length -1 ;
myArray[length] = [];
myArray[length][0] = startPoint;
myArray[length][1] = endPoint;
$(".pin-table").append('<div class="row" id="pinRow_' + length + '"><span>' + startPoint + '</span><img src="/vendor/icons/data-transfer-both.svg" height="30px"/><span>' + endPoint + '</span><img style="transform: rotate(0deg); padding-top: 17.5px;" src="/vendor/icons/trash.svg" onclick="removePin(' + length + ')" height="30px"/></div>');
var myJsonStringURL = encodeURI(JSON.stringify(myArray));
$('#bridge-editor-img').attr("meta", myJsonStringURL);
$('#bridge-editor-img').attr("style", "background-image: url('../stecker.php?data=" + myJsonStringURL + "')");
$('#bridge-editor-edit').attr("value", pinRowNumber);
}
function removePin(row) {
const encoded = $('#bridge-editor-img').attr("meta");
const decoded = decodeURIComponent(encoded);
let myArray = JSON.parse(decoded);
myArray[row] = [];
myArray[row][0] = "";
myArray[row][1] = "";
$("#pinRow_" + row).hide();
var myJsonStringURL = encodeURI(JSON.stringify(myArray));
$('#bridge-editor-img').attr("meta", myJsonStringURL);
$('#bridge-editor-img').attr("style", "background-image: url('../stecker.php?data=" + myJsonStringURL + "')");
$('#bridge-editor-edit').attr("value", pinRowNumber);
}
function programBridge(number) { // Checkboxen für die Auswahl der Brücken im Programm-Editor
let myArray = []; // initialisiere Array
index = 0;
for (i = 0; i <= number; i++) { // iteriere durch alle Checkboxen
if ($('#programSelect_' + i).is(":checked")) { // wenn gesetzt
myArray[index] = $('#programSelect_' + i).val(); // Hänge die Brücken-ID dem Programm an
index++;
}
}
var myJsonString = JSON.stringify(myArray); // kreiere URL
$('#program-editor-img').attr("src", "stecker.php?translate=true&data=" + myJsonString); // aktualisiere das Bild
console.log("OK");
}
function excludeBridge(number) { // Checkboxen für die Auswahl der Brücken im Programm-Editor
let myArray = []; // initialisiere Array
let myArraySubtrahend = []; // initialisiere Array
index = 0;
indexSubtrahend = 0;
for (i = 0; i < number; i++) { // iteriere durch alle Checkboxen
if ($('#programSelect_' + i).is(":checked")) { // wenn gesetzt
myArray[index] = JSON.parse($('#programSelect_' + i).val());
index++;
}
else {
myArraySubtrahend[indexSubtrahend] = JSON.parse($('#programSelect_' + i).val());
indexSubtrahend++;
}
}
var myJsonString = JSON.stringify(myArray); // kreiere URL
var myJsonStringSubtrahend = JSON.stringify(myArraySubtrahend); // kreiere URL
$('#result-editor-result').attr("src", "stecker.php?data=" + myJsonString); // aktualisiere das Bild
$('#database-bridges-hidden').attr("value", encodeURI(myJsonStringSubtrahend));
console.log("OK");
}
function search(page,special) { // Datenbanksuche
value = $('#database-search-term').val(); // Suchbegriff, wenn die Datenbank durchsucht wird
if(page == 'results') {
if (special == "x") { // die Vorschläge werden ignoriert
value = ""; // Lösche Eingabe virtuell
}
}
$.get("/pagecontent/database-search.php", {
meta: value, // Suchbegriff
view: page // je nachdem, von wo aus die Suche ausgelöst wurde
},
function(data, status) {
if (status == "success") {
if(page == 'results') { // je nachdem, von wo aus die Suche ausgelöst wurde, anderen Container verändern
$("div.search-container div.results").html(data);
}
if(page == 'program') {
$("div.inventory-table").html(data);
}
}
});
}
function save(page, action, meta) { // prinzipiell gleiche Funktion wie getcontent, jedoch mit Abfragen von JS-Werten
suppress_page_reload = 0;
if (page == "bridge-editor") {
if (action == "edit" || action == "add") {
number_entry = $('#bridge-editor-edit').val(); // von PHP fixierter Wert (Anzahl der zu Beginn vorhandenen Felder)
let myArray = {}; // Initialisiere myArray als Objekt
const encoded = $('#bridge-editor-img').attr("meta");
const decoded = decodeURIComponent(encoded);
myArray["pins"] = JSON.parse(decoded);
if (action == "add") { // wird die Brücke neu angelegt, muss ein Name gewählt werden
myArray["name"] = prompt("Bitte noch einen Namen festlegen (nur Zahlen):");
}
var myJsonString = JSON.stringify(myArray); // verpacke alle Pin-Brücken als JSON
meta = myJsonString;
}
}
if (page == "program-editor") {
if (action == "edit" || action == "add") {
let myArray = {}; // Initialisiere als leeres, multidimensionales, indexiertes Array
myArray["bridges"] = []; // Initialisiere als leeres Array
index = 0;
for (i = 0; i <= meta; i++) {
if ($('#programSelect_' + i).is(":checked")) {
myArray["bridges"][index] = $('#programSelect_' + i).val();
index++;
}
}
if (action == "add") {
myArray["programID"] = [];
for (i = 0; i <= 4; i++) {
j = i + 1;
myArray["programID"][i] = $('#distinctlyInput' + j).val().toUpperCase();
}
}
myArray["programDescription"] = $('#program-description').val();
console.log(myArray);
var myJsonString = JSON.stringify(myArray);
console.log(myJsonString); // Zeigt die JSON-Ausgabe in der Konsole an
meta = myJsonString;
}
}
if (page == "inventory" && action == "add") {
let myArray = {};
myArray["name"] = $('#database-search-term').val();
myArray["bridges-hidden"] = $('#database-bridges-hidden').val();
myArray["special"] = $('#database-special').val();
myArray["programCache"] = [];
for (i = 0; i <= 4; i++) {
j = i + 1;
myArray["programCache"][i] = $('#distinctlyInput' + j).val().toUpperCase();
}
console.log(myArray);
var myJsonString = JSON.stringify(myArray);
console.log(myJsonString); // Zeigt die JSON-Ausgabe in der Konsole an
meta = myJsonString;
suppress_page_reload = 1;
}
console.log("/actions/" + page + ".php?action=" + action + "&meta=" + meta);
$.get("/actions/" + page + ".php", {
action: action,
meta: meta
},
function(data, status) {
if (status == "success") {
data = JSON.parse(data);
if (data[0] == true) {
if (suppress_page_reload != 1) {
window.location.href = "#" + page;
pageload();
}
message(data[1]);
} else {
message(data[1]);
}
}
});
}
function secondaryNavigation(animation, content) {
if (animation == "open") {
$.get("/pagecontent/plug-select.php", {
},
function(data, status) {
if (status == "success") {
if (data != 'false') {
$("#navigation-secondary").css("left", "250px");
$(".navigation-header").attr("onclick", "secondaryNavigation('close','plug-select')");
setTimeout(function() {
$("#navigation-secondary").html(data);
console.log(data);
}, 200);
} else {
window.location.href = "#";
alert("Die Inhalte konnten nicht abgerufen werden. Vergewissern Sie sich, dass Sie mit dem WLAN-Netzwerk verbunden sind und laden Sie bei Bedarf die Seite neu.");
}
}
});
} else {
$("#navigation-secondary").css("left", "-250px");
$(".navigation-header").attr("onclick", "secondaryNavigation('open','plug-select')");
}
}
function changeSettings(type, value, meta) {
$.get("/actions/save-settings.php", {
type: type,
value: value,
meta: meta
},
function(data, status) {
if (status == "success") {
if (data == 'true') {
applySettings(type);
pageload();
} else {
window.location.href = '#index';
pageload();
alert("Die Inhalte konnten nicht abgerufen werden. Vergewissern Sie sich, dass Sie mit dem WLAN-Netzwerk verbunden sind und laden Sie bei Bedarf die Seite neu.");
}
}
});
}
function applySettings(type) {
secondaryNavigation('close');
$.get("/actions/apply-settings.php", {
type: type
},
function(data, status) {
if (status == "success") {
if (data != 'false') {
if (type == "plug") {
$(".navigation-header>div").html(data);
}
} else {
window.location.href = "#";
alert("Die Inhalte konnten nicht abgerufen werden. Vergewissern Sie sich, dass Sie mit dem WLAN-Netzwerk verbunden sind und laden Sie bei Bedarf die Seite neu.");
}
}
});
}
function message(content) {
$("#message-box").html(content);
$("#message-box").css("display", "block");
setTimeout(function() {
$("#message-box").css("transform", "translateY(0)");
}, 50);
setTimeout(function() {
setTimeout(function() {
$("#message-box").css("display", "none");
}, 500);
$("#message-box").css("transform", "translateY(calc(100% + 5px))");
}, 3000);
}
$(window).bind('beforeunload', function() {
$("#warning").css("display", "block");
return 'Are you sure you want to leave?';
});

1
settings.json Normal file
View File

@@ -0,0 +1 @@
{"plug":"0"}

124
stecker.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
// Setze den Content-Type-Header für SVG
header('Content-Type: image/svg+xml');
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="500" height="100">
<defs>
<style>
@font-face {
font-family: LexendDecaBold;
src: url(http://192.168.37.103/vendor/fonts/LexendDeca-Bold.ttf);
}
* {
font-family: LexendDecaBold;
font-size: 14px;
}
</style>
</defs>
<text x="30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">30</text>
<text x="-30" y="5" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">10</text>
<text x="30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">31</text>
<text x="-30" y="405" fill="black" text-anchor="middle" transform="translate(50,50) rotate(-90)">11</text>
<path d="M500 0 L50 0" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M50 0 L0 25" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M0 25 L0 75" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M0 75 L50 100" style="stroke:black;stroke-width:3; fill:none;" />
<path d="M50 100 L500 100" style="stroke:black;stroke-width:5; fill:none;" />
<path d="M500 100 L500 0" style="stroke:black;stroke-width:5; fill:none;" />
<rect width="35" height="35" x="452" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="470" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="35" height="35" x="13" y="32" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<circle r="8" cx="30" cy="50" fill="none" style="stroke:black;stroke-width:3;" />
<rect width="380" height="80" x="60" y="10" rx="5" ry="5" style="stroke:black;stroke-width:3;fill:none" />
<?php
$pin_coordinates = [
10 => [70, 68], 20 => [70, 44], 30 => [70, 20],
11 => [405, 68], 12 => [370, 68], 13 => [335, 68], 14 => [300, 68],
15 => [265, 68], 16 => [230, 68], 17 => [195, 68], 18 => [160, 68], 19 => [125, 68],
21 => [405, 44], 22 => [370, 44], 23 => [335, 44], 24 => [300, 44],
25 => [265, 44], 26 => [220, 44], 27 => [185, 44], 28 => [150, 44], 29 => [115, 44],
31 => [405, 20], 32 => [370, 20], 33 => [335, 20], 34 => [300, 20],
35 => [265, 20], 36 => [230, 20], 37 => [195, 20], 38 => [160, 20], 39 => [125, 20],
];
// Rechtecke einfärben, wenn "fill"-Modus aktiv ist
if ($_GET["mode"] == "fill") {
$data = $_GET["data"];
$data = json_decode(urldecode($data), true);
foreach ($pin_coordinates as $key => $coordinates) {
$fill = in_array($key, $data) ? "green" : "red";
echo '<rect width="25" height="10" x="' . $coordinates[0] . '" y="' . $coordinates[1] . '" rx="1" ry="1" style="stroke:black;stroke-width:3;fill:' . $fill . '" />';
}
} else {
// Nur Umrisse
foreach ($pin_coordinates as $coordinates) {
echo '<rect width="25" height="10" x="' . $coordinates[0] . '" y="' . $coordinates[1] . '" rx="1" ry="1" style="stroke:black;stroke-width:3;fill:none" />';
}
}
// SVG frühzeitig beenden, wenn kein Data vorhanden oder im fill-Modus
if (empty($_GET['data']) || $_GET["mode"] == "fill") {
print "</svg>";
die();
}
// Brückenverbindungen übersetzen, falls aktiviert
if ($_GET["translate"] == "true") {
$db = new SQLite3('db-test/test.db');
$data = json_decode($_GET["data"], true);
$plug = json_decode(file_get_contents("settings.json"), true)["plug"];
$nodes_raw = [];
foreach ($data as $bridge) {
$nodes = $db->query("SELECT node_from, node_to FROM nodes WHERE required_by = " . $bridge);
while ($node = $nodes->fetchArray(SQLITE3_ASSOC)) {
$nodes_raw[] = [$node['node_from'], $node['node_to']];
}
}
$inputArray = $nodes_raw;
} else {
$inputArray = json_decode($_GET['data'], true);
}
// Koordinaten übersetzen
$translatedArray = array_map(function ($pair) use ($pin_coordinates) {
return [
$pin_coordinates[$pair[0]],
$pin_coordinates[$pair[1]],
];
}, $inputArray);
$colors = ["#1a5fb4", "#26a269", "#e5a50a", "#c64600", "#a51d2d", "#613583", "#63452c", "#3d3846"];
// Linien ausgeben
$colorNumber = 0;
foreach ($translatedArray as $line) {
$point1 = $line[0];
$point2 = $line[1];
$point1x = $point1[0] + 10;
$point1y = $point1[1] + 5;
$point2x = $point2[0] + 10;
$point2y = $point2[1] + 5;
echo '<path d="M' . $point1x . ' ' . $point1y . ' L' . $point2x . ' ' . $point2y .
'" style="stroke:' . $colors[$colorNumber] . ';stroke-width:3; fill:none;" />';
$colorNumber = ($colorNumber + 1) % count($colors);
}
?>
</svg>

553
style3.css Normal file
View File

@@ -0,0 +1,553 @@
@font-face {
font-family: LexendDecaBold;
src: url(/vendor/fonts/LexendDeca-Bold.ttf);
}
@font-face {
font-family: LexendDecaMedium;
src: url(/vendor/fonts/LexendDeca-Medium.ttf);
}
body {
height: 100%;
width: 100%;
padding: 20px;
margin: 0;
box-sizing: border-box;
font-family: LexendDecaBold;
font-weight: light;
color: #2e3436;
}
div.main {
width: 100%;
max-width: 900px;
height: auto;
border-radius: 15px;
overflow: hidden;
margin: 0 auto;
border: 5px solid #c3c3c3;
position: relative;
}
div.navigation {
width: 250px;
height: 600px;
background-color: #ebebeb;
float: left;
display: inline-block;
position: relative;
z-index: 501;
}
div.navigation.secondary {
position: absolute;
left: -250px;
top: 0;
overflow-y: auto;
z-index: 500;
transition-duration: .6s;
width: 500px;
backdrop-filter: blur(10px);
background-color: #ebebeb8c;
}
div.navigation.secondary img {
width: 100px !important;
}
div.navigation>div, div.save-button {
padding: 5px;
margin: 10px;
line-height: 30px;
transition-duration: .4s;
cursor: pointer;
border-radius: 10px;
}
div.navigation>div:hover,div.navigation>div.active,div.navigation-footer,div.navigation-header,div.save-button:hover {
background-color: #d2d2d2;
}
div.navigation div.navigation-header {
text-align: center;
}
div.navigation div.navigation-header>div {
background-color: #fff;
border-radius: 0 0 7px 7px;
color: grey;
line-height: 20px;
font-size: 12px;
}
div.navigation>div>img:not(.infrago-logo),div.content div.save-button>img {
width: 30px;
max-height: 30px;
margin-right:20px;
vertical-align:middle;
filter: contrast(0.3);
}
div.navigation>div>img.infrago-logo {
width: 100%;
display:block;
margin-top: 15px;
}
div.content {
width: calc(100% - 250px);
float: right;
display: inline-block;
padding: 15px 20px;
height: 600px;
box-sizing: border-box;
overflow-y: auto;
text-align: center;
}
div.content>img.loader {
position: absolute;
height: 35%;
width: 35%;
left: 37.5%;
top: 37.5%;
display: none;
}
div#content {
transition-duration: .5s;
}
div#content.loading {
filter: blur(4px);
}
div#content.loading>img.loader {
display: block;
}
div.content div.content-header {
text-align: center;
line-height: 30px;
padding: 0;
margin-bottom: 20px;
display: block;
font-size: 2em;
margin-block-start: 0.67em;
margin-block-end: 0.67em;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-weight: bold;
unicode-bidi: isolate;
}
div.content img {
width: 100%;
margin: 0 auto;
display: block;
}
div.navigation-footer {
position: absolute;
bottom:0;
width: calc(100% - 40px);
padding: 10px !important;
cursor: auto !important;
}
div.connection-indicator {
height: 15px;
width: 15px;
border-radius: 100%;
float: left;
vertical-align:middle;
margin: 7.5px;
}
div.connection-indicator.true {
background-color: #2ec27e;
}
div.connection-indicator.unknown {
background-color: #f6d32d;
}
div.connection-indicator.false {
background-color: #ed333b;
}
div.pin-table {
width: auto;
display: block;
margin: 0 auto;
}
div.pin-table img {
width: 30px;
transform: rotate(90deg);
padding-left: 17.5px;
}
div.pin-table div.row {
width: auto;
display: block;
height: 50px;
}
div.pin-table div.row * {
display: inline-block;
}
div.pin-table div.row input {
width: 100px;
border: 0;
background-color: #ebebeb;
padding: 15px;
font-family: inherit;
border-radius: 20px;
text-align: center;
height: 50px;
}
div.plug-table {
width: 100%;
margin-bottom: 50px;
}
div.row {
width: 100%;
height: 70px;
margin-bottom: 10px;
}
div.inventory-table>div.row {
height: 30px;
text-align: left;
display: block;
top: calc(50% - 27.5px);
position: relative;
width: auto;
background-color: #ebebeb;
margin: 10px;
padding: 5px;
border-radius: 25px;
height: auto;
line-height: 25px;
}
div.inventory-table>div.row div.plug-table {
margin-bottom: 0;
}
div.inventory-table>div.row div.plug-table div.label>span {
background-color: #d2d2d2;
}
div.inventory-table>div.row img,div.inventory-table>div.row img {
max-height: 100%;
width: auto;
display: inline-block;
filter: opacity(.5);
position: absolute;
}
div.inventory-table>div.row img.map,div.inventory-table>div.row img.down {
left: 10px;
top: 5px;
bottom: 5px;
}
div.inventory-table>div.row img.down {
left: unset;
cursor: pointer;
right: 10px;
}
div.inventory-table>div.row span {
margin-left: 4px;
line-height: 25px;
display: block;
}
div.label {
display: inline-block;
width: 20%;
margin: 0 auto;
text-align: center;
line-height: 100%;
float: left;
height: 70px;
}
div.label>span {
display: block;
top: calc(50% - 27.5px);
position:relative;
width: auto;
background-color: #ebebeb;
margin: 10px;
padding: 5px;
border-radius: 25px;
height: 25px;
line-height:25px;
}
div.label>span span {
padding: 2px;
background-color: #fff;
margin: 0px 3px;
border-radius: 100%;
display: block;
float: left;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
}
img#bridge-editor-img {
display: inline-block;
width: 65%;
max-height:70px;
}
div.row div.options {
display: inline-block;
width: 15%;
height: 70px;
float: right;
}
div.row div.options img {
width: 40%;
padding: 3px;
box-sizing: border-box;
display: inline-block;
top: calc(50% - 25px);
position: relative;
filter: opacity(.5);
cursor: pointer;
}
div.action-menu {
position: absolute;
bottom: 10px;
right: 10px;
width: auto;
height: 50px;
z-index: 501;
}
div.action-menu img {
float: left;
width: 50px;
height: 50px;
background-color: #ffffff7a;
border-radius: 100%;
cursor: pointer;
filter: contrast(0.3);
}
div.distinctly-input {
display: flex;
justify-content: space-between;
gap: 10px;
width:70%;
margin: 0 auto;
margin-top: 20px;
}
.code-input, div.search-container input {
width: 50px;
height: 50px;
font-size: 24px;
text-align: center;
background-color: #ebebeb;
border-radius: 8px;
outline: none;
transition: border-color 0.3s ease;
border: none;
}
.code-input:focus {
border-color: #007bff;
}
textarea {
width: 100%;
max-width: 100%;
height: 200px;
max-height: 200px;
font-size: 14px;
text-align: center;
background-color: #ebebeb;
border-radius: 8px;
outline: none;
transition: border-color 0.3s ease;
border: none;
}
textarea:focus {
border-color: #007bff;
}
hr {
border: 3px solid #ebebeb;
margin: 20px 0;
}
div.search-container {
width: 100%;
height: 50px;
position: relative;
}
div.search-container input {
width: calc(100% - 60px);
border-radius: inherit 0px;
border-radius: 10px 0 0 10px;
}
div.search-container div.save-button {
width: 50px;
display: inline-block;
margin: 0;
height: 40px;
float: right;
text-align: center;
border-radius: 0 10px 10px 0
}
div.search-container div.save-button img {
margin: 0;
margin-top: 4px;
}
div.search-container div.results {
position: absolute;
bottom: 50px;
left: 0;
right: 0;
background-color: #d2d2d2;
border-radius: 10px;
}
div.program-field {
padding: 20px;
margin-top: 20px;
border: 3px solid #ebebeb;
border-radius: 25px;
}
.checkbox-container {
position: relative;
display: inline-block;
}
.checkbox-container input[type="checkbox"] {
position: absolute;
opacity: 0;
pointer-events: none;
}
.checkbox-container .checkbox-text {
display: inline-block;
text-align: center;
cursor: pointer;
border-radius: 25px;
background-color: #ebebeb;
padding: 5px 15px;
border: 3px solid #ebebeb;
}
.checkbox-container input[type="checkbox"]:checked + .checkbox-text {
background-color: #d2d2d2;
border: 3px solid #2e3436;
color: #fff;
}
div.save-button img {
display: inline-block;
}
div.save-button {
background-color: #ebebeb
}
::-webkit-scrollbar {
width: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: #ebebeb;
}
/* Handle */
::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover {
background: #d2d2d2;
}
div.toggle-switch {
width: 100%;
height: 50px;
margin-bottom: 10px;
display: flex;
}
div.toggle-switch div {
display: inline-block;
}
div.toggle-switch div:nth-child(1), div.toggle-switch div:nth-child(3) {
width: 39%;
}
div.toggle-switch div:nth-child(2) {
width: 20%;
padding: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 100%;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
border-radius: 25px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 100%;
}
input:checked + .slider:before {
-webkit-transform: translateX(86px);
-ms-transform: translateX(86px);
transform: translateX(86px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
table.simple-devider th,table.simple-devider td {
padding: 15px;
}
table.simple-devider * {
margin: 0;
vertical-align: top;
}
canvas {
cursor: crosshair;
background-repeat: no-repeat;
}
@media only screen and (max-width: 1200px) {
div.main {
width: 100%;
height: 100%;
border: 0;
border-radius: 0;
max-width: unset;
}
body {
padding: 0;
padding-right: 0;
box-sizing: border-box;
}
div.content {
width: calc(100% - 250px);
height: 100%;
padding: 0;
}
div.navigation {
height: 100%;
overflow-y: scroll;
}
div.navigation-footer {
position: relative;
}
}
@media only screen and (max-width: 800px) {
div.navigation {
width: 40px;
height: 100%;
}
div.navigation > div > span {
display: none;
}
div.navigation>div {
padding: 5px;
margin: 0;
}
div.navigation.secondary {
left: -500px !important;
}
div.navigation-footer {
display: none;
}
div.content {
width: calc(100% - 40px);
height: 100%;
padding: 0;
}
}

8
temp.csv Normal file
View File

@@ -0,0 +1,8 @@
"Gemessen am / um";"Stw-Name / Ort";"Erkannte Brücken (Pin-zu-Pin)";"(automatisch) ermittelte Programm-Nummer";Freitext-Besonderheiten
"24.07.2025 11:48:00";"Dudweiler Df";"20<->28, 30<->39, ";?????;
"24.07.2025 11:48:56";"Mannheim-Waldhof Wf (Mwf)";"20<->28, 30<->39, ";12345;
"24.07.2025 12:48:27";"Laupheim West Lf";"20<->28, 30<->39, ";S???1;
"24.07.2025 13:37:29";"Mannheim-Waldhof Wf (Mwf)";"20<->28, 30<->39, ";S???1;
"24.07.2025 14:28:33";"Dudweiler Df";"20<->28, 30<->39, ";S???1;
"28.07.2025 14:52:44";"Sinzig Sf";"19<->22, ";JAAAA;
"29.07.2025 05:57:38";"Mannheim-Waldhof Wf (Mwf)";"19<->22, ";JAAAA;
1 Gemessen am / um Stw-Name / Ort Erkannte Brücken (Pin-zu-Pin) (automatisch) ermittelte Programm-Nummer Freitext-Besonderheiten
2 24.07.2025 11:48:00 Dudweiler Df 20<->28, 30<->39, ?????
3 24.07.2025 11:48:56 Mannheim-Waldhof Wf (Mwf) 20<->28, 30<->39, 12345
4 24.07.2025 12:48:27 Laupheim West Lf 20<->28, 30<->39, S???1
5 24.07.2025 13:37:29 Mannheim-Waldhof Wf (Mwf) 20<->28, 30<->39, S???1
6 24.07.2025 14:28:33 Dudweiler Df 20<->28, 30<->39, S???1
7 28.07.2025 14:52:44 Sinzig Sf 19<->22, JAAAA
8 29.07.2025 05:57:38 Mannheim-Waldhof Wf (Mwf) 19<->22, JAAAA

1
temp.json Normal file
View File

@@ -0,0 +1 @@
{"plug":"0"}

BIN
temp.xlsx Normal file

Binary file not shown.

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="DB_InfraGO_logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 316.5 70"><desc>Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</desc>
<metadata><?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 9.1-c001 79.a8d4753, 2023/03/23-08:56:37 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<dc:creator>
<rdf:Seq>
<rdf:li>Deutsche Bahn</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</dc:description>
<Iptc4xmpCore:AltTextAccessibility>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</Iptc4xmpCore:AltTextAccessibility>
<Iptc4xmpCore:ExtDescrAccessibility>
<rdf:Alt>
<rdf:li xml:lang="x-default">Das ist das Firmenlogo der gemeinwohlorientierten Infrastrukturgesellschaft DB InfraGO AG.</rdf:li>
</rdf:Alt>
</Iptc4xmpCore:ExtDescrAccessibility>
<xmp:MetadataDate>2023-12-20T16:18:38+01:00</xmp:MetadataDate>
<xmpMM:InstanceID>xmp.iid:f4896118-a5fc-4ec6-884b-265e34b1ca9a</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:8feca862-1f36-49d9-a283-4022945ab9e2</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:8feca862-1f36-49d9-a283-4022945ab9e2</xmpMM:OriginalDocumentID>
<xmpMM:History>
<rdf:Seq>
<rdf:li>
<rdf:Description>
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:8feca862-1f36-49d9-a283-4022945ab9e2</stEvt:instanceID>
<stEvt:when>2023-12-20T16:13:32+01:00</stEvt:when>
<stEvt:softwareAgent>Adobe Bridge 2024</stEvt:softwareAgent>
<stEvt:changed>/metadata</stEvt:changed>
</rdf:Description>
</rdf:li>
<rdf:li>
<rdf:Description>
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:f4896118-a5fc-4ec6-884b-265e34b1ca9a</stEvt:instanceID>
<stEvt:when>2023-12-20T16:18:38+01:00</stEvt:when>
<stEvt:softwareAgent>Adobe Bridge 2024</stEvt:softwareAgent>
<stEvt:changed>/metadata</stEvt:changed>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
<photoshop:CaptionWriter>Deutsche Bahn</photoshop:CaptionWriter>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?></metadata>
<g id="DB_Logo"><path d="M90,0H10C4.6,0,0,4.3,0,10v50c0,5.5,4.6,10,10,10h80c5.5,0,10-4.5,10-10.1V10c0-5.7-4.6-10-10-10ZM92.7,59.9c0,1.6-1.2,2.9-2.7,2.9H10c-1.6,0-2.7-1.3-2.7-2.9V10c0-1.6,1.2-2.9,2.7-2.9h80c1.6,0,2.7,1.3,2.7,2.9v49.9Z" style="fill:#ec0016; stroke-width:0px;"/><path d="M30.6,57.9H13.8V12.1h16.8c11.9,0,18.4,7.4,18.4,22.7,0,13.2-4.5,23-18.4,23.1ZM37.9,35.7c0-9.2-1-16.4-10.9-16.4h-2.2v31.2h3.9c5.8,0,9.2-4.7,9.2-14.8Z" style="fill:#ec0016; stroke-width:0px;"/><path d="M78,33.9c3.2-.9,8-4.6,8-10.7,0-1-.2-11.2-13.1-11.2h-19.1v45.8h16.2c4.3,0,17.7,0,17.7-12.9,0-3.1-1.4-9.1-9.7-11ZM64.7,19.2h3.5c4.9,0,6.9,1.7,6.9,5.5,0,3-2.2,5.5-6,5.5h-4.4v-11ZM69.2,50.5h-4.5v-11.8h4.8c5.7,0,7.1,3.3,7.1,5.9,0,5.9-5.6,5.9-7.4,5.9Z" style="fill:#ec0016; stroke-width:0px;"/></g><g id="InfraGo"><path d="M125,19.1h10v38.9h-10V19.1Z" style="stroke-width:0px;"/><path d="M148.3,28.5h2.2v5.2c3.1-3.5,6.2-5.1,9.9-5.1,4.9,0,7.7,3.3,7.7,9v20.3h-9.3v-18.2c0-2.4-1.1-3.5-3.4-3.5-1.6,0-2.8.4-4.9,1.5v20.2h-9.3v-27.6c3.7-.1,6-.7,7.1-1.8Z" style="stroke-width:0px;"/><path d="M175,37.2h-3.6v-6.6h3.6v-2c0-6.3,3.8-10.2,11.3-10.2,1.9,0,3.3.2,4.6.3v6.4c-.7-.1-1.3-.2-2-.2-3.2,0-4.5,1.2-4.5,4v1.7h4.8v6.6h-4.8v20.7h-9.4v-20.7Z" style="stroke-width:0px;"/><path d="M199.3,28.6h2.2v5.7c2.4-3.8,4.9-5.6,7.6-5.6.3,0,.5.1.7.1v8.3l-.6.1c-2.5.2-5.3.8-7.7,1.9v18.9h-9.3v-27.6c3.6-.1,5.9-.7,7.1-1.8Z" style="stroke-width:0px;"/><path d="M226.7,54.2c-2.1,3-4.9,4.5-8.4,4.5-5.3,0-8.4-3-8.4-8.6,0-8.1,7-8.6,16.3-9v-1.5c0-3-1.4-4.1-4.2-4.1s-5.9.5-9.8,1.2v-7.2c4.1-.9,7.2-1.3,10.8-1.3,8.9,0,12.5,3.3,12.5,11.2v11.7c0,3.2.3,4.3,1.2,5v1.6h-9l-1-3.5ZM222.1,52.6c1.5,0,2.9-.5,4.2-1.6v-5.7c-5.3.2-7.2.5-7.2,4-.1,2.3.8,3.3,3,3.3Z" style="stroke-width:0px;"/><path d="M240.1,39v-.2c0-13.6,7.2-20.3,20.4-20.3,4.3,0,7.6.5,11.6,1.7v7.9c-3.9-1.3-7.3-1.9-11.1-1.9-7.9,0-10,4-10,12.6v.3c0,8.8,2.4,12.2,9.4,12.2,1.6,0,2.7-.2,4.2-.6v-6.5h-6.2v-7.4h15.6v19c-4.8,1.8-9,2.8-13.9,2.8-13.6,0-20-5-20-19.6Z" style="stroke-width:0px;"/><path d="M278.7,38.6v-.2c0-12.5,7.1-20,18.9-20,11.9,0,18.9,7.6,18.9,20.1v.2c0,12.5-7,19.9-18.9,19.9-12,0-18.9-7.5-18.9-20ZM305.6,38.7v-.3c0-9-2.4-12.7-8-12.7-5.7,0-8,3.7-8,12.8v.3c0,8.8,2.4,12.4,8,12.4,5.7,0,8-3.6,8-12.5Z" style="stroke-width:0px;"/></g></svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

5
vendor/composer/composer.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"require": {
"phpoffice/phpspreadsheet": "^4.5"
}
}

600
vendor/composer/composer.lock generated vendored Normal file
View File

@@ -0,0 +1,600 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "218a60c9572277f09d6952f6a8761547",
"packages": [
{
"name": "composer/pcre",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.2"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^11.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2025-01-27T12:07:53+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "4.5.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^8.1",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.5.0"
},
"time": "2025-07-24T05:15:59+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/simple-cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"support": {
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
},
"time": "2021-10-29T13:26:27+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

BIN
vendor/composer/composer.phar vendored Executable file

Binary file not shown.

22
vendor/composer/vendor/autoload.php vendored Normal file
View File

@@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit62e10e6c8df9afc192311b196c75bb33::getLoader();

View File

@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@@ -0,0 +1,396 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/vendor/composer/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,17 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'),
'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'),
'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'),
);

View File

@@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit62e10e6c8df9afc192311b196c75bb33
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit62e10e6c8df9afc192311b196c75bb33', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit62e10e6c8df9afc192311b196c75bb33', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit62e10e6c8df9afc192311b196c75bb33::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,81 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit62e10e6c8df9afc192311b196c75bb33
{
public static $prefixLengthsPsr4 = array (
'Z' =>
array (
'ZipStream\\' => 10,
),
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'PhpOffice\\PhpSpreadsheet\\' => 25,
),
'M' =>
array (
'Matrix\\' => 7,
),
'C' =>
array (
'Composer\\Pcre\\' => 14,
'Complex\\' => 8,
),
);
public static $prefixDirsPsr4 = array (
'ZipStream\\' =>
array (
0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
),
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'PhpOffice\\PhpSpreadsheet\\' =>
array (
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
),
'Matrix\\' =>
array (
0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src',
),
'Composer\\Pcre\\' =>
array (
0 => __DIR__ . '/..' . '/composer/pcre/src',
),
'Complex\\' =>
array (
0 => __DIR__ . '/..' . '/markbaker/complex/classes/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit62e10e6c8df9afc192311b196c75bb33::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit62e10e6c8df9afc192311b196c75bb33::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit62e10e6c8df9afc192311b196c75bb33::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,614 @@
{
"packages": [
{
"name": "composer/pcre",
"version": "3.3.2",
"version_normalized": "3.3.2.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"time": "2024-11-12T16:29:46+00:00",
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"install-path": "./pcre"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.2",
"version_normalized": "3.1.2.0",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.2"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^11.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"time": "2025-01-27T12:07:53+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"install-path": "../maennchen/zipstream-php"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"version_normalized": "3.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"time": "2022-12-06T16:21:08+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"install-path": "../markbaker/complex"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"version_normalized": "3.0.1.0",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"time": "2022-12-02T22:17:43+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"install-path": "../markbaker/matrix"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "4.5.0",
"version_normalized": "4.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^8.1",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"time": "2025-07-24T05:15:59+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.5.0"
},
"install-path": "../phpoffice/phpspreadsheet"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"version_normalized": "1.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2023-09-23T14:17:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"install-path": "../psr/http-client"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2024-04-15T12:06:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"install-path": "../psr/http-factory"
},
{
"name": "psr/http-message",
"version": "2.0",
"version_normalized": "2.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"time": "2023-04-04T09:54:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"install-path": "../psr/http-message"
},
{
"name": "psr/simple-cache",
"version": "3.0.0",
"version_normalized": "3.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"time": "2021-10-29T13:26:27+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"support": {
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
},
"install-path": "../psr/simple-cache"
}
],
"dev": true,
"dev-package-names": []
}

View File

@@ -0,0 +1,104 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'composer/pcre' => array(
'pretty_version' => '3.3.2',
'version' => '3.3.2.0',
'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e',
'type' => 'library',
'install_path' => __DIR__ . '/./pcre',
'aliases' => array(),
'dev_requirement' => false,
),
'maennchen/zipstream-php' => array(
'pretty_version' => '3.1.2',
'version' => '3.1.2.0',
'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f',
'type' => 'library',
'install_path' => __DIR__ . '/../maennchen/zipstream-php',
'aliases' => array(),
'dev_requirement' => false,
),
'markbaker/complex' => array(
'pretty_version' => '3.0.2',
'version' => '3.0.2.0',
'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9',
'type' => 'library',
'install_path' => __DIR__ . '/../markbaker/complex',
'aliases' => array(),
'dev_requirement' => false,
),
'markbaker/matrix' => array(
'pretty_version' => '3.0.1',
'version' => '3.0.1.0',
'reference' => '728434227fe21be27ff6d86621a1b13107a2562c',
'type' => 'library',
'install_path' => __DIR__ . '/../markbaker/matrix',
'aliases' => array(),
'dev_requirement' => false,
),
'phpoffice/phpspreadsheet' => array(
'pretty_version' => '4.5.0',
'version' => '4.5.0.0',
'reference' => '2ea9786632e6fac1aee601b6e426bcc723d8ce13',
'type' => 'library',
'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message' => array(
'pretty_version' => '2.0',
'version' => '2.0.0.0',
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/simple-cache' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -0,0 +1,19 @@
Copyright (C) 2021 Composer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,189 @@
composer/pcre
=============
PCRE wrapping library that offers type-safe `preg_*` replacements.
This library gives you a way to ensure `preg_*` functions do not fail silently, returning
unexpected `null`s that may not be handled.
As of 3.0 this library enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage
for all matching and replaceCallback functions, [read more below](#preg_unmatched_as_null)
to understand the implications.
It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it
simplifies and reduces the possible return values from all the `preg_*` functions which
are quite packed with edge cases. As of v2.2.0 / v3.2.0 the library also comes with a
[PHPStan extension](#phpstan-extension) for parsing regular expressions and giving you even better output types.
This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations).
If you are looking for a richer API to handle regular expressions have a look at
[rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead.
[![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions)
Installation
------------
Install the latest version with:
```bash
$ composer require composer/pcre
```
Requirements
------------
* PHP 7.4.0 is required for 3.x versions
* PHP 7.2.0 is required for 2.x versions
* PHP 5.3.2 is required for 1.x versions
Basic usage
-----------
Instead of:
```php
if (preg_match('{fo+}', $string, $matches)) { ... }
if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... }
if (preg_match_all('{fo+}', $string, $matches)) { ... }
$newString = preg_replace('{fo+}', 'bar', $string);
$newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
$newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
$filtered = preg_grep('{[a-z]}', $elements);
$array = preg_split('{[a-z]+}', $string);
```
You can now call these on the `Preg` class:
```php
use Composer\Pcre\Preg;
if (Preg::match('{fo+}', $string, $matches)) { ... }
if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... }
if (Preg::matchAll('{fo+}', $string, $matches)) { ... }
$newString = Preg::replace('{fo+}', 'bar', $string);
$newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
$newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
$filtered = Preg::grep('{[a-z]}', $elements);
$array = Preg::split('{[a-z]+}', $string);
```
The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException`
instead of returning `null` (or false in some cases), so you can now use the return values safely relying on
the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split).
Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety
when the number of pattern matches is not useful:
```php
use Composer\Pcre\Preg;
if (Preg::isMatch('{fo+}', $string, $matches)) // bool
if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool
```
Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups
are always present and thus non-nullable, making it easier to write type-safe code:
```php
use Composer\Pcre\Preg;
// $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw
if (Preg::matchStrictGroups('{fo+}', $string, $matches))
if (Preg::matchAllStrictGroups('{fo+}', $string, $matches))
```
**Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?`
or `(something)*` or branches with a `|` that result in some groups not being matched at all).
A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an
empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in
matches so it is also not a problem to use these with `*StrictGroups` methods.
If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class:
```php
use Composer\Pcre\Regex;
// this is useful when you are just interested in knowing if something matched
// as it returns a bool instead of int(1/0) for match
$bool = Regex::isMatch('{fo+}', $string);
$result = Regex::match('{fo+}', $string);
if ($result->matched) { something($result->matches); }
$result = Regex::matchWithOffsets('{fo+}', $string);
if ($result->matched) { something($result->matches); }
$result = Regex::matchAll('{fo+}', $string);
if ($result->matched && $result->count > 3) { something($result->matches); }
$newString = Regex::replace('{fo+}', 'bar', $string)->result;
$newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result;
$newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result;
```
Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have
complex return types warranting a specific result object.
See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php),
[MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details.
Restrictions / Limitations
--------------------------
Due to type safety requirements a few restrictions are in place.
- matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`.
You cannot pass the flag to `match`/`matchAll`.
- `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets`
instead.
- `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There
is no alternative provided as you can fairly easily code around it.
- `preg_filter` is not supported as it has a rather crazy API, most likely you should rather
use `Preg::grep` in combination with some loop and `Preg::replace`.
- `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`,
only simple strings.
- As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much
saner/more predictable results](#preg_unmatched_as_null). As of 3.0 the flag is also set for
`replaceCallback` and `replaceCallbackArray`.
#### PREG_UNMATCHED_AS_NULL
As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*`
functions. As of 3.0 it is also done for `replaceCallback` and `replaceCallbackArray`.
This means your matches will always contain all matching groups, either as null if unmatched
or as string if it matched.
The advantages in clarity and predictability are clearer if you compare the two outputs of
running this with and without PREG_UNMATCHED_AS_NULL in $flags:
```php
preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags);
```
| no flag | PREG_UNMATCHED_AS_NULL |
| --- | --- |
| array (size=4) | array (size=5) |
| 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) |
| 1 => string 'a' (length=1) | 1 => string 'a' (length=1) |
| 2 => string '' (length=0) | 2 => null |
| 3 => string 'c' (length=1) | 3 => string 'c' (length=1) |
| | 4 => null |
| group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for |
| group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` |
PHPStan Extension
-----------------
To use the PHPStan extension if you do not use `phpstan/extension-installer` you can include `vendor/composer/pcre/extension.neon` in your PHPStan config.
The extension provides much better type information for $matches as well as regex validation where possible.
License
-------
composer/pcre is licensed under the MIT License, see the LICENSE file for details.

View File

@@ -0,0 +1,54 @@
{
"name": "composer/pcre",
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"type": "library",
"license": "MIT",
"keywords": [
"pcre",
"regex",
"preg",
"regular expression"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"require": {
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8 || ^9",
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\Pcre\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"scripts": {
"test": "@php vendor/bin/phpunit",
"phpstan": "@php phpstan analyse"
}
}

View File

@@ -0,0 +1,22 @@
# composer/pcre PHPStan extensions
#
# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon'
# in your phpstan config
services:
-
class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension
tags:
- phpstan.staticMethodParameterOutTypeExtension
-
class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension
tags:
- phpstan.staticMethodParameterClosureTypeExtension
rules:
- Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule
- Composer\Pcre\PHPStan\InvalidRegexPatternRule

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchAllResult
{
/**
* An array of match group => list of matched strings
*
* @readonly
* @var array<int|string, list<string|null>>
*/
public $matches;
/**
* @readonly
* @var 0|positive-int
*/
public $count;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<int|string, list<string|null>> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
$this->count = $count;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchAllStrictGroupsResult
{
/**
* An array of match group => list of matched strings
*
* @readonly
* @var array<int|string, list<string>>
*/
public $matches;
/**
* @readonly
* @var 0|positive-int
*/
public $count;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<list<string>> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
$this->count = $count;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchAllWithOffsetsResult
{
/**
* An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match)
*
* @readonly
* @var array<int|string, list<array{string|null, int}>>
* @phpstan-var array<int|string, list<array{string|null, int<-1, max>}>>
*/
public $matches;
/**
* @readonly
* @var 0|positive-int
*/
public $count;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<int|string, list<array{string|null, int}>> $matches
* @phpstan-param array<int|string, list<array{string|null, int<-1, max>}>> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
$this->count = $count;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchResult
{
/**
* An array of match group => string matched
*
* @readonly
* @var array<int|string, string|null>
*/
public $matches;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<string|null> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchStrictGroupsResult
{
/**
* An array of match group => string matched
*
* @readonly
* @var array<int|string, string>
*/
public $matches;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<string> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class MatchWithOffsetsResult
{
/**
* An array of match group => pair of string matched + offset in bytes (or -1 if no match)
*
* @readonly
* @var array<int|string, array{string|null, int}>
* @phpstan-var array<int|string, array{string|null, int<-1, max>}>
*/
public $matches;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
* @param array<array{string|null, int}> $matches
* @phpstan-param array<int|string, array{string|null, int<-1, max>}> $matches
*/
public function __construct(int $count, array $matches)
{
$this->matches = $matches;
$this->matched = (bool) $count;
}
}

View File

@@ -0,0 +1,142 @@
<?php declare(strict_types = 1);
namespace Composer\Pcre\PHPStan;
use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use Composer\Pcre\PcreException;
use Nette\Utils\RegexpException;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function in_array;
use function sprintf;
/**
* Copy of PHPStan's RegularExpressionPatternRule
*
* @implements Rule<StaticCall>
*/
class InvalidRegexPatternRule implements Rule
{
public function getNodeType(): string
{
return StaticCall::class;
}
public function processNode(Node $node, Scope $scope): array
{
$patterns = $this->extractPatterns($node, $scope);
$errors = [];
foreach ($patterns as $pattern) {
$errorMessage = $this->validatePattern($pattern);
if ($errorMessage === null) {
continue;
}
$errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build();
}
return $errors;
}
/**
* @return string[]
*/
private function extractPatterns(StaticCall $node, Scope $scope): array
{
if (!$node->class instanceof FullyQualified) {
return [];
}
$isRegex = $node->class->toString() === Regex::class;
$isPreg = $node->class->toString() === Preg::class;
if (!$isRegex && !$isPreg) {
return [];
}
if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) {
return [];
}
$functionName = $node->name->name;
if (!isset($node->getArgs()[0])) {
return [];
}
$patternNode = $node->getArgs()[0]->value;
$patternType = $scope->getType($patternNode);
$patternStrings = [];
foreach ($patternType->getConstantStrings() as $constantStringType) {
if ($functionName === 'replaceCallbackArray') {
continue;
}
$patternStrings[] = $constantStringType->getValue();
}
foreach ($patternType->getConstantArrays() as $constantArrayType) {
if (
in_array($functionName, [
'replace',
'replaceCallback',
], true)
) {
foreach ($constantArrayType->getValueTypes() as $arrayKeyType) {
foreach ($arrayKeyType->getConstantStrings() as $constantString) {
$patternStrings[] = $constantString->getValue();
}
}
}
if ($functionName !== 'replaceCallbackArray') {
continue;
}
foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) {
foreach ($arrayKeyType->getConstantStrings() as $constantString) {
$patternStrings[] = $constantString->getValue();
}
}
}
return $patternStrings;
}
private function validatePattern(string $pattern): ?string
{
try {
$msg = null;
$prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool {
$msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message);
return true;
});
if ($pattern === '') {
return 'Empty string is not a valid regular expression';
}
Preg::match($pattern, '');
if ($msg !== null) {
return $msg;
}
} catch (PcreException $e) {
if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) {
return $msg;
}
return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage());
} finally {
restore_error_handler();
}
return null;
}
}

View File

@@ -0,0 +1,70 @@
<?php declare(strict_types=1);
namespace Composer\Pcre\PHPStan;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;
use PhpParser\Node\Arg;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;
final class PregMatchFlags
{
static public function getType(?Arg $flagsArg, Scope $scope): ?Type
{
if ($flagsArg === null) {
return new ConstantIntegerType(PREG_UNMATCHED_AS_NULL);
}
$flagsType = $scope->getType($flagsArg->value);
$constantScalars = $flagsType->getConstantScalarValues();
if ($constantScalars === []) {
return null;
}
$internalFlagsTypes = [];
foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) {
if (!is_int($constantScalarValue)) {
return null;
}
$internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL);
}
return TypeCombinator::union(...$internalFlagsTypes);
}
static public function removeNullFromMatches(Type $matchesType): Type
{
return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}
if ($type instanceof ConstantArrayType) {
return new ConstantArrayType(
$type->getKeyTypes(),
array_map(static function (Type $valueType) use ($traverse): Type {
return $traverse($valueType);
}, $type->getValueTypes()),
$type->getNextAutoIndexes(),
[],
$type->isList()
);
}
if ($type instanceof ArrayType) {
return new ArrayType($type->getKeyType(), $traverse($type->getItemType()));
}
return TypeCombinator::removeNull($type);
});
}
}

View File

@@ -0,0 +1,65 @@
<?php declare(strict_types=1);
namespace Composer\Pcre\PHPStan;
use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterOutTypeExtension;
use PHPStan\Type\Type;
final class PregMatchParameterOutTypeExtension implements StaticMethodParameterOutTypeExtension
{
/**
* @var RegexArrayShapeMatcher
*/
private $regexShapeMatcher;
public function __construct(
RegexArrayShapeMatcher $regexShapeMatcher
)
{
$this->regexShapeMatcher = $regexShapeMatcher;
}
public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
{
return
$methodReflection->getDeclaringClass()->getName() === Preg::class
&& in_array($methodReflection->getName(), [
'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
], true)
&& $parameter->getName() === 'matches';
}
public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $methodCall->getArgs();
$patternArg = $args[0] ?? null;
$matchesArg = $args[2] ?? null;
$flagsArg = $args[3] ?? null;
if (
$patternArg === null || $matchesArg === null
) {
return null;
}
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
if ($flagsType === null) {
return null;
}
if (stripos($methodReflection->getName(), 'matchAll') !== false) {
return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
}
return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
}
}

View File

@@ -0,0 +1,119 @@
<?php declare(strict_types=1);
namespace Composer\Pcre\PHPStan;
use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;
final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
/**
* @var TypeSpecifier
*/
private $typeSpecifier;
/**
* @var RegexArrayShapeMatcher
*/
private $regexShapeMatcher;
public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
{
$this->regexShapeMatcher = $regexShapeMatcher;
}
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}
public function getClass(): string
{
return Preg::class;
}
public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool
{
return in_array($methodReflection->getName(), [
'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
], true)
&& !$context->null();
}
public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$args = $node->getArgs();
$patternArg = $args[0] ?? null;
$matchesArg = $args[2] ?? null;
$flagsArg = $args[3] ?? null;
if (
$patternArg === null || $matchesArg === null
) {
return new SpecifiedTypes();
}
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
if ($flagsType === null) {
return new SpecifiedTypes();
}
if (stripos($methodReflection->getName(), 'matchAll') !== false) {
$matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
} else {
$matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
}
if ($matchedType === null) {
return new SpecifiedTypes();
}
if (
in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)
) {
$matchedType = PregMatchFlags::removeNullFromMatches($matchedType);
}
$overwrite = false;
if ($context->false()) {
$overwrite = true;
$context = $context->negate();
}
// @phpstan-ignore function.alreadyNarrowedType
if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) {
$typeSpecifier = $this->typeSpecifier->create(
$matchesArg->value,
$matchedType,
$context,
$scope
)->setRootExpr($node);
return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier;
}
// @phpstan-ignore arguments.count
return $this->typeSpecifier->create(
$matchesArg->value,
$matchedType,
$context,
// @phpstan-ignore argument.type
$overwrite,
$scope,
$node
);
}
}

View File

@@ -0,0 +1,91 @@
<?php declare(strict_types=1);
namespace Composer\Pcre\PHPStan;
use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;
final class PregReplaceCallbackClosureTypeExtension implements StaticMethodParameterClosureTypeExtension
{
/**
* @var RegexArrayShapeMatcher
*/
private $regexShapeMatcher;
public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
{
$this->regexShapeMatcher = $regexShapeMatcher;
}
public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
{
return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true)
&& in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true)
&& $parameter->getName() === 'replacement';
}
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $methodCall->getArgs();
$patternArg = $args[0] ?? null;
$flagsArg = $args[5] ?? null;
if (
$patternArg === null
) {
return null;
}
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
$matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
if ($matchesType === null) {
return null;
}
if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) {
$matchesType = $matchesType->getConstantArrays()[0];
$matchesType = new ConstantArrayType(
$matchesType->getKeyTypes(),
array_map(static function (Type $valueType): Type {
if (count($valueType->getConstantArrays()) === 1) {
$valueTypeArray = $valueType->getConstantArrays()[0];
return new ConstantArrayType(
$valueTypeArray->getKeyTypes(),
array_map(static function (Type $valueType): Type {
return TypeCombinator::removeNull($valueType);
}, $valueTypeArray->getValueTypes()),
$valueTypeArray->getNextAutoIndexes(),
[],
$valueTypeArray->isList()
);
}
return TypeCombinator::removeNull($valueType);
}, $matchesType->getValueTypes()),
$matchesType->getNextAutoIndexes(),
[],
$matchesType->isList()
);
}
return new ClosureType(
[
new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()),
],
new StringType()
);
}
}

View File

@@ -0,0 +1,112 @@
<?php declare(strict_types=1);
namespace Composer\Pcre\PHPStan;
use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use function sprintf;
/**
* @implements Rule<StaticCall>
*/
final class UnsafeStrictGroupsCallRule implements Rule
{
/**
* @var RegexArrayShapeMatcher
*/
private $regexShapeMatcher;
public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
{
$this->regexShapeMatcher = $regexShapeMatcher;
}
public function getNodeType(): string
{
return StaticCall::class;
}
public function processNode(Node $node, Scope $scope): array
{
if (!$node->class instanceof FullyQualified) {
return [];
}
$isRegex = $node->class->toString() === Regex::class;
$isPreg = $node->class->toString() === Preg::class;
if (!$isRegex && !$isPreg) {
return [];
}
if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) {
return [];
}
$args = $node->getArgs();
if (!isset($args[0])) {
return [];
}
$patternArg = $args[0] ?? null;
if ($isPreg) {
if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway
return [];
}
$flagsArg = $args[3] ?? null;
} else {
$flagsArg = $args[2] ?? null;
}
if ($patternArg === null) {
return [];
}
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
if ($flagsType === null) {
return [];
}
$matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
if ($matchedType === null) {
return [
RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name))
->identifier('composerPcre.maybeUnsafeStrictGroups')
->build(),
];
}
if (count($matchedType->getConstantArrays()) === 1) {
$matchedType = $matchedType->getConstantArrays()[0];
$nullableGroups = [];
foreach ($matchedType->getValueTypes() as $index => $type) {
if (TypeCombinator::containsNull($type)) {
$nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue();
}
}
if (\count($nullableGroups) > 0) {
return [
RuleErrorBuilder::message(sprintf(
'The %s call is unsafe as match group%s "%s" %s optional and may be null.',
$node->name->name,
\count($nullableGroups) > 1 ? 's' : '',
implode('", "', $nullableGroups),
\count($nullableGroups) > 1 ? 'are' : 'is'
))->identifier('composerPcre.unsafeStrictGroups')->build(),
];
}
}
return [];
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
class PcreException extends \RuntimeException
{
/**
* @param string $function
* @param string|string[] $pattern
* @return self
*/
public static function fromFunction($function, $pattern)
{
$code = preg_last_error();
if (is_array($pattern)) {
$pattern = implode(', ', $pattern);
}
return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code);
}
/**
* @param int $code
* @return string
*/
private static function pcreLastErrorMessage($code)
{
if (function_exists('preg_last_error_msg')) {
return preg_last_error_msg();
}
$constants = get_defined_constants(true);
if (!isset($constants['pcre']) || !is_array($constants['pcre'])) {
return 'UNDEFINED_ERROR';
}
foreach ($constants['pcre'] as $const => $val) {
if ($val === $code && substr($const, -6) === '_ERROR') {
return $const;
}
}
return 'UNDEFINED_ERROR';
}
}

View File

@@ -0,0 +1,430 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
class Preg
{
/** @internal */
public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.';
/** @internal */
public const INVALID_TYPE_MSG = '$subject must be a string, %s given.';
/**
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @return 0|1
*
* @param-out array<int|string, string|null> $matches
*/
public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
self::checkOffsetCapture($flags, 'matchWithOffsets');
$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
if ($result === false) {
throw PcreException::fromFunction('preg_match', $pattern);
}
return $result;
}
/**
* Variant of `match()` which outputs non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @return 0|1
* @throws UnexpectedNullMatchException
*
* @param-out array<int|string, string> $matches
*/
public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
$result = self::match($pattern, $subject, $matchesInternal, $flags, $offset);
$matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match');
return $result;
}
/**
* Runs preg_match with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported
* @return 0|1
*
* @param-out array<int|string, array{string|null, int<-1, max>}> $matches
*/
public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
{
$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
if ($result === false) {
throw PcreException::fromFunction('preg_match', $pattern);
}
return $result;
}
/**
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @return 0|positive-int
*
* @param-out array<int|string, list<string|null>> $matches
*/
public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
self::checkOffsetCapture($flags, 'matchAllWithOffsets');
self::checkSetOrder($flags);
$result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
throw PcreException::fromFunction('preg_match_all', $pattern);
}
return $result;
}
/**
* Variant of `match()` which outputs non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @return 0|positive-int
* @throws UnexpectedNullMatchException
*
* @param-out array<int|string, list<string>> $matches
*/
public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
$result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset);
$matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll');
return $result;
}
/**
* Runs preg_match_all with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
* @return 0|positive-int
*
* @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
*/
public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
{
self::checkSetOrder($flags);
$result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
throw PcreException::fromFunction('preg_match_all', $pattern);
}
return $result;
}
/**
* @param string|string[] $pattern
* @param string|string[] $replacement
* @param string $subject
* @param int $count Set by method
*
* @param-out int<0, max> $count
*/
public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string
{
if (!is_scalar($subject)) {
if (is_array($subject)) {
throw new \InvalidArgumentException(static::ARRAY_MSG);
}
throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
}
$result = preg_replace($pattern, $replacement, $subject, $limit, $count);
if ($result === null) {
throw PcreException::fromFunction('preg_replace', $pattern);
}
return $result;
}
/**
* @param string|string[] $pattern
* @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
* @param string $subject
* @param int $count Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*
* @param-out int<0, max> $count
*/
public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
{
if (!is_scalar($subject)) {
if (is_array($subject)) {
throw new \InvalidArgumentException(static::ARRAY_MSG);
}
throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
}
$result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
if ($result === null) {
throw PcreException::fromFunction('preg_replace_callback', $pattern);
}
return $result;
}
/**
* Variant of `replaceCallback()` which outputs non-null matches (or throws)
*
* @param string $pattern
* @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
* @param string $subject
* @param int $count Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*
* @param-out int<0, max> $count
*/
public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
{
return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) {
return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback'));
}, $subject, $limit, $count, $flags);
}
/**
* @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
* @param string $subject
* @param int $count Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*
* @param-out int<0, max> $count
*/
public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
{
if (!is_scalar($subject)) {
if (is_array($subject)) {
throw new \InvalidArgumentException(static::ARRAY_MSG);
}
throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
}
$result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
if ($result === null) {
$pattern = array_keys($pattern);
throw PcreException::fromFunction('preg_replace_callback_array', $pattern);
}
return $result;
}
/**
* @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
* @return list<string>
*/
public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
{
if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) {
throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead');
}
$result = preg_split($pattern, $subject, $limit, $flags);
if ($result === false) {
throw PcreException::fromFunction('preg_split', $pattern);
}
return $result;
}
/**
* @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set
* @return list<array{string, int}>
* @phpstan-return list<array{string, int<0, max>}>
*/
public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
{
$result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE);
if ($result === false) {
throw PcreException::fromFunction('preg_split', $pattern);
}
return $result;
}
/**
* @template T of string|\Stringable
* @param string $pattern
* @param array<T> $array
* @param int-mask<PREG_GREP_INVERT> $flags PREG_GREP_INVERT
* @return array<T>
*/
public static function grep(string $pattern, array $array, int $flags = 0): array
{
$result = preg_grep($pattern, $array, $flags);
if ($result === false) {
throw PcreException::fromFunction('preg_grep', $pattern);
}
return $result;
}
/**
* Variant of match() which returns a bool instead of int
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*
* @param-out array<int|string, string|null> $matches
*/
public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
{
return (bool) static::match($pattern, $subject, $matches, $flags, $offset);
}
/**
* Variant of `isMatch()` which outputs non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @throws UnexpectedNullMatchException
*
* @param-out array<int|string, string> $matches
*/
public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
{
return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);
}
/**
* Variant of matchAll() which returns a bool instead of int
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*
* @param-out array<int|string, list<string|null>> $matches
*/
public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
{
return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset);
}
/**
* Variant of `isMatchAll()` which outputs non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*
* @param-out array<int|string, list<string>> $matches
*/
public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
{
return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);
}
/**
* Variant of matchWithOffsets() which returns a bool instead of int
*
* Runs preg_match with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*
* @param-out array<int|string, array{string|null, int<-1, max>}> $matches
*/
public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
{
return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);
}
/**
* Variant of matchAllWithOffsets() which returns a bool instead of int
*
* Runs preg_match_all with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param array<mixed> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*
* @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
*/
public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
{
return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);
}
private static function checkOffsetCapture(int $flags, string $useFunctionName): void
{
if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead');
}
}
private static function checkSetOrder(int $flags): void
{
if (($flags & PREG_SET_ORDER) !== 0) {
throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches');
}
}
/**
* @param array<int|string, string|null|array{string|null, int}> $matches
* @return array<int|string, string>
* @throws UnexpectedNullMatchException
*/
private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod)
{
foreach ($matches as $group => $match) {
if (is_string($match) || (is_array($match) && is_string($match[0]))) {
continue;
}
throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
}
/** @var array<string> */
return $matches;
}
/**
* @param array<int|string, list<string|null>> $matches
* @return array<int|string, list<string>>
* @throws UnexpectedNullMatchException
*/
private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod)
{
foreach ($matches as $group => $groupMatches) {
foreach ($groupMatches as $match) {
if (null === $match) {
throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
}
}
}
/** @var array<int|string, list<string>> */
return $matches;
}
}

View File

@@ -0,0 +1,176 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
class Regex
{
/**
* @param non-empty-string $pattern
*/
public static function isMatch(string $pattern, string $subject, int $offset = 0): bool
{
return (bool) Preg::match($pattern, $subject, $matches, 0, $offset);
}
/**
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*/
public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult
{
self::checkOffsetCapture($flags, 'matchWithOffsets');
$count = Preg::match($pattern, $subject, $matches, $flags, $offset);
return new MatchResult($count, $matches);
}
/**
* Variant of `match()` which returns non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @throws UnexpectedNullMatchException
*/
public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult
{
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
$count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);
return new MatchStrictGroupsResult($count, $matches);
}
/**
* Runs preg_match with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
*/
public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult
{
$count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);
return new MatchWithOffsetsResult($count, $matches);
}
/**
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
*/
public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult
{
self::checkOffsetCapture($flags, 'matchAllWithOffsets');
self::checkSetOrder($flags);
$count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset);
return new MatchAllResult($count, $matches);
}
/**
* Variant of `matchAll()` which returns non-null matches (or throws)
*
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
* @throws UnexpectedNullMatchException
*/
public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult
{
self::checkOffsetCapture($flags, 'matchAllWithOffsets');
self::checkSetOrder($flags);
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
$count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);
return new MatchAllStrictGroupsResult($count, $matches);
}
/**
* Runs preg_match_all with PREG_OFFSET_CAPTURE
*
* @param non-empty-string $pattern
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
*/
public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult
{
self::checkSetOrder($flags);
$count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);
return new MatchAllWithOffsetsResult($count, $matches);
}
/**
* @param string|string[] $pattern
* @param string|string[] $replacement
* @param string $subject
*/
public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult
{
$result = Preg::replace($pattern, $replacement, $subject, $limit, $count);
return new ReplaceResult($count, $result);
}
/**
* @param string|string[] $pattern
* @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
* @param string $subject
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*/
public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
{
$result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags);
return new ReplaceResult($count, $result);
}
/**
* Variant of `replaceCallback()` which outputs non-null matches (or throws)
*
* @param string $pattern
* @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
* @param string $subject
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*/
public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
{
$result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags);
return new ReplaceResult($count, $result);
}
/**
* @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
* @param string $subject
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
*/
public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult
{
$result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags);
return new ReplaceResult($count, $result);
}
private static function checkOffsetCapture(int $flags, string $useFunctionName): void
{
if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead');
}
}
private static function checkSetOrder(int $flags): void
{
if (($flags & PREG_SET_ORDER) !== 0) {
throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type');
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
final class ReplaceResult
{
/**
* @readonly
* @var string
*/
public $result;
/**
* @readonly
* @var 0|positive-int
*/
public $count;
/**
* @readonly
* @var bool
*/
public $matched;
/**
* @param 0|positive-int $count
*/
public function __construct(int $count, string $result)
{
$this->count = $count;
$this->matched = (bool) $count;
$this->result = $result;
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of composer/pcre.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\Pcre;
class UnexpectedNullMatchException extends PcreException
{
public static function fromFunction($function, $pattern)
{
throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class);
}
}

View File

@@ -0,0 +1,29 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
}
if (PHP_INT_SIZE !== 8) {
$issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}

View File

@@ -0,0 +1,22 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.{yml,md,xml}]
indent_style = space
indent_size = 2
[*.{rst,php}]
indent_style = space
indent_size = 4
[composer.json]
indent_style = space
indent_size = 2
[composer.lock]
indent_style = space
indent_size = 4

View File

@@ -0,0 +1,6 @@
.gitignore text eol=lf
.gitattributes text eol=lf
*.md text eol=lf
*.php text eol=lf
*.yml text eol=lf
*.xml text eol=lf

View File

@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jonatan@maennchen.ch.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[mozilla coc]: https://github.com/mozilla/diversity
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

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