16 Commits

Author SHA1 Message Date
81a3967b0e Beschreibung Adresstext angepasst 2025-10-27 12:17:45 +01:00
14bae6c9ef Feature: Labelprint für Kistenetiketten hinzugefügt 2025-10-27 12:14:44 +01:00
43bc416554 Include vendor directory 2025-10-24 12:35:37 +02:00
b81164279f Feature: Versicherungsexport 2025-10-24 12:25:51 +02:00
345d22958f Feature: Packdatum/Vorbereitungsdatum wird Grün und nicht mehr überfällig wenn alle Geräte ausgecheckt wurden 2025-10-23 09:51:02 +02:00
20805c11db Kleine Kosmetische anpassungen in der Packschein und Aufgabenansicht 2025-10-10 17:24:36 +02:00
957d52dd21 If-Schleife zum Finden von offenen Aufträgen robuster gestaltet 2025-10-10 15:12:22 +02:00
f8c8725622 RefreshZeit auf 10s für Epirent erhöht, falls Webserver zu langsam antwortet.
Dashboard Aufgeräumt, wird in zukunft hübsch gemacht.
2025-10-10 12:29:25 +02:00
f8fba4275d Kleiner Fix: Wenn ein FHD Hochkant Display genutzt wurde (Dann Breite = 1080) wurden teilweise die Tabellen nebeneinander angezeigt. Durch Anpassung des Div-Containers (col-md -> col-xl) der Tabellen im Packmonitor wird nun bereits bei einer breite <1200px auf die Untereinander-Ansicht gewechselt 2025-10-10 11:46:11 +02:00
5912bc29ac Abrufzeit für Aufgaben auf 60s verlängert um 429 Fehler von CB zu vermeiden 2025-10-10 11:28:38 +02:00
36944c71bf -Feature: Ab sofort werden alle Configdateien über eine Example.config.php definiert. über das Dashboard kann nun über den neuen Punkt "Config" die eigentliche config.php bearbeitet werden. Änderungen durch Programmupdates werden jetzt automatisch in der example.config.php definiert und beim nächsten Speichern der config-datei über die Website angepasst.
-Feature: Scrollen der Listen können einzeln abeschalten werden
2025-10-10 10:46:33 +02:00
91c0a2d9d9 - Endzeiten bei CheckOut und Startzeiten bei CheckIn komplett entfernt
- Zeiten einzeln Ein- und Ausblendbar gemacht
- Quelle für Zeilenfarbe einstellbar gemacht
- Zeiten werden jetzt bei erreichen bzw überschreiten immer markiert (nur die Zeit)
- Nicht benötigte Spalten werden automatisch ausgeblendet
- Config Datei Ausgedünnt (Bitte komplette Epirent-Settings (nicht Login) Sektion ersetzen
2025-10-10 09:36:11 +02:00
0ff0c0d55e Endlos Scrollen der Listen implementiert (falls notwendig) 2025-10-09 20:42:19 +02:00
1358e17ac3 config Datei angepasst 2025-10-09 18:22:07 +02:00
4ce1578bb5 Readme angepasst 2025-10-09 18:21:25 +02:00
72dac40753 V1.6.0: Shipping hinzugefügt, Erlaube Pachscheintermine als Packtermine, Feature: Shipping abgeschlossen kann nun als CheckOut Bedingung genommen werden (vorher: Fertig gepackt) 2025-10-09 18:20:17 +02:00
1758 changed files with 409848 additions and 2714 deletions

2
.gitignore vendored
View File

@@ -1 +1,3 @@
config.php config.php
.vscode
sftp.json

View File

@@ -1,113 +1,250 @@
<?php <?php
require('config.php'); require('config.php');
require('vendor/autoload.php'); require('vendor/autoload.php');
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <title>Aufgabenmonitor</title>
<title>Aufgabenmonitor</title>
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css" > <link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css">
<script src="scripts/jquery-3.5.1.min.js"></script> <link href="css/sticky-footer.css" rel="stylesheet">
<link href="css/sticky-footer.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="text/javascript"> <style>
$(document).ready(function(){ body { background-color: #000; }
refreshAufgabenTable(); .tableFixHead { overflow:auto; }
.tableFixHead thead th {
position: sticky;
top: 0;
z-index: 1;
background-color: #212529; /* .table-dark head */
}
.table-dark td, .table-dark th { vertical-align: middle; }
</style>
}); <script src="scripts/jquery-3.5.1.min.js"></script>
</head>
function refreshAufgabenTable(){ <body>
$('#AufgabenTableHolder').load('sources/getAufgabenTable.php', function(){
setTimeout(refreshAufgabenTable, 30000);
});
}
// function refreshCheckInTable(){
// $('#getCheckInTableHolder').load('sources/getCheckInTable.php', function(){
// setTimeout(refreshCheckInTable, 5000);
// });
// }
</script>
</head>
<body style="background-color: black;">
<div class="container-fluid">
<div class="row">
<div class="col-lg">
<h2 class="text-light">Aufgaben</h2>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Bearbeiter</th>
<th scope="col">Aufgabe</th>
<th scope="col">Zieldatum</th>
<th scope="col">Priorität</th>
</tr>
</thead>
<tbody id="AufgabenTableHolder">
</tbody>
</table>
</div>
</header>
<div class="container-fluid py-3">
<div class="row">
<div class="col-12">
<h2 class="text-light mb-3">Aufgaben</h2>
<!-- SCROLL CONTAINER -->
<div id="aufgaben-scroll" class="tableFixHead">
<table id="aufgaben-table" class="table table-dark table-striped table-hover mb-0">
<thead>
<tr>
<th scope="col" style="width:6%">#</th>
<th scope="col" style="width:24%">Bearbeiter</th>
<th scope="col" style="width:40%">Aufgabe</th>
<th scope="col" style="width:15%">Ziel</th>
<th scope="col" style="width:15%">Prio</th>
</tr>
</thead>
<tbody id="AufgabenTableHolder">
<!-- wird via AJAX gefüllt -->
</tbody>
</table>
</div>
<!-- /SCROLL CONTAINER -->
</div>
</div>
</div> </div>
<!-- Bootstrap JS -->
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<!-- === Scroll/Reload Script (wie bei CheckIn/CheckOut) === -->
<script type="text/javascript">
/* ===========================
Feature-Toggle aus PHP
=========================== */
const SCROLL_FLAGS = {
aufgaben: <?php echo (defined('EnableScrollingAufgaben') && EnableScrollingAufgaben) ? 'true' : 'false'; ?>
};
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> function isEnabled() { return !!SCROLL_FLAGS.aufgaben; }
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed --> /* ===========================
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script> Höhe der Scroll-Container
</body> - Voller Viewport ab Oberkante
=========================== */
function sizeScrollContainers() {
const node = document.querySelector('#aufgaben-scroll');
if (!node) return;
const rect = node.getBoundingClientRect();
const bottomMargin = 16;
const minH = 120;
const available = window.innerHeight - rect.top - bottomMargin;
node.style.maxHeight = Math.max(minH, available) + 'px';
}
/* ===========================
Scroll-/Interaktionsstatus
=========================== */
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
function ensureState(el) {
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
return scrollState.get(el);
}
function attachScrollGuards($scroller) {
const el = $scroller.get(0);
if (!el || el.__guardsBound) return;
if (!isEnabled()) return;
const state = ensureState(el);
const markActive = () => {
state.userActive = true;
clearTimeout(state._quietT);
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
stopAutoScroll($scroller);
};
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
el.__guardsBound = true;
}
/* ===========================
Nahtloser Loop (Clone)
=========================== */
function buildSeamlessLoop($table, $scroller) {
$table.find('tbody.__loopClone').remove();
const scEl = $scroller.get(0);
const st = ensureState(scEl);
st.loopHeight = 0;
if (!isEnabled()) return;
const $main = $table.find('tbody').first();
if ($main.length === 0 || $main.children().length === 0) return;
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
if (!needScroll) return;
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
$table.append($clone);
st.loopHeight = $main.get(0).offsetHeight;
}
/* ===========================
Auto-Scroll
=========================== */
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
const el = $scroller.get(0);
if (!isEnabled()) return;
const st = ensureState(el);
if (st.autoTimer) return;
if (st.loopHeight <= 0) return;
const tick = () => {
if (st.userActive) { stopAutoScroll($scroller); return; }
el.scrollTop += speedPx;
if (el.scrollTop >= st.loopHeight) el.scrollTop -= st.loopHeight;
};
st.autoTimer = setInterval(tick, stepMs);
}
function stopAutoScroll($scroller) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.autoTimer) {
clearInterval(st.autoTimer);
st.autoTimer = null;
}
}
function maybeStartAutoScroll($scroller) {
const el = $scroller.get(0);
if (!isEnabled()) { stopAutoScroll($scroller); return; }
const st = ensureState(el);
if (st.userActive) return;
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
else stopAutoScroll($scroller);
}
/* ===========================
Smart AJAX Reload
=========================== */
function smartLoad($scroller, $table, $target, url, intervalMs) {
const scEl = $scroller.get(0);
const st = ensureState(scEl);
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
const posRatio = Math.min(1, posInLoop / oldLoop);
$target.load(url, function () {
buildSeamlessLoop($table, $scroller);
if (st.loopHeight > 0 && isEnabled()) {
const newPos = Math.floor(posRatio * st.loopHeight);
if (Math.abs(scEl.scrollTop - newPos) > 1) {
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
}
stopAutoScroll($scroller);
startAutoScroll($scroller, 1, 80);
} else {
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
});
}
/* ===========================
Initialisierung
=========================== */
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', function () {
sizeScrollContainers();
const $scroller = $('#aufgaben-scroll');
const $table = $('#aufgaben-table');
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
// Guards nur aktivieren, wenn Scrolling erlaubt
if (isEnabled()) attachScrollGuards($('#aufgaben-scroll'));
// Erstladen
(function init() {
const $scroller = $('#aufgaben-scroll');
const $table = $('#aufgaben-table');
const $target = $('#AufgabenTableHolder');
const url = 'sources/getAufgabenTable.php';
const interval = 60000;
$target.load(url, function () {
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
setTimeout(() => smartLoad($scroller, $table, $target, url, interval), interval);
});
})();
});
</script>
</body>
</html> </html>
<?php <?php
// (Optional hier nicht benötigt, aber gelassen falls du später etwas anzeigst)
function getTimeFromSeconds(string $timestring) { function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);
$hours = floor($timestring / 3600); $mins = floor($timestring / 60 % 60);
$mins = floor($timestring / 60 % 60); $timeFormat = sprintf('%02d:%02d', $hours, $mins);
$secs = floor($timestring % 60); return $timeFormat;
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
} }
?> ?>

View File

@@ -12,7 +12,7 @@ public function requestEpiApi(string $requestString){
$requestUrl = Epirent_Connectionprotocol."://".Epirent_Server.":".Epirent_Port.$requestString; $requestUrl = Epirent_Connectionprotocol."://".Epirent_Server.":".Epirent_Port.$requestString;
$response = $client->request('GET', $requestUrl,[ $response = $client->request('GET', $requestUrl,[
'headers' =>[ 'headers' =>[
'X-EPI-NO-SESSION' => 'True', 'X-EPI-NO-SESSION' => 'true',
'X-EPI-ACC-TOK' => Epirent_Token 'X-EPI-ACC-TOK' => Epirent_Token
] ]
]); ]);

View File

@@ -1,11 +1,9 @@
<?php <?php
require('config.php'); require('config.php');
require('EpiApi.php'); require('EpiApi.php');
require('vendor/autoload.php'); require('vendor/autoload.php');
$Epi = new Epirent(); $Epi = new Epirent();
?> ?>
@@ -21,155 +19,418 @@ $Epi = new Epirent();
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css" > <link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css" >
<script src="scripts/jquery-3.5.1.min.js"></script> <script src="scripts/jquery-3.5.1.min.js"></script>
<link href="css/sticky-footer.css" rel="stylesheet"> <link href="css/sticky-footer.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <style>
<!--[if lt IE 9]> @media (min-width: 1800px) {
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> .row {
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> flex-wrap: nowrap; /* verhindert Umbruch */
<![endif]--> }
/* Checkout + Checkin: je 45% */
.row > .col-sm:nth-child(1),
.row > .col-sm:nth-child(2) {
flex: 0 0 35%;
max-width: 35%;
}
/* Aufgaben: 10% */
.row > .col-sm:nth-child(3) {
flex: 0 0 30%;
max-width: 30%;
}
}
.table td, .table th {
padding: .25rem;
}
</style>
<script type="text/javascript"> <script type="text/javascript">
// Dynamische Höhe: Wrapper exakt bis Viewport-Unterkante // === PHP-Flags in JS bringen ===
const SCROLL_FLAGS = {
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>,
aufgaben: <?php echo (defined('EnableScrollingAufgaben') && EnableScrollingAufgaben) ? 'true' : 'false'; ?>
};
// Mapping: DOM-ID -> Flag-Key
const SCROLLER_KEYS = new Map([
['checkout-scroll', 'checkout'],
['checkin-scroll', 'checkin'],
['aufgaben-scroll', 'aufgaben']
]);
const TABLE_KEYS = new Map([
['checkout-table', 'checkout'],
['checkin-table', 'checkin'],
['aufgaben-table', 'aufgaben']
]);
function keyForEl(el, map) {
if (!el || !el.id) return null;
return map.get(el.id) || null;
}
function isEnabledByEl(el) {
const k = keyForEl(el, SCROLLER_KEYS);
return !!(k && SCROLL_FLAGS[k]);
}
function isEnabledByKey(key) {
return !!SCROLL_FLAGS[key];
}
// === Höhe der Scroll-Container: nebeneinander = volle Höhe; gestapelt = Drittel ===
function sizeScrollContainers() { function sizeScrollContainers() {
$('.tableFixHead').each(function () { const nodes = [
const rect = this.getBoundingClientRect(); document.querySelector('#checkout-scroll'),
const bottomMargin = 16; // kleiner Abstand zum Rand document.querySelector('#checkin-scroll'),
const available = window.innerHeight - rect.top - bottomMargin; document.querySelector('#aufgaben-scroll')
this.style.maxHeight = (available > 120 ? available : 120) + 'px'; ].filter(Boolean);
if (nodes.length === 0) return;
// Ermitteln, ob alles in einer Zeile (nebeneinander) oder gestapelt
const tops = nodes.map(n => n.getBoundingClientRect().top);
const ROW_TOL = 8; // px
const uniqueRows = [];
tops.forEach(t => {
const exists = uniqueRows.some(rt => Math.abs(rt - t) <= ROW_TOL);
if (!exists) uniqueRows.push(t);
});
const isOneRow = uniqueRows.length === 1;
const bottomMargin = 16;
const minH = 120;
nodes.forEach(node => {
// Wenn Scrolling für diesen Bereich deaktiviert ist, Höhe trotzdem setzen (wie gefordert)
const rect = node.getBoundingClientRect();
if (isOneRow) {
const available = window.innerHeight - rect.top - bottomMargin;
node.style.maxHeight = Math.max(minH, available) + 'px';
} else {
const third = Math.floor(window.innerHeight / 3) - bottomMargin;
node.style.maxHeight = Math.max(minH, third) + 'px';
}
}); });
} }
// Lädt tbody via AJAX und erhält die Scrollposition relativ zum unteren Rand // === Scroll-/Interaktionsstatus je Scroller ===
function smartLoad($scroller, $target, url, intervalMs) { const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
const scroller = $scroller.get(0);
// Abstand vom unteren Rand merken (wichtiger als absolute scrollTop) function ensureState(el) {
const fromBottomBefore = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop; if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
return scrollState.get(el);
}
function attachScrollGuards($scroller) {
const el = $scroller.get(0);
if (!el || el.__guardsBound) return;
if (!isEnabledByEl(el)) return; // nur wenn Feature aktiv
const state = ensureState(el);
const markActive = () => {
state.userActive = true;
clearTimeout(state._quietT);
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
stopAutoScroll($scroller);
};
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
el.__guardsBound = true;
}
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf & nur wenn aktiv ===
function buildSeamlessLoop($table, $scroller) {
$table.find('tbody.__loopClone').remove();
const scEl = $scroller.get(0);
const enabled = isEnabledByEl(scEl);
const st = ensureState(scEl);
st.loopHeight = 0;
if (!enabled) return; // deaktiviert -> keine Loops
const $main = $table.find('tbody').first();
if ($main.length === 0 || $main.children().length === 0) return;
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
if (!needScroll) return;
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
$table.append($clone);
st.loopHeight = $main.get(0).offsetHeight; // Höhe des Original-Inhalts als Loop-Länge
}
// === Auto-Scroll (nahtlos) ===
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
const el = $scroller.get(0);
if (!isEnabledByEl(el)) return;
const st = ensureState(el);
if (st.autoTimer) return; // schon aktiv
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
const tick = () => {
if (st.userActive) { stopAutoScroll($scroller); return; }
el.scrollTop += speedPx;
if (el.scrollTop >= st.loopHeight) {
el.scrollTop -= st.loopHeight;
}
};
st.autoTimer = setInterval(tick, stepMs);
}
function stopAutoScroll($scroller) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.autoTimer) {
clearInterval(st.autoTimer);
st.autoTimer = null;
}
}
function maybeStartAutoScroll($scroller) {
const el = $scroller.get(0);
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
const st = ensureState(el);
if (st.userActive) return;
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
else stopAutoScroll($scroller);
}
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
function smartLoad($scroller, $table, $target, url, intervalMs) {
const scEl = $scroller.get(0);
const st = ensureState(scEl);
const enabled = isEnabledByEl(scEl);
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
const posRatio = Math.min(1, posInLoop / oldLoop);
$target.load(url, function () { $target.load(url, function () {
// Nach dem Ersetzen: dieselbe Distanz zum unteren Rand wiederherstellen if (enabled) {
const newScrollTop = scroller.scrollHeight - scroller.clientHeight - fromBottomBefore; buildSeamlessLoop($table, $scroller);
// Begrenzen, falls weniger Inhalt if (st.loopHeight > 0) {
scroller.scrollTop = Math.max(0, newScrollTop); const newPos = Math.floor(posRatio * st.loopHeight);
if (Math.abs(scEl.scrollTop - newPos) > 1) {
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
}
stopAutoScroll($scroller);
startAutoScroll($scroller, 1, 80);
} else {
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
}
// Nächstes Update planen setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
setTimeout(function () {
smartLoad($scroller, $target, url, intervalMs);
}, intervalMs);
}); });
} }
// === Initialisierung ===
$(document).ready(function () { $(document).ready(function () {
sizeScrollContainers(); sizeScrollContainers();
$(window).on('resize', sizeScrollContainers);
// Initial laden + Auto-Refresh, jeweils eigener Scroller $(window).on('resize', function () {
smartLoad($('#checkout-scroll'), $('#getCheckOutTableHolder'), 'sources/getCheckOutTable.php', 5000); sizeScrollContainers();
smartLoad($('#checkin-scroll'), $('#getCheckInTableHolder'), 'sources/getCheckInTable.php', 5000); ['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
smartLoad($('#aufgaben-scroll'), $('#AufgabenTableHolder'), 'sources/getAufgabenTable.php', 30000); const $scroller = $(`${prefix}-scroll`);
const $table = $(`${prefix}-table`);
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
});
// Guards nur für aktivierte Scroller
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin'], ['#aufgaben-scroll','aufgaben']].forEach(([sel,key]) => {
if (isEnabledByKey(key)) attachScrollGuards($(sel));
});
// Erstladen je Tabelle
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
const $scroller = $(scrollerSel);
const $table = $(tableSel);
const $target = $(tbodySel);
const enabled = isEnabledByKey(key);
$target.load(url, function () {
if (enabled) {
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
}
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
});
}
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 10000, 'checkout');
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 10000, 'checkin');
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 60000, 'aufgaben');
}); });
</script> </script>
<style> <style>
/* Scroll-Wrapper je Tabelle */ /* Scroll-Wrapper je Tabelle */
.tableFixHead { .tableFixHead {
overflow-y: auto; overflow-y: auto;
/* Höhe wird per JS dynamisch gesetzt, damit genau bis zum Viewport-Ende gescrollt wird */ /* Höhe wird per JS dynamisch gesetzt, damit genau bis zum Viewport-Ende gescrollt wird */
max-height: 60vh; /* Fallback */ max-height: 60vh; /* Fallback */
border: 1px solid rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.1);
border-radius: .25rem; border-radius: .25rem;
} }
/* Sticky Header */ /* Sticky Header */
.tableFixHead thead th { .tableFixHead thead th {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 2; /* über Body-Zellen */ z-index: 2; /* über Body-Zellen */
} }
/* Saubere Hintergrundfarbe für sticky Header (Bootstrap .table-dark) */ /* Saubere Hintergrundfarbe für sticky Header (Bootstrap .table-dark) */
.table-dark thead th { .table-dark thead th {
background-color: #212529; /* gleiche Farbe wie .table-dark header */ background-color: #212529; /* gleiche Farbe wie .table-dark header */
} }
/* Optional: dünne Trennlinien */ /* Optional: dünne Trennlinien */
.table-dark tbody tr + tr td { .table-dark tbody tr + tr td {
border-top: 1px solid rgba(255,255,255,.08); border-top: 1px solid rgba(255,255,255,.08);
} }
</style> </style>
</head> </head>
<body style="background-color: black;"> <body style="margin-bottom:0px; background-color: black;">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-sm"> <div class="col-sm" style="padding: 5px !important;">
<h2 class="text-light">Check-Out</h2> <h2 class="text-light">Check-Out</h2>
<div class="tableFixHead" id="checkout-scroll"> <div class="tableFixHead" id="checkout-scroll">
<table class="table table-dark mb-0" id="checkout-table"> <table class="table table-dark mb-0" id="checkout-table">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col">Kunde</th> <th scope="col">Kunde</th>
<th scope="col">Event</th> <th scope="col">Event</th>
<th scope="col">Dispo-Start<br><i>VB-Start</i></th>
<?php
if(!HideCheckInTimeOnCheckout){ <?php
echo "<th scope='col'>Dispo-Ende<br><i>VB-Ende</i></th>"; if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
} echo '<th scope="col">';
?> }
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="getCheckOutTableHolder"></tbody>
</table>
</div>
</div>
<div class="col-sm"> if(ShowCheckoutTimeOnCheckout){
<h2 class="text-light">Check-In</h2> echo "Dispo-Start";
<div class="tableFixHead" id="checkin-scroll"> }
<table class="table table-dark mb-0" id="checkin-table"> if(ShowVorbereitungTimeOnCheckout){
<thead> if(ShowCheckoutTimeOnCheckout){echo "<br>";}
<tr> echo "VB-Start";
<th scope="col">#</th> }
<th scope="col">Kunde</th> if(ShowPackagingTimeOnCheckout){
<th scope="col">Event</th> if(ShowCheckoutTimeOnCheckout||ShowVorbereitungTimeOnCheckout){echo "<br>";}
<?php echo "Packen";
}
if(!HideCheckOutTimeOnCheckin){ if(ShowDeliveryTimeOnCheckout){
echo "<th scope='col'>Dispo-Start<br><i>RP-Start</i></th>"; if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout){echo "<br>";}
} echo "Liefern";
?> }
<th scope="col">Dispo-Ende<br><i>RP-Ende</i></th> if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
<th scope="col">Status</th> echo '</th>';
</tr> }
</thead>
<tbody id="getCheckInTableHolder"></tbody>
</table>
</div>
</div>
<div class="col-sm">
<h2 class="text-light">Aufgaben</h2>
<div class="tableFixHead" id="aufgaben-scroll">
<table class="table table-dark mb-0" id="aufgaben-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Bearbeiter</th>
<th scope="col">Aufgabe</th>
<th scope="col">Zieldatum</th>
<th scope="col">Priorität</th>
</tr>
</thead>
<tbody id="AufgabenTableHolder"></tbody>
</table>
</div>
</div>
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckOutTableHolder"></tbody>
</table>
</div>
</div> </div>
<div class="col-sm" style="padding: 5px !important;">
<h2 class="text-light">Check-In</h2>
<div class="tableFixHead" id="checkin-scroll">
<table class="table table-dark mb-0" id="checkin-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th> <?php
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin ||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '<th scope="col">';
}
if(ShowCheckInTimeOnCheckin){
echo "Dispo-Ende";
}
if(ShowNachbereitungTimeOnCheckin){
if(ShowCheckInTimeOnCheckin){echo "<br>";}
echo "Nachbereitung";
}
if(ShowRePackagingTimeOnCheckin){
if(ShowCheckInTimeOnCheckin||ShowNachbereitungTimeOnCheckin){echo "<br>";}
echo "Zurückpacken";
}
if(ShowReDeliveryTimeOnCheckin){
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin){echo "<br>";}
echo "Rückliefern";
}
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '</th>';
}
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckInTableHolder"></tbody>
</table>
</div>
</div>
<div class="col-sm" style="padding: 5px !important;">
<h2 class="text-light">Aufgaben</h2>
<div class="tableFixHead" id="aufgaben-scroll">
<table class="table table-dark mb-0" id="aufgaben-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Bearbeiter</th>
<th scope="col">Aufgabe</th>
<th scope="col">Ziel</th>
<th scope="col">Prio</th>
</tr>
</thead>
<tbody id="AufgabenTableHolder"></tbody>
</table>
</div>
</div>
</div>
@@ -182,7 +443,6 @@ $Epi = new Epirent();
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed --> <!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script> <script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body> </body>

View File

@@ -1,9 +1,4 @@
<?php <?php
error_reporting(E_ALL);
require('config.php'); require('config.php');
require('EpiApi.php'); require('EpiApi.php');
require('vendor/autoload.php'); require('vendor/autoload.php');
@@ -14,143 +9,383 @@ $Epi = new Epirent();
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <title>Packmonitor</title>
<title>Packmonitor</title>
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css" > <link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css">
<script src="scripts/jquery-3.5.1.min.js"></script> <script src="scripts/jquery-3.5.1.min.js"></script>
<link href="css/sticky-footer.css" rel="stylesheet"> <link href="css/sticky-footer.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { /* ===========================
refreshCheckOutTable(); Feature-Toggles aus PHP
refreshCheckInTable(); =========================== */
}); const SCROLL_FLAGS = {
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>
};
function refreshCheckOutTable() { const SCROLLER_KEYS = new Map([
$('#getCheckOutTableHolder').load('sources/getCheckOutTable.php', function () { ['checkout-scroll', 'checkout'],
setTimeout(refreshCheckOutTable, 5000); ['checkin-scroll', 'checkin']
}); ]);
function keyForEl(el) { return el && el.id ? SCROLLER_KEYS.get(el.id) : null; }
function isEnabledByEl(el) { const k = keyForEl(el); return !!(k && SCROLL_FLAGS[k]); }
function isEnabledByKey(key) { return !!SCROLL_FLAGS[key]; }
/* ===========================
Höhe der Scroll-Container
- 2 nebeneinander: volle Höhe bis Viewport-Ende
- gestapelt: je 1/2 Fensterhöhe
=========================== */
function sizeScrollContainers() {
const nodes = [
document.querySelector('#checkout-scroll'),
document.querySelector('#checkin-scroll')
].filter(Boolean);
if (!nodes.length) return;
const tops = nodes.map(n => n.getBoundingClientRect().top);
const ROW_TOL = 8;
const firstTop = tops[0];
const isOneRow = tops.every(t => Math.abs(t - firstTop) <= ROW_TOL);
const bottomMargin = 16;
const minH = 120;
nodes.forEach(node => {
const rect = node.getBoundingClientRect();
if (isOneRow) {
const available = window.innerHeight - rect.top - bottomMargin;
node.style.maxHeight = Math.max(minH, available) + 'px';
} else {
const half = Math.floor(window.innerHeight / 2) - bottomMargin;
node.style.maxHeight = Math.max(minH, half) + 'px';
}
});
}
/* ===========================
Scroll-/Interaktionsstatus
=========================== */
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
function ensureState(el) {
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
return scrollState.get(el);
}
function attachScrollGuards($scroller) {
const el = $scroller.get(0);
if (!el || el.__guardsBound) return;
if (!isEnabledByEl(el)) return;
const state = ensureState(el);
const markActive = () => {
state.userActive = true;
clearTimeout(state._quietT);
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
stopAutoScroll($scroller);
};
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
el.__guardsBound = true;
}
/* ===========================
Nahtloser Loop (Clone)
=========================== */
function buildSeamlessLoop($table, $scroller) {
$table.find('tbody.__loopClone').remove();
const scEl = $scroller.get(0);
const enabled = isEnabledByEl(scEl);
const st = ensureState(scEl);
st.loopHeight = 0;
if (!enabled) return;
const $main = $table.find('tbody').first();
if ($main.length === 0 || $main.children().length === 0) return;
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
if (!needScroll) return;
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
$table.append($clone);
st.loopHeight = $main.get(0).offsetHeight;
}
/* ===========================
Auto-Scroll
=========================== */
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
const el = $scroller.get(0);
if (!isEnabledByEl(el)) return;
const st = ensureState(el);
if (st.autoTimer) return;
if (st.loopHeight <= 0) return;
const tick = () => {
if (st.userActive) { stopAutoScroll($scroller); return; }
el.scrollTop += speedPx;
if (el.scrollTop >= st.loopHeight) el.scrollTop -= st.loopHeight;
};
st.autoTimer = setInterval(tick, stepMs);
}
function stopAutoScroll($scroller) {
const el = $scroller.get(0);
const st = ensureState(el);
if (st.autoTimer) {
clearInterval(st.autoTimer);
st.autoTimer = null;
}
}
function maybeStartAutoScroll($scroller) {
const el = $scroller.get(0);
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
const st = ensureState(el);
if (st.userActive) return;
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
else stopAutoScroll($scroller);
}
/* ===========================
Smart AJAX Reload
=========================== */
function smartLoad($scroller, $table, $target, url, intervalMs) {
const scEl = $scroller.get(0);
const st = ensureState(scEl);
const enabled = isEnabledByEl(scEl);
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
const posRatio = Math.min(1, posInLoop / oldLoop);
$target.load(url, function () {
if (enabled) {
buildSeamlessLoop($table, $scroller);
if (st.loopHeight > 0) {
const newPos = Math.floor(posRatio * st.loopHeight);
if (Math.abs(scEl.scrollTop - newPos) > 1) {
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
}
stopAutoScroll($scroller);
startAutoScroll($scroller, 1, 40);
} else {
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
}
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
});
}
/* ===========================
Initialisierung
=========================== */
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', function () {
sizeScrollContainers();
['#checkout', '#checkin'].forEach(prefix => {
const $scroller = $(`${prefix}-scroll`);
const $table = $(`${prefix}-table`);
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
});
// Guards nur für aktivierte Scroller
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin']].forEach(([sel,key]) => {
if (isEnabledByKey(key)) attachScrollGuards($(sel));
});
// Erstladen je Tabelle
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
const $scroller = $(scrollerSel);
const $table = $(tableSel);
const $target = $(tbodySel);
const enabled = isEnabledByKey(key);
$target.load(url, function () {
if (enabled) {
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
}
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
});
}
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 10000, 'checkout');
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 10000, 'checkin');
});
</script>
<style>
/* Scroll-Wrapper je Tabelle */
.tableFixHead {
overflow-y: auto;
/* Höhe wird per JS dynamisch gesetzt, damit genau bis zum Viewport-Ende gescrollt wird */
max-height: 60vh; /* Fallback */
border: 1px solid rgba(255,255,255,.1);
border-radius: .25rem;
}
/* Sticky Header */
.tableFixHead thead th {
position: sticky;
top: 0;
z-index: 2; /* über Body-Zellen */
}
/* Saubere Hintergrundfarbe für sticky Header (Bootstrap .table-dark) */
.table-dark thead th {
background-color: #212529; /* gleiche Farbe wie .table-dark header */
}
/* Optional: dünne Trennlinien */
.table-dark tbody tr + tr td {
border-top: 1px solid rgba(255,255,255,.08);
}
body { background-color: black; margin-bottom: 0; }
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Check-Out -->
<div class="col-xl">
<h2 class="text-light">Check-Out</h2>
<div class="tableFixHead" id="checkout-scroll">
<table class="table table-dark mb-0" id="checkout-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '<th scope="col">';
}
if(ShowCheckoutTimeOnCheckout){
echo "Dispo-Start";
} }
function refreshCheckInTable() { if(ShowVorbereitungTimeOnCheckout){
$('#getCheckInTableHolder').load('sources/getCheckInTable.php', function () { if(ShowCheckoutTimeOnCheckout){echo "<br>";}
setTimeout(refreshCheckInTable, 5000); echo "VB-Start";
});
} }
if(ShowPackagingTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout||ShowVorbereitungTimeOnCheckout){echo "<br>";}
echo "Packen";
}
if(ShowDeliveryTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout){echo "<br>";}
echo "Liefern";
}
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '</th>';
}
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckOutTableHolder"></tbody>
</table>
</div>
</div>
<!-- Check-In -->
<div class="col-xl">
<h2 class="text-light">Check-In</h2>
<div class="tableFixHead" id="checkin-scroll">
<table class="table table-dark mb-0" id="checkin-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin ||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '<th scope="col">';
}
</script> if(ShowCheckInTimeOnCheckin){
echo "Dispo-Ende";
</head> }
<body style="background-color: black;"> if(ShowNachbereitungTimeOnCheckin){
if(ShowCheckInTimeOnCheckin){echo "<br>";}
<div class="container-fluid"> echo "Nachbereitung";
<div class="row"> }
<div class="col-lg"> if(ShowRePackagingTimeOnCheckin){
<h2 class="text-light">Check-Out if(ShowCheckInTimeOnCheckin||ShowNachbereitungTimeOnCheckin){echo "<br>";}
<?php echo "Zurückpacken";
if (CheckOut_FutureDays != -1) { }
echo "in den nächsten " . CheckOut_FutureDays . " Tagen"; if(ShowReDeliveryTimeOnCheckin){
} if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin){echo "<br>";}
?></h2> echo "Rückliefern";
<table class="table table-dark"> }
<thead> if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
<tr> echo '</th>';
<th scope="col">#</th> }
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<th scope="col">Dispo-Start<br><i>VB-Start</i></th>
<?php
if(!HideCheckInTimeOnCheckout){
echo "<th scope='col'>Dispo-Ende<br><i>VB-Ende</i></th>";
}
?>
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="getCheckOutTableHolder">
</tbody>
</table>
</div>
<div class="col-lg">
<h2 class="text-light">Check-In
<?php
if (CheckIn_FutureDays != -1) {
echo "in den nächsten " . CheckIn_FutureDays . " Tagen";
}
?></h2>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if(!HideCheckOutTimeOnCheckin){
echo "<th scope='col'>Dispo-Start<br><i>RP-Start</i></th>";
}
?>
<th scope="col">Dispo-Ende<br><i>RP-Ende</i></th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="getCheckInTableHolder">
</tbody>
</table>
</div>
</div>
</header>
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckInTableHolder"></tbody>
</table>
</div>
</div>
</div>
</div> </div>
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
</body>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>
</html> </html>
<?php <?php
function getTimeFromSeconds(string $timestring) { function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);
$hours = floor($timestring / 3600); $mins = floor($timestring / 60 % 60);
$mins = floor($timestring / 60 % 60); return sprintf('%02d:%02d', $hours, $mins);
$secs = floor($timestring % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
} }
?> ?>

View File

@@ -1,8 +1,8 @@
# EPIWebview # EPIWebview
**aktuellste stable Version:** 1.5.0 - **aktuellste stable Version:** 1.5.0
**Lizenz:** Creative Commons Attribution-NonCommercial-ShareAlike 4.0 (CC BY-NC-SA 4.0). Einsehbar unter: (LICENSE.MD) - **Lizenz:** Creative Commons Attribution-NonCommercial-ShareAlike 4.0 (CC BY-NC-SA 4.0). Einsehbar unter: (LICENSE.MD)
**Kompatibilität:** Erweiterung für **Epirent** und **CrewBrain** - **Kompatibilität:** Erweiterung für **Epirent** und **CrewBrain**
--- ---
@@ -36,12 +36,7 @@ Die Anwendung ist speziell für den Einsatz in Lagerprozessen entwickelt.
1. Repository klonen oder Dateien ins Webverzeichnis kopieren: 1. Repository klonen oder Dateien ins Webverzeichnis kopieren:
```bash ```bash
git clone http://srvgitea01.vtm.zone:3000/epi/EpiWebview git clone http://srvgitea01.vtm.zone:3000/epi/EpiWebview
2. Dashboard Aufrufen und auf die Config-Seite wechseln, notwendige Einstellungen vornehmen.
## Changelog ## Changelog
### Änderungen in Version 1.5.0 Verschoben in Releases (Git)
**Neu:**
- Die Spalte **„CheckIn“** kann beim **Checkout** ausgeblendet werden.
- Die Spalte **„CheckOut“** kann beim **Checkin** ausgeblendet werden.
--- ---

View File

@@ -2,6 +2,9 @@
"require": { "require": {
"guzzlehttp/guzzle": "^7.0", "guzzlehttp/guzzle": "^7.0",
"twbs/bootstrap": "^4.5", "twbs/bootstrap": "^4.5",
"chillerlan/php-qrcode": "^5.0" "chillerlan/php-qrcode": "^5.0",
"phpoffice/phpspreadsheet": "^5.1",
"tecnickcom/tc-lib-pdf": "^8.1",
"tecnickcom/tcpdf": "^6.10"
} }
} }

1419
composer.lock generated

File diff suppressed because it is too large Load Diff

413
dist/editconfig.php vendored Normal file
View File

@@ -0,0 +1,413 @@
<?php
declare(strict_types=1);
/**
* Config-Editor für EPI Webview
* - Liest ../config.php (falls vorhanden) und ../example.config.php
* - Zeigt editierbare Felder für alle Keys aus example.config.php
* - Zeigt veraltete (nur in config.php existierende) Keys rot & disabled
* - Speichert Werte zurück nach ../config.php
*/
error_reporting(E_ALL);
// ---- Dateipfade anpassen falls nötig ----
$CONFIG_FILE = realpath(__DIR__ . '/../config.php') ?: (__DIR__ . '/../config.php');
$EXAMPLE_FILE = realpath(__DIR__ . '/../example.config.php') ?: (__DIR__ . '/../example.config.php');
// ---- Hilfsfunktionen: Parsing ----
function parse_define_file(string $file): array {
if (!is_file($file)) return ['defines' => [], 'order' => [], 'ui' => []];
$lines = file($file, FILE_IGNORE_NEW_LINES);
$defines = [];
$order = [];
$ui = [];
// BOM entfernen, falls vorhanden
if (isset($lines[0])) {
$lines[0] = preg_replace('/^\xEF\xBB\xBF/', '', $lines[0]);
}
// Regex
$reDefine = '/^\s*define\(\s*([\'"])([^\'"]+)\1\s*,\s*(.+?)\s*\)\s*;\s*(?:(?:\/\/|#)\s*(.*))?$/u';
$reSection = '/^\s*\/\/\s*@section\s*:\s*(.+)\s*$/iu';
$reHR = '/^\s*\/\/\s*@hr\s*$/iu';
$reNote = '/^\s*\/\/\s*@note\s*:\s*(.+)\s*$/iu';
foreach ($lines as $ln) {
// Meta-Kommentare zuerst prüfen (UI-Struktur)
if (preg_match($reSection, $ln, $m)) {
$ui[] = ['kind' => 'section', 'title' => trim($m[1])];
continue;
}
if (preg_match($reHR, $ln)) {
$ui[] = ['kind' => 'hr'];
continue;
}
if (preg_match($reNote, $ln, $m)) {
$ui[] = ['kind' => 'note', 'text' => trim($m[1])];
continue;
}
// define(…) Zeilen
if (!preg_match($reDefine, $ln, $m)) {
continue;
}
$name = $m[2];
$rawValueExpr = trim($m[3]);
$inlineC = isset($m[4]) ? trim($m[4]) : '';
// Typ bestimmen
$type = 'string';
$valForForm = '';
$raw = rtrim($rawValueExpr, ',');
if (preg_match('/^(true|false)$/i', $raw)) {
$type = 'bool';
$valForForm = (strtolower($raw) === 'true');
} elseif (preg_match('/^[+-]?\d+$/', $raw)) {
$type = 'int';
$valForForm = (int)$raw;
} elseif (preg_match('/^([\'"])(.*)\1$/s', $raw, $mm)) {
$type = 'string';
$valForForm = stripcslashes($mm[2]);
} else {
$type = 'string';
$valForForm = $raw;
}
$defines[$name] = [
'name' => $name,
'type' => $type,
'value' => $valForForm,
'raw' => $rawValueExpr,
'comment' => $inlineC,
'line' => $ln,
];
$order[] = $name;
// Wichtig: define auch in die UI-Reihenfolge aufnehmen
$ui[] = ['kind' => 'define', 'name' => $name];
}
return ['defines' => $defines, 'order' => $order, 'ui' => $ui];
}
function php_export_define_value(string $type, $value): string {
// Baut den rechten Teil von define('X', <HIER>);
if ($type === 'bool') {
return $value ? 'true' : 'false';
}
if ($type === 'int') {
return (string)intval($value);
}
// String:
// einfache Quotes nehmen und escapen
$escaped = str_replace("'", "\\'", (string)$value);
return "'" . $escaped . "'";
}
function h(?string $s): string { return htmlspecialchars((string)$s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
// ---- Dateien parsen ----
$example = parse_define_file($EXAMPLE_FILE);
$current = parse_define_file($CONFIG_FILE);
// Keys klassifizieren
$exampleKeys = array_keys($example['defines']);
$currentKeys = array_keys($current['defines']);
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys)); // nur in config.php
$newKeys = array_values(array_diff($exampleKeys, $currentKeys)); // nur in example (neu)
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
// Beim Rendern: Reihenfolge aus example beibehalten
$renderKeys = $example['order'];
// ---- POST: Speichern ----
$message = '';
$err = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['__save_config']) && isset($_POST['cfg']) && is_array($_POST['cfg'])) {
$incoming = $_POST['cfg']; // ['NAME' => valueString/checkbox etc.]
// Build neuer config.php Inhalt:
$out = [];
$out[] = "<?php";
$out[] = "// Diese Datei wurde durch den Epi Webview Config-Editor erzeugt/aktualisiert.";
$out[] = "// Bearbeite Werte bevorzugt über die Weboberfläche.";
$out[] = "";
// --- Inhalt in der Reihenfolge & Struktur der example.config.php ---
foreach ($example['ui'] as $item) {
if ($item['kind'] === 'section') {
$out[] = "// @section: " . $item['title'];
continue;
}
if ($item['kind'] === 'hr') {
$out[] = "// @hr";
continue;
}
if ($item['kind'] === 'note') {
$out[] = "// @note: " . $item['text'];
continue;
}
if ($item['kind'] !== 'define') {
continue;
}
$label = $item['name'];
$ex = $example['defines'][$label] ?? null;
if (!$ex) continue;
$type = $ex['type']; // Typ an example orientieren
$tooltip = $ex['comment'] ?? '';
// Wert aus POST übernehmen (Checkbox-Fix: existiert das Feld?)
if ($type === 'bool') {
$bool = array_key_exists($label, $incoming);
$valueForDefine = php_export_define_value('bool', $bool);
} elseif ($type === 'int') {
$raw = $incoming[$label] ?? '';
$valueForDefine = php_export_define_value('int', $raw);
} else {
$raw = $incoming[$label] ?? '';
$valueForDefine = php_export_define_value('string', $raw);
}
$line = "define('{$label}', {$valueForDefine});";
if (!empty($tooltip)) {
$line .= " // " . $tooltip; // Kommentare als Tooltip auch in config.php mitführen
}
$out[] = $line;
}
// Deprecated Block (nur in aktueller config.php vorhanden, nicht in example)
if (!empty($deprecatedKeys)) {
$out[] = "";
$out[] = "// --- Veraltete/entfernte Parameter (nur Referenz, werden nicht mehr genutzt) ---";
foreach ($deprecatedKeys as $key) {
$c = $current['defines'][$key];
$valueForDefine = php_export_define_value($c['type'], $c['value']);
$line = "// define('{$c['name']}', {$valueForDefine});";
if (!empty($c['comment'])) {
$line .= " // " . $c['comment'];
}
$out[] = $line;
}
}
$out[] = "?>";
$content = implode("\n", $out) . "\n";
try {
if (false === file_put_contents($CONFIG_FILE, $content)) {
$err = "Konnte config.php nicht schreiben: " . h($CONFIG_FILE);
} else {
$message = "Konfiguration gespeichert.";
// Nach dem Speichern neu parsen, damit UI aktuell ist
$current = parse_define_file($CONFIG_FILE);
$currentKeys = array_keys($current['defines']);
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys));
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
}
} catch (Throwable $e) {
$err = "Fehler beim Schreiben: " . h($e->getMessage());
}
}
// ---- Werte für Formular vorbereiten ----
// Für jeden example-Key: Formularwert = aus aktueller config (falls vorhanden), sonst example default
function valueForForm(array $example, array $current, string $key) {
$ex = $example['defines'][$key] ?? null;
$cu = $current['defines'][$key] ?? null;
$type = $ex ? $ex['type'] : ($cu ? $cu['type'] : 'string');
$comment = $ex && $ex['comment'] !== '' ? $ex['comment'] : ($cu['comment'] ?? '');
if ($cu) {
$val = $cu['value'];
} else {
$val = $ex['value'];
}
return [$type, $val, $comment];
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="Konfiguration bearbeiten" />
<meta name="author" content="" />
<title>Konfiguration Epi Webview</title>
<link href="css/styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
<script src="js/jquery-3.5.1.min.js"></script>
<style>
.deprecated { color: #c62828; }
.cfg-help { margin-left: .35rem; cursor: help; }
.form-group { margin-bottom: .75rem; }
.small-muted { font-size:.85rem; color:#6c757d; }
.checkbox-inline { display:flex; align-items:center; gap:.5rem; }
.cfg-label { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
</style>
<script>
$(function(){
// Tooltips (Bootstrap 4)
$('[data-toggle="tooltip"]').tooltip();
// Side UI nachladen wie in deinem Template
loadSidenav();
loadFooter();
});
function loadSidenav(){
$('#layoutSidenav_nav').load('../sources/getSidenav.php');
}
function loadFooter(){
$('#footerholder').load('../sources/getFooter.php');
}
</script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.php">Epi Webview</a>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle" type="button"><i class="fas fa-bars"></i></button>
<ul class="navbar-nav ml-auto ml-md-0"></ul>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Konfiguration</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item active">Dashboard / Konfiguration</li>
</ol>
<?php if ($message): ?>
<div class="alert alert-success"><?= h($message) ?></div>
<?php endif; ?>
<?php if ($err): ?>
<div class="alert alert-danger"><?= h($err) ?></div>
<?php endif; ?>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-sliders-h mr-1"></i>
Einstellungen
<span class="small-muted">( <code><?=h(basename($CONFIG_FILE))?></code> Source: <code><?=h(basename($EXAMPLE_FILE))?></code>)</span>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="__save_config" value="1" />
<?php foreach ($example['ui'] as $item): ?>
<?php
if ($item['kind'] === 'section') {
echo '<h5 class="mt-3">'.h($item['title']).'</h5>';
continue;
}
if ($item['kind'] === 'hr') {
echo '<hr />';
continue;
}
if ($item['kind'] === 'note') {
echo '<p class="small-muted">'.h($item['text']).'</p>';
continue;
}
if ($item['kind'] !== 'define') {
continue;
}
$key = $item['name'];
// Nur Keys rendern, die in example vorkommen
if (!isset($example['defines'][$key])) continue;
[$type, $val, $tooltip] = valueForForm($example, $current, $key);
?>
<div class="form-group">
<label class="cfg-label" for="cfg_<?=h($key)?>">
<?= h($key) ?>
<?php if ($tooltip): ?>
<i class="far fa-question-circle cfg-help"
data-toggle="tooltip" data-placement="right"
title="<?=h($tooltip)?>"></i>
<?php endif; ?>
</label>
<?php if ($type === 'bool'): ?>
<div class="checkbox-inline">
<input type="checkbox"
id="cfg_<?=h($key)?>"
name="cfg[<?=h($key)?>]"
value="1" <?= $val ? 'checked' : '' ?> />
<span><?= $val ? 'Aktiviert' : 'Deaktiviert' ?></span>
</div>
<?php elseif ($type === 'int'): ?>
<input type="number" step="1" class="form-control"
id="cfg_<?=h($key)?>"
name="cfg[<?=h($key)?>]"
value="<?=h((string)$val)?>" />
<?php else: ?>
<input type="text" class="form-control"
id="cfg_<?=h($key)?>"
name="cfg[<?=h($key)?>]"
value="<?=h((string)$val)?>" />
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php if (!empty($deprecatedKeys)): ?>
<hr />
<h5 class="deprecated">Veraltete Parameter (nur in <code><?=h(basename($CONFIG_FILE))?></code>, nicht mehr in <code><?=h(basename($EXAMPLE_FILE))?></code>)</h5>
<?php foreach ($deprecatedKeys as $key): ?>
<?php $c = $current['defines'][$key]; ?>
<div class="form-group">
<label class="cfg-label deprecated" for="dep_<?=h($key)?>">
<?= h($key) ?>
<?php if (!empty($c['comment'])): ?>
<i class="far fa-question-circle cfg-help" data-toggle="tooltip" data-placement="right" title="<?=h($c['comment'])?>"></i>
<?php endif; ?>
</label>
<input type="text" class="form-control is-invalid" id="dep_<?=h($key)?>" value="<?=
h($c['type']==='bool' ? ($c['value']?'true':'false') : (string)$c['value'])
?>" disabled />
<div class="invalid-feedback">Dieser Parameter ist in der aktuellen Version nicht mehr vorgesehen.</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
<div class="mt-4">
<button type="submit" class="btn btn-primary"><i class="fas fa-save mr-1"></i> Speichern</button>
<span class="small-muted ml-2">Speichert nach <code><?=h($CONFIG_FILE)?></code></span>
</div>
</form>
</div>
</div>
</div>
</main>
<div id="footerholder"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="js/scripts.js"></script>
<script>
// Sidebar Toggle (falls benötigt)
$('#sidebarToggle').on('click', function(){ $('body').toggleClass('sb-sidenav-toggled'); });
</script>
</body>
</html>

195
dist/index.php vendored
View File

@@ -1,141 +1,78 @@
<?php
// index.php Dashboard Startseite (lädt Teil-HTMLs wie die Auftragsliste, ohne Auto-Refresh)
require('../config.php');
require('../EpiApi.php');
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" /> <title>Dashboard - EpiWebview</title>
<meta name="author" content="" />
<title>Dashboard - EpiWebview</title>
<link href="css/styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
<script src="js/jquery-3.5.1.min.js"></script>
<script type="text/javascript">
<!-- Styles -->
<link href="css/styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
$(document).ready(function(){ <!-- JS (nur 1x jQuery laden) -->
refreshOrderTable(); <script src="js/jquery-3.5.1.min.js"></script>
loadSidenav(); <script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
loadFooter();
}); <style>
function refreshOrderTable(){ .opacity-50 { opacity: .5; }
$('#OrderTableHolder').load('../sources/getOrders.php', function(){ .card .h2, .card .display-4 { font-weight: 700; }
setTimeout(refreshOrderTable, 5000); .kpi-updated { font-size: .82rem; opacity: .85; }
}); </style>
}
function loadSidenav(){ <script>
// Einmalige Loads analog zur Auftragsliste
$(function () {
loadSidenav();
loadFooter();
});
function loadSidenav() {
$('#layoutSidenav_nav').load('../sources/getSidenav.php'); $('#layoutSidenav_nav').load('../sources/getSidenav.php');
} }
function loadFooter(){
function loadFooter() {
$('#footerholder').load('../sources/getFooter.php'); $('#footerholder').load('../sources/getFooter.php');
} }
</script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.php">Epi Webview</a>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Dashboard</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item active">Dashboard</li>
</ol>
</script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.php">Epi Webview</a>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle" href="#"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav">
</nav>
</div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Dashboard</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item active">Dashboard</li>
</ol>
<div class="row">
<div class="col-xl-3 col-md-6">
<div class="card bg-primary text-white mb-4">
<div class="card-body">Primary Card</div>
<div class="card-footer d-flex align-items-center justify-content-between">
<a class="small text-white stretched-link" href="#">View Details</a>
<div class="small text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-warning text-white mb-4">
<div class="card-body">Warning Card</div>
<div class="card-footer d-flex align-items-center justify-content-between">
<a class="small text-white stretched-link" href="#">View Details</a>
<div class="small text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-success text-white mb-4">
<div class="card-body">Success Card</div>
<div class="card-footer d-flex align-items-center justify-content-between">
<a class="small text-white stretched-link" href="#">View Details</a>
<div class="small text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-danger text-white mb-4">
<div class="card-body">Danger Card</div>
<div class="card-footer d-flex align-items-center justify-content-between">
<a class="small text-white stretched-link" href="#">View Details</a>
<div class="small text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-area mr-1"></i>
Area Chart Example
</div>
<div class="card-body"><canvas id="myAreaChart" width="100%" height="40"></canvas></div>
</div>
</div>
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-bar mr-1"></i>
Bar Chart Example
</div>
<div class="card-body"><canvas id="myBarChart" width="100%" height="40"></canvas></div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-table mr-1"></i>
Aufträge
</div>
<div class="card-body">
<div class="table-responsive" id="OrderTableHolder">
</div> </div>
</div> </main>
</div> <div id="footerholder"></div>
</div> </div>
</main> </div>
<div id="footerholder"></div>
</div> <!-- Bootstrap Bundle (Popper inkl.) -->
</div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="js/jquery-3.5.1.min.js"></script> </body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="js/scripts.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
<script src="assets/demo/chart-area-demo.js"></script>
<script src="assets/demo/chart-bar-demo.js"></script>
<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.10.20/js/dataTables.bootstrap4.min.js" crossorigin="anonymous"></script>
<script src="assets/demo/datatables-demo.js"></script>
</body>
</html> </html>

371
dist/insurance.php vendored Normal file
View File

@@ -0,0 +1,371 @@
<?php
// insurance.php Versicherungsgeräte (liegt in /dist)
require('../config.php');
require('../EpiApi.php');
require_once __DIR__ . '/../vendor/autoload.php'; // PhpSpreadsheet Autoload
date_default_timezone_set('Europe/Berlin');
$Epi = new Epirent();
function formatEuro(float $amount): string {
$formatted = number_format(abs($amount), 2, ',', '.') . ' €';
return $amount < 0 ? '-' . $formatted : $formatted;
}
/* =========================
Excel-Export: Konfiguration
========================= */
$EXCEL_CUSTOM_HEADER = [
['EpiWebview Versicherungsgeräte'],
['Exportdatum: ' . date('d.m.Y H:i') . ' | Mandant: ' . (defined('Epirent_Mandant') ? Epirent_Mandant : '')]
];
$EXCEL_COLS = ['#','Name','Warengruppe(n)','Versicherung netto','Seriennummer','Anschaffung','Kaufpreis','Besitzer'];
/* =========================
1) Daten einmalig holen & Zeilen bauen
========================= */
$result = $Epi->requestEpiApi('/v1/product/all?ia=true&ir=true&cl=' . Epirent_Mandant);
$productList = json_decode($result)->payload;
$filteredProductArray = [];
foreach ($productList as $product) {
if ($product->is_stock_control && $product->is_stock_single_entry && !$product->is_virtual) {
if (!empty($product->stock_data[0]->stock_rent) && $product->stock_data[0]->stock_rent > 0) {
$filteredProductArray[] = $product;
}
}
}
$rows = [];
foreach ($filteredProductArray as $filteredProduct) {
$detailRes = $Epi->requestEpiApi('/v1/product/' . $filteredProduct->primary_key . '?cl=' . Epirent_Mandant);
$insuredProduct = json_decode($detailRes)->payload[0] ?? null;
if (!$insuredProduct || empty($insuredProduct->pricing) || !isset($insuredProduct->pricing->insurance_net)) {
continue;
}
if ((float)$insuredProduct->pricing->insurance_net == 0.0) {
continue;
}
// Materials
$MaterialList = "";
if (!empty($insuredProduct->materials)) {
foreach ($insuredProduct->materials as $material) {
if ($MaterialList !== "") $MaterialList .= ", ";
if (!empty($material->is_free_material) && $material->is_free_material) {
$MaterialList .= $material->name;
} else {
$materialResult = $Epi->requestEpiApi('/v1/product/' . $material->mat_product_pk . '?cl=' . Epirent_Mandant);
$materialProductObject = json_decode($materialResult)->payload[0] ?? null;
$MaterialList .= $materialProductObject ? $materialProductObject->name : '';
}
}
}
// Warengruppen
$groupOfGoodsList = "";
if (!empty($insuredProduct->group_of_goods)) {
foreach ($insuredProduct->group_of_goods as $groupOfGoods) {
if ($groupOfGoodsList !== "") $groupOfGoodsList .= ", ";
$groupOfGoodsList .= str_replace(";", "->", $groupOfGoods->sequence_name);
}
}
// Geräte (Stocks) nur einmal je Produkt
$stockRes = $Epi->requestEpiApi('/v1/stock/filter?ppk=' . $insuredProduct->primary_key . '&cl=' . Epirent_Mandant);
$deviceStockObjectArray = json_decode($stockRes)->payload ?? [];
foreach ($deviceStockObjectArray as $device) {
$rows[] = [
'pk' => $insuredProduct->product_no,
'name' => $insuredProduct->name,
'materials' => $MaterialList,
'groups' => $groupOfGoodsList,
'insurance_net' => (float)$insuredProduct->pricing->insurance_net,
'serial_no' => (string)($device->serial_no ?? ''),
'date_purchase' => (string)($device->date_purchase ?? ''),
'purchase_price'=> (float)($device->purchase_price ?? 0.0),
'owner' => (string)($device->owner ?? ''),
];
}
}
/* =========================
2) XLSX sofort erzeugen (ohne setCellValueByColumnAndRow)
========================= */
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
use PhpOffice\PhpSpreadsheet\Style\Fill;
$exportDir = __DIR__ . '/exports';
if (!is_dir($exportDir)) {
@mkdir($exportDir, 0775, true);
}
$timestamp = date('Ymd_His');
$excelFileName = "Versicherungsgeraete_{$timestamp}.xlsx";
$excelFilePath = $exportDir . '/' . $excelFileName;
$excelDownloadUrl = 'exports/' . $excelFileName;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Versicherungsgeräte');
// kleine Helfer zum Adressieren per Spaltenbuchstaben
$cell = function(int $colIndex, int $rowIndex): string {
return Coordinate::stringFromColumnIndex($colIndex) . $rowIndex;
};
$colCount = count($EXCEL_COLS);
$lastCol = Coordinate::stringFromColumnIndex($colCount);
// Spaltenindex-Konstanten für Summen
$INSURANCE_COL = 4; // "Versicherung netto"
$PURCHASE_COL = 7; // "Kaufpreis"
// Custom Header
$rowIdx = 1;
foreach ($EXCEL_CUSTOM_HEADER as $hdrLine) {
$text = implode(' ', $hdrLine);
$sheet->setCellValue($cell(1, $rowIdx), $text);
$sheet->mergeCells($cell(1, $rowIdx) . ':' . $cell($colCount, $rowIdx));
$sheet->getStyle($cell(1, $rowIdx) . ':' . $cell($colCount, $rowIdx))
->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle($cell(1, $rowIdx) . ':' . $cell($colCount, $rowIdx))
->getFont()->setBold(true)->setSize($rowIdx === 1 ? 14 : 11);
$rowIdx++;
}
$rowIdx++;
// Tabellen-Header
foreach ($EXCEL_COLS as $i => $label) {
$addr = $cell($i+1, $rowIdx);
$sheet->setCellValue($addr, $label);
$sheet->getStyle($addr)->getFont()->setBold(true);
$sheet->getStyle($addr)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($i+1))->setAutoSize(true);
}
$headerRow = $rowIdx;
$rowIdx++;
// Daten
$dataStartRow = $rowIdx;
foreach ($rows as $r) {
$c = 1;
// #
$sheet->setCellValue($cell($c++, $rowIdx), $r['pk']);
// Name (+ Materials)
$nameOut = $r['name'] . ($r['materials'] ? " | {$r['materials']}" : "");
$sheet->setCellValue($cell($c++, $rowIdx), $nameOut);
// Warengruppen
$sheet->setCellValue($cell($c++, $rowIdx), $r['groups']);
// Versicherung netto (numerisch, EU-Format)
$sheet->setCellValueExplicit($cell($c, $rowIdx), (float)$r['insurance_net'], DataType::TYPE_NUMERIC);
$sheet->getStyle($cell($c, $rowIdx))->getNumberFormat()->setFormatCode('#,##0.00 [$€-de-DE]');
$c++;
// Seriennummer
$sheet->setCellValue($cell($c++, $rowIdx), $r['serial_no']);
// Anschaffung (Datum)
$excelDate = '';
if (!empty($r['date_purchase'])) {
$ts = strtotime($r['date_purchase']);
if ($ts !== false) {
$excelDate = ExcelDate::PHPToExcel($ts);
}
}
if ($excelDate !== '') {
$sheet->setCellValue($cell($c, $rowIdx), $excelDate);
$sheet->getStyle($cell($c, $rowIdx))->getNumberFormat()->setFormatCode('dd.mm.yyyy');
} else {
$sheet->setCellValue($cell($c, $rowIdx), '');
}
$c++;
// Kaufpreis (numerisch, EU-Format)
$sheet->setCellValueExplicit($cell($c, $rowIdx), abs((float)$r['purchase_price']), DataType::TYPE_NUMERIC);
$sheet->getStyle($cell($c, $rowIdx))->getNumberFormat()->setFormatCode('#,##0.00 [$€-de-DE]');
$c++;
// Besitzer
$sheet->setCellValue($cell($c++, $rowIdx), $r['owner']);
$rowIdx++;
}
$dataEndRow = $rowIdx - 1;
/* === Summenzeile mit grauer Hinterlegung === */
$totalRow = $rowIdx + 1;
// Label in Spalte A
$sheet->setCellValue($cell(1, $totalRow), 'Gesamtsummen:');
$sheet->getStyle($cell(1, $totalRow))->getFont()->setBold(true);
// SUM-Formel Versicherung netto (Spalte 4)
$insColLetter = Coordinate::stringFromColumnIndex($INSURANCE_COL);
$sheet->setCellValue(
$cell($INSURANCE_COL, $totalRow),
"=SUM({$insColLetter}{$dataStartRow}:{$insColLetter}{$dataEndRow})"
);
$sheet->getStyle($cell($INSURANCE_COL, $totalRow))
->getNumberFormat()->setFormatCode('#,##0.00 [$€-de-DE]');
$sheet->getStyle($cell($INSURANCE_COL, $totalRow))->getFont()->setBold(true);
// SUM-Formel Kaufpreis (Spalte 7)
$purColLetter = Coordinate::stringFromColumnIndex($PURCHASE_COL);
$sheet->setCellValue(
$cell($PURCHASE_COL, $totalRow),
"=SUM({$purColLetter}{$dataStartRow}:{$purColLetter}{$dataEndRow})"
);
$sheet->getStyle($cell($PURCHASE_COL, $totalRow))
->getNumberFormat()->setFormatCode('#,##0.00 [$€-de-DE]');
$sheet->getStyle($cell($PURCHASE_COL, $totalRow))->getFont()->setBold(true);
// graue Hinterlegung über die ganze Summenzeile
$sheet->getStyle($cell(1, $totalRow) . ':' . $cell($colCount, $totalRow))
->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB('FFEFEFEF');
// Filter + Freeze
$sheet->setAutoFilter("A{$headerRow}:{$lastCol}{$headerRow}");
$sheet->freezePane("A" . ($headerRow + 1));
// Datei speichern
$writer = new Xlsx($spreadsheet);
$writer->save($excelFilePath);
/* =========================
3) HTML-Ausgabe (Tabelle aus $rows) + Download-Link
========================= */
// Summen für HTML
$totalInsurance = array_sum(array_column($rows, 'insurance_net'));
$totalPurchase = array_sum(array_map(fn($r) => abs($r['purchase_price']), $rows));
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Dashboard - EpiWebview</title>
<!-- Styles -->
<link href="css/styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
<!-- JS -->
<script src="js/jquery-3.5.1.min.js"></script>
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<style>
.opacity-50 { opacity: .5; }
.card .h2, .card .display-4 { font-weight: 700; }
.kpi-updated { font-size: .82rem; opacity: .85; }
</style>
<script>
$(function () {
$('#layoutSidenav_nav').load('../sources/getSidenav.php');
$('#footerholder').load('../sources/getFooter.php');
});
</script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.php">Epi Webview</a>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Versicherung</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item active">Versicherung</li>
</ol>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div><i class="fas fa-table mr-1"></i> Versicherungsgeräte</div>
<a class="btn btn-sm btn-success" href="<?php echo htmlspecialchars($excelDownloadUrl); ?>">
<i class="fas fa-file-excel"></i> Export nach Excel
</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Warengruppe(n)</th>
<th scope="col">Versicherung netto</th>
<th scope="col">Seriennummer</th>
<th scope="col">Anschaffung</th>
<th scope="col">Kaufpreis</th>
<th scope="col">Besitzer</th>
</tr>
<?php foreach ($rows as $r): ?>
<tr>
<th><?php echo htmlspecialchars((string) $r['pk']); ?></th>
<th>
<?php
echo htmlspecialchars($r['name']);
if (!empty($r['materials'])) {
echo "<br><small>" . htmlspecialchars($r['materials']) . "</small>";
}
?>
</th>
<th><?php echo htmlspecialchars($r['groups']); ?></th>
<th><?php echo formatEuro((float)$r['insurance_net']); ?></th>
<th><?php echo htmlspecialchars($r['serial_no']); ?></th>
<th>
<?php
$purchaseDateOut = '';
if (!empty($r['date_purchase'])) {
try { $purchaseDateOut = date_format(new \DateTime($r['date_purchase']), 'd.m.Y'); } catch (\Throwable $e) {}
}
echo htmlspecialchars($purchaseDateOut);
?>
</th>
<th><?php echo formatEuro(abs((float)$r['purchase_price'])); ?></th>
<th><?php echo htmlspecialchars($r['owner']); ?></th>
</tr>
<?php endforeach; ?>
<!-- Summenzeile in HTML -->
<tr style="font-weight:bold; background:#f8f9fa;">
<td colspan="3" class="text-right">Gesamtsummen:</td>
<td><?php echo formatEuro($totalInsurance); ?></td>
<td></td>
<td></td>
<td><?php echo formatEuro($totalPurchase); ?></td>
<td></td>
</tr>
</table>
</div>
</div>
</div>
</div>
</main>
<div id="footerholder"></div>
</div>
</div>
<!-- Bootstrap Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
</body>
</html>

152
dist/labelprint.php vendored Normal file
View File

@@ -0,0 +1,152 @@
<?php
require('../config.php');
require('../EpiApi.php');
require_once __DIR__ . '/../vendor/autoload.php';
date_default_timezone_set('Europe/Berlin');
$Epi = new Epirent();
$productList = json_decode($Epi->requestEpiApi('/v1/product/all?cl=' . Epirent_Mandant))->payload;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Labelprint - EpiWebview</title>
<!-- Styles -->
<link href="css/styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
<!-- JS -->
<script src="js/jquery-3.5.1.min.js"></script>
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.20/js/dataTables.bootstrap4.min.js"></script>
<style>
.opacity-50 {
opacity: .5;
}
.card .h2,
.card .display-4 {
font-weight: 700;
}
.kpi-updated {
font-size: .82rem;
opacity: .85;
}
</style>
<script>
$(function() {
$('#layoutSidenav_nav').load('../sources/getSidenav.php');
$('#footerholder').load('../sources/getFooter.php');
});
</script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.php">Epi Webview</a>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Labelprint</h1>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div><i class="fas fa-table mr-1"></i> Labelprint</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="dataTable" class="table table-bordered" width="100%" cellspacing="0">
<thead>
<tr>
<th>#</th>
<th>PN</th>
<th>Name</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php
foreach ($productList as $product) {
echo "<tr>";
echo "<td>" . htmlspecialchars($product->primary_key) . "</td>";
echo "<td>" . htmlspecialchars($product->product_no) . "</td>";
echo "<td>" . htmlspecialchars($product->name) . "</td>";
echo "
<td>
<a class='btn btn-sm btn-outline-primary'
target='_blank'
href='../sources/getProductLabel.php?id=" . urlencode($product->primary_key) . "'>
Export
</a>
<input type='number'
id='nrInput_" . htmlspecialchars($product->primary_key) . "'
min='1' max='9999'
style='width:70px; margin-left:6px; text-align:center;'
placeholder='#'>
<a class='btn btn-sm btn-outline-secondary'
target='_blank'
id='exportWithIdBtn_" . htmlspecialchars($product->primary_key) . "'
href='#'
onclick=\"
var nr = document.getElementById('nrInput_" . htmlspecialchars($product->primary_key) . "').value;
if(nr) {
this.href = '../sources/getProductLabel.php?id=" . urlencode($product->primary_key) . "&nr=' + encodeURIComponent(nr);
} else {
alert('Bitte eine Nummer zwischen 1 und 9999 eingeben.');
return false;
}
\">
Export mit ID
</a>
</td>";
echo "</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<div id="footerholder"></div>
</div>
</div>
<!-- Bootstrap Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$('#dataTable').DataTable({
"pageLength": 25,
"language": {
"url": "//cdn.datatables.net/plug-ins/1.10.20/i18n/German.json"
}
});
});
</script>
</body>
</html>

View File

@@ -1,39 +1,78 @@
<?php <?php
//EPIRENT CONNECTION EINSTELLUNGEN// // @section: Epirent Verbindung
define('Epirent_Server', 'xxx'); define('Epirent_Server', 'xxx');
define('Epirent_Connectionprotocol', 'http'); define('Epirent_Connectionprotocol', 'http');
define('Epirent_Port', '8080'); define('Epirent_Port', '8080');
define('Epirent_Token', 'xxx'); define('Epirent_Token', 'xxx');
define('Epirent_Mandant', '0'); //NORMALERWEISE 0 define('Epirent_Mandant', '0'); //NORMALERWEISE 0
// @hr
//CREWBRAIN CONNECTION EINSTELLUNGEN-NUR AUSFÜLLEN WENN GEWÜNSCHT// // @section: Crewbrain Verbindung
// @note: Nur ausfüllen wenn gewünscht
define('CrewBrain_Username', 'xxx'); define('CrewBrain_Username', 'xxx');
define('CrewBrain_password', 'xxx'); define('CrewBrain_password', 'xxx');
define('CrewBrain_Server', 'xxx.crewbrain.com'); define('CrewBrain_Server', 'xxx.crewbrain.com');
define('CrewBrain_Connectionprotocol', 'https'); define('CrewBrain_Connectionprotocol', 'https');
define('CrewBrain_TaskListID', 6); define('CrewBrain_TaskListID', 6);
//CREWBRAIN SPEZIFISCHE EINSTELLUNGEN (AUFGABENLISTE)//
define('Enable_QR_Code_CrewBrainAufgaben', true); define('Enable_QR_Code_CrewBrainAufgaben', true);
// @hr
//EPIRENT SPEZIFISCHE EINSTELLUNGEN (CHECK IN & CHECK OUT) // @section: Epirent-Spezifische Einstellungen
define('Enable_QR_Code_CheckOut', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckOut an define('Enable_QR_Code_CheckOut', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckOut an
define('Enable_QR_Code_CheckIn', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckIn an define('Enable_QR_Code_CheckIn', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckIn an
define('Vorbereitungs_Zeitvariable', 'Packen'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check Out Angezeigt werden soll define('Vorbereitungs_Zeitvariable', 'Packen'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check Out Angezeigt werden soll
define('Rueckpacken_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check In Angezeigt werden soll define('Nachbereitung_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts aus dem Auftrag, der die Nachbereitung angibt
define('Rückpacken_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts aus dem Auftrag, der das Rückpacken angibt.
define('UseDeliveredForCheckOutCompleted', true); //Nutzt statt dem erfolgreichen abschließen eines Packscheins durch ausbuchen das erfolgreiche Liefern (Geliefert != 00.00.00)
define('EnableScrollingCheckOut', true); //Aktiviert das automatische Scrollen der CheckOut Liste
define('EnableScrollingCheckIn', true); //Aktiviert das automatische Scrollen der CheckIn Liste
define('EnableScrollingAufgaben', true); //Aktiviert das automatische Scrollen der Aufgabenliste
// @note: -------------------- CheckOutRowMarkSource: Konfig Zusände --------------------
// @note: 1 = $packingjob->date_start (Dispo Start)
// @note: 2 = $VorbereitungsTimeDetail->date_start (Vorbereitung Start)
// @note: 3 = $PackingNoteDetail->date_packing (Packen Zeit)
// @note: 4 = $PackingNoteDetail->date_delivery (Delivery Zeit)
define('CheckOutRowMarkSource', 4);
define('UsePackingNoteDateForCheckout', true); // Nutzt statt den Zeitabscnitten aus dem Auftrag die Informationen aus dem Packschein für den Checkout. Wenn die UseDispo Variablen false sind, werden diese Variablen für das Rowmarking genutzt falls "true". // @note: -------------------- CheckInRowMarkSource: Konfig Zusände --------------------
define('UsePackingNoteDateForCheckin', true); // Nutzt statt den Zeitabscnitten aus dem Auftrag die Informationen aus dem Packschein für den Checkout. Wenn die UseDispo Variablen false sind, werden diese Variablen für das Rowmarking genutzt falls "true". // @note: 1 = $packingjob->date_end (Dispo Ende)
// @note: 2 = $NachbereitungssTimeDetail->date_start (Nachbereitung Start)
// @note: 3 = $RePackagingTimeDetail->date_start (Rückpacken Zeit AUS AUFTRAG)
// @note: 4 = $PackingNoteDetail->date_redelivery (ReDelivery Zeit)
define('CheckInRowMarkSource', 4);
define('CheckOut_UseDispoStartForRowMarking', false); //else: Use Same Variable as "Vorbereitung Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim CheckOut Verwendet werden soll
define('CheckIn_UseDispoEndForRowMarking', false); //else: Use Same Variable as "Rueckpacken Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim Check In Verwendet werden soll define('CheckIn_UseDispoEndForRowMarking', false); //else: Use Same Variable as "Rueckpacken Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim Check In Verwendet werden soll
define('HideCheckInTimeOnCheckout', true); //Versteckt die CheckIn Zeit im Checkout define('ShowCheckoutTimeOnCheckout', true); //Zeigt die Checkout Zeit im Checkout
define('HideCheckOutTimeOnCheckin', true); //Versteckt die CheckOut Zeit im CheckIn define('ShowVorbereitungTimeOnCheckout', true); //Zeigt die Vorbereitungs Zeitvariable im Checkout
define('ShowPackagingTimeOnCheckout', true); //Zeigt die Packenzeit im Checkout
define('ShowDeliveryTimeOnCheckout', true); //Zeigt die Lieferzeit im Checkout
define('ShowTimesOnCheckout', true); //Aktiviert das anzeigen der Uhrzeit im Checkout
// @hr
define('ShowCheckInTimeOnCheckin', true); //Zeigt die CheckIn Zeit im CheckIn
define('ShowNachbereitungTimeOnCheckin', true); //Zeigt die Nachbereitungs Zeitvariable im CheckIn
define('ShowRePackagingTimeOnCheckin', true); //Zeigt die Rückpackzeit im Checkin <br><b>Achtung: Zeit nur Im Auftrag festlegbar</b>
define('ShowReDeliveryTimeOnCheckin', true); //Zeigt die geplante Rücklieferung im Checkin
define('ShowTimesOnCheckin', true); //Aktiviert das anzeigen der Uhrzeit im CheckIn
// @section: Epirent-Spezifische Einstellungen - Shipping
define('ShowShippingIcons', true); //Zeigt Lieferung / Rücklieferungs Icons an
define('KurierContainsText', 'Kurier'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als KURIER erkannt wird.
define('SpeditionContainsText', 'Spedition'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als SPEDITION erkannt wird.
define('DHLContainsText', 'DHL'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als DHL erkannt wird.
define('LKWContainsText', 'LKW'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als eigener LKW erkannt wird.
define('TransporterContainsText', 'Sprinter'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als eigener Transporter erkannt wird.
define('PKWContainsText', 'PKW'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als eigener PKW erkannt wird.
define('UseShippingStatus', true); //Nutzt den Packscheinstatus als Shipping Status. Hierdurch können die Icons unter verschiedenen Vorraussetzungen Grün werden, wenn der Versand organisiert wurde
define('ShippingOutOrganizedStatus', 'Hinversand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der Hinversand Organisiert / Bestellt wurde
define('ShippingInOrganizedStatus', 'Rückversand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der Rückversand Organisiert / Bestellt wurde
define('ShippingOrganizedStatus', 'Versand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der komplette Versand Organisiert / Bestellt wurde
// @section: Epirent-Spezifische Einstellungen - Anzeigesortierung
define('CheckOut_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckOut angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckOut_UseDispoStartForRowMarking define('CheckOut_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckOut angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckOut_UseDispoStartForRowMarking
define('CheckIn_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckIn angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckIn_UseDispoEndForRowMarking define('CheckIn_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckIn angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckIn_UseDispoEndForRowMarking
define('SortCheckOut', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispostart define('SortCheckOut', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispostart
define('SortCheckIn', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispoende define('SortCheckIn', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispoende
// @section: Epirent-Spezifische Einstellungen - Labeldruck
define('Labelprint_Logopath', '/../src/assets/img/logo.png'); //Pfad zum Logo für den Etikettendruck
define('Labelprint_Addresstext', '<b>VT-Media</b><br/>Augsburger Straße 22<br/>87672 Roßhaupten<br/>info@vt-media.de<br/>vt-media.de<br/>08367/4619-990'); //Text der im Adressfeld der Etiketten angezeigt wird. max 6 Zeilen Empfohlen. Umbruch mit <br>. Fett mit <b>Text</b>
?> ?>

View File

@@ -3,22 +3,35 @@
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/> <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2"> <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
<group> <group>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckOutCards.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/index.html</file>
<file>file:/C:/xampp/htdocs/EpiWebview/.git/config</file> <file>file:/C:/xampp/htdocs/EpiWebview/.git/config</file>
<file>file:/C:/xampp/htdocs/EpiWebview/PackAufgabenMonitor.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getSidenav.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/index.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/dist/index.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getOrderDetails.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/sources/getOrderDetails.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/config.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/dist/css/styles.css</file>
<file>file:/C:/xampp/htdocs/EpiWebview/EpiApi.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getOrders.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/Aufgabenmonitor.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/Aufgabenmonitor.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/layout-sidenav-light.html</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/editconfig.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/composer.json</file> <file>file:/C:/xampp/htdocs/EpiWebview/composer.json</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckInTable.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckInTable.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/CrewbrainApi.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/CrewbrainApi.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/example.config.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/composer.lock</file> <file>file:/C:/xampp/htdocs/EpiWebview/composer.lock</file>
<file>file:/C:/xampp/htdocs/EpiWebview/info.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/info.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/Packmonitor.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/Packmonitor.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/index.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/index.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/.gitignore</file>
<file>file:/C:/xampp/htdocs/EpiWebview/PackAufgabenMonitor.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getSidenav.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/README.MD</file>
<file>file:/C:/xampp/htdocs/EpiWebview/config.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/EpiApi.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getOrders.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckInCards.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/orders.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/dist/insurance.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/LICENSE.MD</file>
<file>file:/C:/xampp/htdocs/EpiWebview/vendor/autoload.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getAufgabenTable.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/sources/getAufgabenTable.php</file>
<file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckOutTable.php</file> <file>file:/C:/xampp/htdocs/EpiWebview/sources/getCheckOutTable.php</file>
</group> </group>

View File

@@ -62,9 +62,13 @@ foreach ($data_output as $aufgabe) {
} }
echo "<td>"; echo "<td>";
$i=0;
foreach($AufgabeDetail->Responsibles as $bearbeiter){ foreach($AufgabeDetail->Responsibles as $bearbeiter){
echo $bearbeiter->Name; echo $bearbeiter->Name;
if($i>0){
echo ", ";
}
$i++;
} }
echo "</td>"; echo "</td>";
echo "<td>" . $aufgabe->Titel . "</td>"; echo "<td>" . $aufgabe->Titel . "</td>";

193
sources/getCheckInCards.php Normal file
View File

@@ -0,0 +1,193 @@
<?php
// ../sources/getCheckInCards.php
error_reporting(E_ALL);
require('../config.php');
require('../EpiApi.php');
$Epi = new Epirent();
use chillerlan\QRCode\{ QRCode, QROptions };
require('../vendor/autoload.php');
const APP_TZ = 'Europe/Berlin';
/** ---------- Helpers (wie in deiner Check-In-Datei) ---------- */
function dt(?string $s): ?DateTimeImmutable {
if (!$s) return null;
try { return new DateTimeImmutable($s, new DateTimeZone(APP_TZ)); }
catch (Throwable $e) { return null; }
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
function rowClassForDate(?DateTimeImmutable $markDate, ?DateTimeImmutable $today): string {
if (!$markDate || !$today) return '';
if ($markDate == $today) return 'text-dark bg-warning';
if ($markDate < $today) return 'bg-danger';
return '';
}
/**
* Row-Marking Quelle (Check-In) identisch zu deiner Tabelle:
* 1 = $packingjob->date_end
* 2 = $NachbereitungsTimeDetail->date_start
* 3 = $RePackagingTimeDetail->date_start
* 4 = $PackingNoteDetail->date_redelivery
* Fallback: $packingjob->date_end
*/
function resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
int $source
): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_end ?? null); break;
case 2: $candidate = dt($NachbereitungsTimeDetail->date_start ?? null); break;
case 3: $candidate = dt($RePackagingTimeDetail->date_start ?? null); break;
case 4: $candidate = dt($PackingNoteDetail->date_redelivery ?? null); break;
default: $candidate = null; break;
}
if (!$candidate) {
$candidate = dt($packingjob->date_end ?? null);
}
return $candidate ? dayStart($candidate) : null;
}
/** ---------- Daten holen (wie in deiner Check-In-Datei) ---------- */
// Offene Packingnotes für Check-IN-Seite: isco=False
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
$data_output = json_decode($result)->payload ?? [];
/** ---------- Zähler ---------- */
$today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
$limit = null;
if (CheckIn_FutureDays != -1) {
$limit = $today->modify('+' . (int)CheckIn_FutureDays . ' day');
}
$cntOverdue = 0;
$cntToday = 0;
$cntFuture = 0;
/** ---------- Zählen mit exakt deiner Logik ---------- */
foreach ($data_output as $packingjob) {
if ($packingjob->is_archived == true) continue;
// OrderDetails (für Nachbereitung / Rückpacken)
$orderRes = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = json_decode($orderRes)->payload[0] ?? null;
// PackingNote Details (für Redelivery)
$pnRes = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($pnRes)->payload[0] ?? null;
// Zeit-Slots aus dem Schedule
$NachbereitungsTimeDetail = null;
$RePackagingTimeDetail = null;
if ($orderdetail_output && !empty($orderdetail_output->order_schedule)) {
foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if ($scheduledetail->name == Nachbereitung_Zeitvariable) {
$NachbereitungsTimeDetail = $scheduledetail;
}
if ($scheduledetail->name == Rückpacken_Zeitvariable) {
$RePackagingTimeDetail = $scheduledetail;
}
}
}
// Row-Mark Source (mit Override-Flag)
$source = CheckIn_UseDispoEndForRowMarking ? 1 : (int)CheckInRowMarkSource;
$markDate = resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
$source
);
if (!$markDate) continue;
// Fenster berücksichtigen (gleich wie in deiner Tabelle)
if (CheckIn_FutureDays != -1 && $limit && $markDate > $limit) {
continue;
}
// Zählen
if ($markDate < $today) {
$cntOverdue++;
} elseif ($markDate == $today) {
$cntToday++;
} else {
$cntFuture++;
}
}
$ts = (new DateTimeImmutable('now', new DateTimeZone(APP_TZ)))->format('H:i:s');
?>
<h3 class="mb-3">
<span class="badge badge-secondary d-block w-100 py-2 text-center">CheckIn</span>
</h3>
<div class="row">
<div class="col-xl-4 col-md-6">
<div class="card bg-danger text-white mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">Überfällige CheckIns</div>
<div class="h2 mb-0"><?= (int)$cntOverdue ?></div>
</div>
<i class="fa-solid fa-triangle-exclamation fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-white-50">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small">bis <?= htmlspecialchars($today->format('d.m.Y')) ?></span>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6">
<div class="card bg-warning text-dark mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">Heutige CheckIns</div>
<div class="h2 mb-0"><?= (int)$cntToday ?></div>
</div>
<i class="fa-solid fa-boxes-packing fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-muted">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small"><?= htmlspecialchars($today->format('d.m.Y')) ?></span>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6">
<div class="card bg-success text-white mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">
Zukünftige CheckIns
<?php if (CheckIn_FutureDays != -1 && $limit): ?>
(≤ <?= htmlspecialchars($limit->format('d.m.Y')) ?>)
<?php endif; ?>
</div>
<div class="h2 mb-0"><?= (int)$cntFuture ?></div>
</div>
<i class="fa-solid fa-forward-step fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-white-50">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small">
<?php if (CheckIn_FutureDays == -1): ?>
unbegrenzt
<?php else: ?>
bis <?= htmlspecialchars($limit->format('d.m.Y')) ?>
<?php endif; ?>
</span>
</div>
</div>
</div>
</div>

View File

@@ -10,133 +10,240 @@ use chillerlan\QRCode\{
require('../vendor/autoload.php'); require('../vendor/autoload.php');
$options = new QROptions([ $options = new QROptions([
'imageBase64' => false, 'imageBase64' => false,
'qrCodeHeight' => 75, 'qrCodeHeight' => 75,
'qrCodeWidth' => 75, 'qrCodeWidth' => 75,
'version' => -1, 'version' => -1,
'quietzoneSize' => 1 'quietzoneSize' => 1
]); ]);
$Epi = new Epirent(); $Epi = new Epirent();
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant); $result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
$data_output = json_decode($result)->payload; $data_output = json_decode($result)->payload;
/** ---------- Helpers & Marking (analog Checkout) ---------- */
const APP_TZ = 'Europe/Berlin';
function dt(?string $s): ?DateTimeImmutable {
if (!$s) return null;
try {
return new DateTimeImmutable($s, new DateTimeZone(APP_TZ));
} catch (Throwable $e) {
return null;
}
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
function rowClassForDate(?DateTimeImmutable $markDate, ?DateTimeImmutable $today): string {
if (!$markDate || !$today) return '';
if ($markDate == $today) return 'text-dark bg-warning';
if ($markDate < $today) return 'bg-danger';
return '';
}
/**
* Row-Marking Quelle (Check-In):
* 1 = $packingjob->date_end
* 2 = $NachbereitungsTimeDetail->date_start
* 3 = $RePackagingTimeDetail->date_start
* 4 = $PackingNoteDetail->date_redelivery
* Fallback: $packingjob->date_end
*/
function resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
int $source
): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_end ?? null); break;
case 2: $candidate = dt($NachbereitungsTimeDetail->date_start ?? null); break;
case 3: $candidate = dt($RePackagingTimeDetail->date_start ?? null); break;
case 4: $candidate = dt($PackingNoteDetail->date_redelivery ?? null); break;
default: $candidate = null; break;
}
if (!$candidate) {
$candidate = dt($packingjob->date_end ?? null);
}
return $candidate ? dayStart($candidate) : null;
}
function echoMarkedTimeLine(?string $dateStr, ?int $timeSeconds, DateTimeImmutable $today, $ReturnTime): void {
if (!$dateStr) return;
$d = dt($dateStr);
if (!$d) return;
$mark = dayStart($d);
$cls = rowClassForDate($mark, $today);
echo $cls ? "<span class=\"{$cls}\">" : "";
echo date_format(new \DateTime($dateStr), 'd.m.Y');
if ($ReturnTime) {
echo " " . getTimeFromSeconds((string)$timeSeconds);
}
echo $cls ? "</span>" : "";
}
/** ---------- Sortierung nach Ende ---------- */
if (SortCheckIn == 2) { if (SortCheckIn == 2) {
// Prüfen, ob $data_output ein Array ist
if (is_array($data_output)) { if (is_array($data_output)) {
usort($data_output, function ($a, $b) { usort($data_output, function ($a, $b) {
// Konvertiere time_start von Millisekunden in Sekunden $timeEndA = $a->time_end / 1000;
$timeStartA = $a->time_end / 1000; // Zeit in Sekunden $timeEndB = $b->time_end / 1000;
$timeStartB = $b->time_end / 1000; $datetimeA = strtotime($a->date_end) + $timeEndA;
$datetimeB = strtotime($b->date_end) + $timeEndB;
// Kombiniere date_start mit time_start
$datetimeA = strtotime($a->date_end) + $timeStartA;
$datetimeB = strtotime($b->date_end) + $timeStartB;
// Vergleich für die Sortierung
return $datetimeA <=> $datetimeB; return $datetimeA <=> $datetimeB;
}); });
// Sortierte Daten ausgeben oder weiterverarbeiten
// print_r($data_output);
} else { } else {
echo "Daten konnten nicht verarbeitet werden."; echo "Daten konnten nicht verarbeitet werden.";
} }
} }
/** ---------- Tabelle ---------- */
foreach ($data_output as $packingjob) { foreach ($data_output as $packingjob) {
if ($packingjob->is_archived != true) { if ($packingjob->is_archived != true) {
//get OrderDetails // OrderDetails
$result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant); $result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = json_decode($result)->payload[0]; $orderdetail_output = json_decode($result)->payload[0];
$NachbereitungsTimeDetail; // PackingNote Details
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
// Zeit-Slots aus dem Schedule
$NachbereitungsTimeDetail = null;
$RePackagingTimeDetail = null;
foreach ($orderdetail_output->order_schedule as $scheduledetail) { foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if ($scheduledetail->name == Nachbereitung_Zeitvariable) {
if ($scheduledetail->name == Rueckpacken_Zeitvariable) {
$NachbereitungsTimeDetail = $scheduledetail; $NachbereitungsTimeDetail = $scheduledetail;
} }
} if ($scheduledetail->name == Rückpacken_Zeitvariable) {
$RePackagingTimeDetail = $scheduledetail;
//End Of get Order Details
if (CheckIn_UseDispoEndForRowMarking || ($NachbereitungsTimeDetail->date_start == null)) {
$date = new DateTime($packingjob->date_end);
} else {
$date = new DateTime($NachbereitungsTimeDetail->date_start);
}
$date->setTime(0, 0, 0);
$today = new DateTime();
$today->setTime(0, 0, 0);
$todayFilter = new DateTime();
$todayFilter->setTime(0, 0, 0);
if (CheckIn_FutureDays == -1 || $date <= ($todayFilter->modify('+' . CheckIn_FutureDays . ' day'))) {
//prüfe, ob entweder unbegrenzte (-1) Anzeige Aktiv ist, oder das Datum kleiner oder Gleich heute + Zukunftsspanne ist
if ($date == $today) {
echo "<tr class='text-dark bg-warning'>";
} else if ($date < $today) {
echo "<tr class=' bg-danger'>";
} else {
echo "<tr>";
} }
}
// --- Row-Marking bestimmen (konfigurierbar, analog Checkout) ---
$today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
$limit = null;
if (CheckIn_FutureDays != -1) {
$limit = $today->modify('+' . (int)CheckIn_FutureDays . ' day');
}
// Override-Flag wie bei Checkout: Dispo-Ende erzwingen
$source = CheckIn_UseDispoEndForRowMarking ? 1 : (int)CheckInRowMarkSource;
$markDate = resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
$source
);
if (CheckIn_FutureDays == -1 || ($markDate && (!$limit || $markDate <= $limit))) {
$trClass = rowClassForDate($markDate, $today);
echo $trClass ? "<tr class='{$trClass}'>" : "<tr>";
// QR / Kopfspalten
if (Enable_QR_Code_CheckIn) { if (Enable_QR_Code_CheckIn) {
echo "<td>" . '<div style="width: 5vb;">' . (new QRCode($options))->render($packingjob->packingnote_no) . "</div></td>"; echo "<td><div style=\"width: 5vb;\">" . (new QRCode($options))->render($packingjob->packingnote_no) . "</div></td>";
} else { } else {
echo "<td>" . $packingjob->packingnote_no . "</td>"; echo "<td>" . $packingjob->packingnote_no . "</td>";
} }
echo "<td>" . $packingjob->contact->name . "</td>"; echo "<td>" . $packingjob->contact->name . "</td>";
echo "<td>" . $packingjob->event . "</td>"; echo "<td>" . $packingjob->event . "</td>";
if(!HideCheckOutTimeOnCheckin){
if ($NachbereitungsTimeDetail->date_start != null) { // Zeitspalte öffnen, wenn mindestens eine Anzeige aktiv ist
echo "<td><small>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</small><br><i>" . date_format(new \DateTime($NachbereitungsTimeDetail->date_start), 'd.m.Y') . " " . getTimeFromSeconds($NachbereitungsTimeDetail->time_start) . "</i></td>"; if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin) {
} else { echo "<td>";
echo "<td>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</td>";
} }
// Dispo-Ende (Check-In Termin) — ENDE korrekt verwendet
if (ShowCheckInTimeOnCheckin && $packingjob->date_end != null) {
echoMarkedTimeLine($packingjob->date_end, (int)$packingjob->time_end, $today, ShowTimesOnCheckin);
} }
if ($NachbereitungsTimeDetail->date_end != null) {
echo "<td><small>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</small><br><i>" . date_format(new \DateTime($NachbereitungsTimeDetail->date_end), 'd.m.Y') . " " . getTimeFromSeconds($NachbereitungsTimeDetail->time_end) . "</i></td>"; // Nachbereitung (START) — wie von dir bereits genutzt
} else { if (ShowNachbereitungTimeOnCheckin && $NachbereitungsTimeDetail && $NachbereitungsTimeDetail->date_start != null) {
echo "<td>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</td>"; if (ShowCheckInTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($NachbereitungsTimeDetail->date_start, (int)$NachbereitungsTimeDetail->time_start, $today, ShowTimesOnCheckin);
} }
// Rückpacken (START)
if (ShowRePackagingTimeOnCheckin && $RePackagingTimeDetail && $RePackagingTimeDetail->date_start != null) {
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($RePackagingTimeDetail->date_start, (int)$RePackagingTimeDetail->time_start, $today, ShowTimesOnCheckin);
}
// Rücklieferung (REDELIVERY) — delivery ↔ redelivery gespiegelt
if (ShowReDeliveryTimeOnCheckin && $PackingNoteDetail && $PackingNoteDetail->date_redelivery != null) {
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($PackingNoteDetail->date_redelivery, (int)$PackingNoteDetail->time_redelivery, $today, ShowTimesOnCheckin);
}
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin) {
echo "</td>";
}
// Fortschritt
echo "<td>"; echo "<td>";
if ($packingjob->is_all_in == 0) {
if ($packingjob->is_all_in ==0) {
echo "<span class='badge badge-success'>"; echo "<span class='badge badge-success'>";
} else { } else {
echo '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: ' .
echo '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: '.(( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) / ($packingjob->pieces_sum_total - $packingjob ->is_all_out)) *100 .'%" aria-valuenow="' .( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . '" aria-valuemin="0" aria-valuemax="' . ($packingjob->pieces_sum_total - $packingjob ->is_all_out). '"></div></div>'; (( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) / ($packingjob->pieces_sum_total - $packingjob->is_all_out)) * 100 .
'%" aria-valuenow="' . ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) .
'" aria-valuemin="0" aria-valuemax="' . ($packingjob->pieces_sum_total - $packingjob->is_all_out) . '"></div></div>';
echo "<span class='badge badge-info'>"; echo "<span class='badge badge-info'>";
} }
echo ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . "/" . ($packingjob->pieces_sum_total - $packingjob->is_all_out) . " (" . $packingjob->pieces_sum_total . ")";
echo "</span></td>";
echo ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . "/" . ($packingjob->pieces_sum_total - $packingjob ->is_all_out) . " (" . $packingjob->pieces_sum_total.")"; // Shipping-Icons (inbound)
echo "</span><td>"; if (ShowShippingIcons) {
if (UseShippingStatus) {
if (($PackingNoteDetail->status == ShippingInOrganizedStatus) || ($PackingNoteDetail->status == ShippingOrganizedStatus)) {
echo "<td style='color:#66FF00; text-align:center;'>";
} else {
echo "<td style='text-align:center;'>";
}
if ($PackingNoteDetail->is_self_redeliver) {
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else {
if (preg_match('/' . KurierContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
}
if (preg_match('/' . SpeditionContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . DHLContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if (preg_match('/' . LKWContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . TransporterContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if (preg_match('/' . PKWContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
}
}
echo "</td>";
}
echo "</tr>";
}
echo "</tr>"; echo "</tr>";
} }
@@ -144,14 +251,9 @@ foreach ($data_output as $packingjob) {
} }
function getTimeFromSeconds(string $timestring) { function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600); $hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60); $mins = floor($timestring / 60 % 60);
$secs = floor($timestring % 60); $secs = floor($timestring % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins); $timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat; return $timeFormat;
} }
?>

View File

@@ -0,0 +1,189 @@
<?php
// sources/getCheckoutTable.php zählt statt Tabellenausgabe drei KPIs als Karten
error_reporting(E_ALL);
require('../config.php');
require('../EpiApi.php');
$Epi = new Epirent();
const APP_TZ = 'Europe/Berlin';
/** ---- Helpers (aus deiner bisherigen Datei) ---- */
function dt(?string $s): ?DateTimeImmutable {
if (!$s) return null;
try { return new DateTimeImmutable($s, new DateTimeZone(APP_TZ)); }
catch (Throwable $e) { return null; }
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
/**
* Quelle für das Markierungsdatum (identisch zu deiner Liste):
* 1 = $packingjob->date_start
* 2 = $VorbereitungsTimeDetail->date_start
* 3 = $PackingNoteDetail->date_packing
* 4 = $PackingNoteDetail->date_delivery
* Fallback: $packingjob->date_start
*/
function resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, int $source): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_start ?? null); break;
case 2: $candidate = dt($VorbereitungsTimeDetail->date_start ?? null); break;
case 3: $candidate = dt($PackingNoteDetail->date_packing ?? null); break;
case 4: $candidate = dt($PackingNoteDetail->date_delivery ?? null); break;
default:$candidate = null; break;
}
if (!$candidate) $candidate = dt($packingjob->date_start ?? null);
return $candidate ? dayStart($candidate) : null;
}
/** ---- Daten laden wie bisher (Checkout-Quelle) ---- */
if (UseDeliveredForCheckOutCompleted) {
// isco=False -> Checkout-Ansicht offen
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
} else {
// isci=False -> Alternative Logik (wie in deiner Datei)
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=False&cl=' . Epirent_Mandant);
}
$data_output = json_decode($result)->payload ?? [];
/** ---- Zähler vorbereiten ---- */
$today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
$limit = null; // optionales Fenster wie in deiner Liste
if (CheckOut_FutureDays != -1) {
$limit = $today->modify('+' . (int)CheckOut_FutureDays . ' day');
}
$cntOverdue = 0;
$cntToday = 0;
$cntFuture = 0;
/** ---- Iteration identisch zur bisherigen Logik ---- */
foreach ($data_output as $packingjob) {
// Details nur holen, wenn nötig & wie gehabt
$PackingNoteDetail = null;
if ($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0] ?? null;
}
// Aufnahmebedingung exakt wie bei dir:
if (
$packingjob->is_archived != true
&& (
!UseDeliveredForCheckOutCompleted
|| ($PackingNoteDetail && ($PackingNoteDetail->date_delivered !== "0000-00-00" || (int)$PackingNoteDetail->time_delivered !== 0))
)
&& ($PackingNoteDetail && (int)$PackingNoteDetail->is_all_out !== 0)
) {
// OrderDetails (für Vorbereitungszeit)
$result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = (json_decode($result)->payload[0] ?? null);
// PackingNoteDetail nachladen, falls oben noch nicht geholt
if (!UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0] ?? null;
if (!$PackingNoteDetail) continue;
}
// Vorbereitungs-Zeitdetail ermitteln
$VorbereitungsTimeDetail = null;
if ($orderdetail_output && !empty($orderdetail_output->order_schedule)) {
foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if ($scheduledetail->name == Vorbereitungs_Zeitvariable) {
$VorbereitungsTimeDetail = $scheduledetail;
break;
}
}
}
// Markierungsdatum wie in der Tabelle
$markDate = resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, (int)CheckOutRowMarkSource);
if (!$markDate) continue;
// Optionales Zukunftsfenster berücksichtigen (wie bisher)
if (CheckOut_FutureDays != -1 && $limit && $markDate > $limit) {
// außerhalb des Fensters ignorieren
continue;
}
// Zählen
if ($markDate < $today) {
$cntOverdue++;
} elseif ($markDate == $today) {
$cntToday++;
} else { // $markDate > $today
$cntFuture++;
}
}
}
// Zeitstempel für die Karten
$ts = (new DateTimeImmutable('now', new DateTimeZone(APP_TZ)))->format('H:i:s');
/** ---- Ausgabe: 3 Bootstrap-Karten ---- */
?>
<h3 class="mb-3">
<span class="badge badge-secondary d-block w-100 py-2 text-center">Checkout</span>
</h3>
<div class="row">
<div class="col-xl-4 col-md-6">
<div class="card bg-danger text-white mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">Überfällige CheckOuts</div>
<div class="h2 mb-0"><?= (int)$cntOverdue ?></div>
</div>
<i class="fa-solid fa-triangle-exclamation fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-white-50">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small">bis <?= htmlspecialchars($today->format('d.m.Y')) ?></span>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6">
<div class="card bg-warning text-dark mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">Heutige CheckOuts</div>
<div class="h2 mb-0"><?= (int)$cntToday ?></div>
</div>
<i class="fa-solid fa-dolly fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-muted">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small"><?= htmlspecialchars($today->format('d.m.Y')) ?></span>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6">
<div class="card bg-success text-white mb-4">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<div class="small">Zukünftige CheckOuts<?= (CheckOut_FutureDays != -1 ? ' (≤ '.$limit->format('d.m.Y').')' : '') ?></div>
<div class="h2 mb-0"><?= (int)$cntFuture ?></div>
</div>
<i class="fa-solid fa-forward-step fa-2x" style="opacity:.6"></i>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="small text-white-50">Stand <?= htmlspecialchars($ts) ?></span>
<span class="small">
<?php if (CheckOut_FutureDays == -1): ?>
unbegrenzt
<?php else: ?>
bis <?= htmlspecialchars($limit->format('d.m.Y')) ?>
<?php endif; ?>
</span>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
<?php <?php
error_reporting(E_ALL); error_reporting(E_ALL);
require('../config.php'); require('../config.php');
require('../EpiApi.php'); require('../EpiApi.php');
@@ -19,20 +20,65 @@ $options = new QROptions([
'quietzoneSize' => 1 'quietzoneSize' => 1
]); ]);
$Epi = new Epirent(); $Epi = new Epirent();
const APP_TZ = 'Europe/Berlin';
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=False&cl=' . Epirent_Mandant); /** Hilfsfunktionen für die Row-Marking-Logik (ohne weitere Änderungen am Rest) */
function dt(?string $s): ?DateTimeImmutable {
if (!$s)
return null;
try {
return new DateTimeImmutable($s, new DateTimeZone(APP_TZ));
} catch (Throwable $e) {
return null;
}
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
function resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, int $source): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_start ?? null);
break;
case 2: $candidate = dt($VorbereitungsTimeDetail->date_start ?? null);
break;
case 3: $candidate = dt($PackingNoteDetail->date_packing ?? null);
break;
case 4: $candidate = dt($PackingNoteDetail->date_delivery ?? null);
break;
default: $candidate = null;
break;
}
if (!$candidate) {
$candidate = dt($packingjob->date_start ?? null);
}
return $candidate ? dayStart($candidate) : null;
}
function rowClassForDate(?DateTimeImmutable $markDate, ?DateTimeImmutable $today): string {
if (!$markDate || !$today)
return '';
if ($markDate == $today)
return 'text-dark bg-warning';
if ($markDate < $today)
return 'bg-danger';
return '';
}
if (UseDeliveredForCheckOutCompleted) {
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=true&cl=' . Epirent_Mandant);
} else {
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=False&cl=' . Epirent_Mandant);
}
$data_output = json_decode($result)->payload; $data_output = json_decode($result)->payload;
if (SortCheckOut == 2) { if (SortCheckOut == 2) {
// Prüfen, ob $data_output ein Array ist // Prüfen, ob $data_output ein Array ist
if (is_array($data_output)) { if (is_array($data_output)) {
usort($data_output, function ($a, $b) { usort($data_output, function ($a, $b) {
@@ -57,45 +103,56 @@ if (SortCheckOut == 2) {
foreach ($data_output as $packingjob) { foreach ($data_output as $packingjob) {
$PackingNoteDetail = null;
if ($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
}
if ($packingjob->is_archived != true) { if (
($packingjob->is_archived ?? false) != true
&& (
((int)($PackingNoteDetail->is_all_out ?? 0)) !== 0
|| (($PackingNoteDetail->date_delivered ?? "0000-00-00") === "0000-00-00"
|| (int)($PackingNoteDetail->time_delivered ?? 0) === 0)
)
) {
//get OrderDetails //get OrderDetails
$result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant); $result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = json_decode($result)->payload[0]; $orderdetail_output = json_decode($result)->payload[0];
$VorbereitungsTimeDetail; // get PackingNote Details, aber nur wenn nicht schon vor der schleife geholt.
if (!UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
}
$VorbereitungsTimeDetail = null;
foreach ($orderdetail_output->order_schedule as $scheduledetail) { foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if ($scheduledetail->name == Vorbereitungs_Zeitvariable) { if ($scheduledetail->name == Vorbereitungs_Zeitvariable) {
$VorbereitungsTimeDetail = $scheduledetail; $VorbereitungsTimeDetail = $scheduledetail;
} }
} }
//End Of get Order Details
if (CheckOut_UseDispoStartForRowMarking || ($VorbereitungsTimeDetail->date_start == null)) { // --- Row-Marking Datum bestimmen (konfigurierbar) ---
$date = new DateTime($packingjob->date_start); $today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
} else { $todayFilter = $today; // für die Window-Berechnung
$date = new DateTime($VorbereitungsTimeDetail->date_start); $limit = null;
if (CheckOut_FutureDays != -1) {
$limit = $todayFilter->modify('+' . (int) CheckOut_FutureDays . ' day');
} }
$date->setTime(0, 0, 0); $markDate = resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, (int) CheckOutRowMarkSource);
$today = new DateTime();
$today->setTime(0, 0, 0);
$todayFilter = new DateTime(); if (CheckOut_FutureDays == -1 || ($markDate && $markDate <= $limit)) {
$todayFilter->setTime(0, 0, 0); $trClass = rowClassForDate($markDate, $today);
if (CheckOut_FutureDays == -1 || $date <= ($todayFilter->modify('+' . CheckOut_FutureDays . ' day'))) {
//prüfe, ob entweder unbegrenzte (-1) Anzeige Aktiv ist, oder das Datum kleiner oder Gleich heute + Zukunftsspanne ist
if ($date == $today) {
echo "<tr class='text-dark bg-warning'>"; if ($trClass) {
} else if ($date < $today) { echo "<tr class='{$trClass}'>";
echo "<tr class=' bg-danger'>";
} else { } else {
echo "<tr>"; echo "<tr>";
} }
@@ -107,20 +164,55 @@ foreach ($data_output as $packingjob) {
} }
echo "<td>" . $packingjob->contact->name . "</td>"; echo "<td>" . $packingjob->contact->name . "</td>";
echo "<td>" . $packingjob->event . "</td>"; echo "<td>" . $packingjob->event . "</td>";
if ($VorbereitungsTimeDetail->date_start != null) {
echo "<td><small>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</small><br><i>" . date_format(new \DateTime($VorbereitungsTimeDetail->date_start), 'd.m.Y') . " " . getTimeFromSeconds($VorbereitungsTimeDetail->time_start) . "</i></td>";
} else {
echo "<td>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</td>";
}
if(!HideCheckInTimeOnCheckout){
if (($VorbereitungsTimeDetail->date_end != null)) {
echo "<td><small>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</small><br><i>" . date_format(new \DateTime($VorbereitungsTimeDetail->date_end), 'd.m.Y') . " " . getTimeFromSeconds($VorbereitungsTimeDetail->time_end) . "</i></td>";
} else {
echo "<td>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</td>";
}
}
echo "<td>";
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout) { echo "<td>";}
if (ShowCheckoutTimeOnCheckout && $packingjob->date_start != null) {
echoMarkedTimeLine($packingjob->date_start, (int) $packingjob->time_start, $today, ShowTimesOnCheckout);
}
if (ShowVorbereitungTimeOnCheckout && $VorbereitungsTimeDetail && $VorbereitungsTimeDetail->date_start != null) {
if (ShowCheckoutTimeOnCheckout) {
echo "<br>";
}
if($packingjob->is_all_out != 0){
echoMarkedTimeLine($VorbereitungsTimeDetail->date_start, (int) $VorbereitungsTimeDetail->time_start, $today, ShowTimesOnCheckout);
}else{
echo "<span class='bg-success text-light'>";
echo date_format(new \DateTime($VorbereitungsTimeDetail->date_start), 'd.m.Y');
if($VorbereitungsTimeDetail->time_start){echo " ".getTimeFromSeconds((string) $VorbereitungsTimeDetail->time_start);}
echo "</span>";
}
}
if (ShowPackagingTimeOnCheckout && $PackingNoteDetail && $PackingNoteDetail->date_packing != null) {
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout) {
echo "<br>";
}
if($packingjob->is_all_out != 0){
echoMarkedTimeLine($PackingNoteDetail->date_packing, (int) $PackingNoteDetail->time_packing, $today, ShowTimesOnCheckout);
}else{
echo "<span class='bg-success text-light'>";
echo date_format(new \DateTime($PackingNoteDetail->date_packing), 'd.m.Y');
if($PackingNoteDetail->time_packing){echo " ".getTimeFromSeconds((string) $PackingNoteDetail->time_packing);}
echo "</span>";
}
}
if (ShowDeliveryTimeOnCheckout && $PackingNoteDetail && $PackingNoteDetail->date_delivery != null) {
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout) {
echo "<br>";
}
echoMarkedTimeLine($PackingNoteDetail->date_delivery, (int) $PackingNoteDetail->time_delivery, $today, ShowTimesOnCheckout);
}
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout) {
echo "</td>";
}
echo "<td>";
if ($packingjob->is_all_out == 0) { if ($packingjob->is_all_out == 0) {
echo "<span class='badge badge-success'>"; echo "<span class='badge badge-success'>";
@@ -132,12 +224,66 @@ foreach ($data_output as $packingjob) {
} }
echo ($packingjob->pieces_sum_total - abs($packingjob->is_all_out)) . "/" . $packingjob->pieces_sum_total; echo ($packingjob->pieces_sum_total - abs($packingjob->is_all_out)) . "/" . $packingjob->pieces_sum_total;
echo "</span><td>"; echo "</span></td>";
if (ShowShippingIcons) {
if (UseShippingStatus) {
if (($PackingNoteDetail->status == ShippingOutOrganizedStatus) || ($PackingNoteDetail->status == ShippingOrganizedStatus)) {
echo "<td style='color:#66FF00; text-align:center;'>";
} else {
echo "<td style='text-align:center;'>";
}
if ($PackingNoteDetail->is_self_pickup) {
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else {
if (preg_match('/' . KurierContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
}
if (preg_match('/' . SpeditionContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . DHLContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if (preg_match('/' . LKWContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . TransporterContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if (preg_match('/' . PKWContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
}
}
echo "</td>";
}
echo "</tr>";
}
echo "</tr>"; echo "</tr>";
} }
} }
} }
function echoMarkedTimeLine(?string $dateStr, ?int $timeSeconds, DateTimeImmutable $today, $ReturnTime): void {
if (!$dateStr)
return;
$d = dt($dateStr);
if (!$d)
return;
$mark = dayStart($d);
$cls = rowClassForDate($mark, $today);
echo $cls ? "<span class=\"{$cls}\">" : "";
echo date_format(new \DateTime($dateStr), 'd.m.Y');
if($ReturnTime){echo " ".getTimeFromSeconds((string) $timeSeconds);}
echo $cls ? "</span>" : "";
}
function getTimeFromSeconds(string $timestring) { function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600); $hours = floor($timestring / 3600);

877
sources/getProductLabel.php Normal file
View File

@@ -0,0 +1,877 @@
<?php
error_reporting(E_ALL & ~E_DEPRECATED);
require('../config.php');
require('../EpiApi.php');
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../vendor/tecnickcom/tcpdf/tcpdf.php';
date_default_timezone_set('Europe/Berlin');
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$deviceNumber = (!empty($_GET['nr'])) ? '--' . trim($_GET['nr']) : '';
$Epi = new Epirent();
$product =json_decode($Epi->requestEpiApi('/v1/product/'.$id.'?cl=' . Epirent_Mandant))->payload[0];
$productImage =json_decode($Epi->requestEpiApi('/v1/product/image/'.$id.'?cl=' . Epirent_Mandant))->payload[0]->image_data;
function s(?string $v): string { return trim((string)$v); }
function fnum(?float $v, int $dec = 2): string {
if ($v === null) return '';
$n = number_format((float)$v, $dec, ',', '.');
return rtrim(rtrim($n, '0'), ',');
}
//Set Variables
$productName = $product->name;
$productNumber = $product->product_no;
$bruttoWeightSum = $product->tech_data->weight_gro;
$bundle = buildBundleForProduct($Epi, $product);
/** Test-Daten
*/
$dimsBHT =$product->tech_data->width_gro." x ".$product->tech_data->depth_gro." x ".$product->tech_data->height_gro;
$volume = ((float)$product->tech_data->height_gro * (float)$product->tech_data->width_gro * (float)$product->tech_data->depth_gro)* 0.000001;
$storageLoc = $product->product_data->storage_location_nearby;
/** Layout */
$PAGE_SIZE = 'A4';
$MARGIN_LEFT = 8;
$MARGIN_TOP = 8;
$MARGIN_RIGHT = 8;
$MARGIN_BOTTOM = 10;
$CELL_PAD = 1.6;
$LINE_GRAY = '#efefef';
$LEVEL_MIN = 1; $LEVEL_MAX = 5;
$INDENT_PX_PER_LEVEL = 10;
$BASE_FONT_PX = 10;
$FONT_STEP_PX = 1.5;
/** TCPDF */
$pdf = new TCPDF('P', 'mm', $PAGE_SIZE, true, 'UTF-8', false);
// 1) Pfade sauber
$fontDir = realpath(__DIR__ . '/../src/assets/font') . '/';
$tcpdfFontsDir = defined('K_PATH_FONTS') ? K_PATH_FONTS : (dirname((new \ReflectionClass('TCPDF'))->getFileName()) . '/fonts/');
// 2) Rechte (einmalig sicherstellen)
// -> hast du schon gefixt
// 3) TTFs registrieren und Rückgabewerte merken
$fontReg = file_exists($fontDir.'Hurme3/HurmeGeometricSans3-Regular.ttf')
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme3/HurmeGeometricSans3-Regular.ttf', 'TrueTypeUnicode', '', 96)
: false;
$fontBold = file_exists($fontDir.'Hurme1/HurmeGeometricSans1-Bold.ttf')
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme1/HurmeGeometricSans1-Bold.ttf', 'TrueTypeUnicode', '', 96)
: false;
$fontItal = file_exists($fontDir.'Hurme3/HurmeGeometricSans3-Italic.ttf')
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme3/HurmeGeometricSans3-Italic.ttf', 'TrueTypeUnicode', '', 96)
: false;
$fontBI = file_exists($fontDir.'Hurme1/HurmeGeometricSans1-BoldItalic.ttf')
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme1/HurmeGeometricSans1-BoldItalic.ttf', 'TrueTypeUnicode', '', 96)
: false;
// 4) Fallback setzen
$baseFont = $fontReg ?: 'dejavusans';
// 5) Header/Footer und Defaults
$pdf->setHeaderFont([$baseFont, '', 10]);
$pdf->setFooterFont([$baseFont, '', 9]);
$pdf->SetDefaultMonospacedFont('courier');
// 6) *** AB JETZT KEIN dejavusans MEHR SETZEN! ***
$pdf->SetFont($baseFont, '', 10.5);
$pdf->SetCreator('VT-Media EpiWebview');
$pdf->SetAuthor('VT-Media');
$pdf->SetTitle('Kistenetikett '.$productName);
$pdf->SetSubject('Kistenetikett');
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->SetMargins($MARGIN_LEFT, $MARGIN_TOP, $MARGIN_RIGHT);
$pdf->SetAutoPageBreak(true, $MARGIN_BOTTOM);
$pdf->setImageScale(1.0);
$pdf->setFontSubsetting(true);
$pdf->setCellPadding($CELL_PAD);
$pdf->AddPage();
// --- LOGO in fester Box mit Rahmen, zentriert & verzerrungsfrei ---
$logoPath = __DIR__ . Labelprint_Logopath;
$startX = $pdf->GetX();
$startY = $pdf->GetY();
// feste Rahmen-Größe (in mm)
$frameW = 38.0; // Gesamtbreite des Rahmens
$frameH = 20; // Gesamthöhe des Rahmens
$frameOffsetX = 1.4; // Abstand vom linken Margin (wie vorher genutzt)
$frameOffsetY = 1.5; // Abstand von oben (wie vorher genutzt)
// Rahmen-Position
$frameX = $startX + $frameOffsetX;
$frameY = $startY + $frameOffsetY;
// Rahmen zeichnen
$pdf->SetLineWidth(0.3);
$pdf->Rect($frameX, $frameY, $frameW, $frameH, 'D');
// Logo innerhalb des Rahmens mit Innenabstand
$innerPad = 1.2; // mm Luft im Rahmen
$maxW = $frameW - 2 * $innerPad;
$maxH = $frameH - 2 * $innerPad;
if (is_file($logoPath)) {
// Seitenverhältnis ermitteln
$imgWpx = $imgHpx = 0;
if (@list($imgWpx, $imgHpx) = @getimagesize($logoPath)) {
$ratio = ($imgHpx > 0) ? ($imgWpx / $imgHpx) : 1.0;
// proportional in den Rahmen einpassen
if ($maxW / $maxH > $ratio) {
$drawH = $maxH;
$drawW = $maxH * $ratio;
} else {
$drawW = $maxW;
$drawH = $maxW / $ratio;
}
// zentrieren
$imgX = $frameX + ($frameW - $drawW) / 2;
$imgY = $frameY + ($frameH - $drawH) / 2;
// ausgeben
$pdf->Image($logoPath, $imgX, $imgY, $drawW, $drawH, '', '', '', false, 300);
}
}
$afterLogoX = $frameX + $frameW - 1.4; // gleicher Abstand wie zuvor, jetzt basierend auf Rahmenbreite
$afterLogoBottomY = $frameY + $frameH; // falls du unten bündig etwas brauchst
$pdf->SetXY($afterLogoX, $startY); // Cursor rechts neben dem Logo positionieren
$addrHtml = '
<div style="line-height:1.25; font-size:6px; margin:0; padding:0;">'.Labelprint_Addresstext.'</div>';
// Feste Box rechts neben dem Logo (direkt bündig)
$addrBoxW = 30.0; // feste Breite (mm)
$addrBoxH = 20.0; // feste Höhe (mm)
$addrPad = 0.5; // Innenabstand Inhalt → Rahmen (mm)
// bündig rechts neben dem Logo-Rahmen starten
$addrX = $frameX + $frameW; // kein zusätzlicher Abstand
$addrY = $startY+1.5; // gleiche Oberkante wie Logo
// 1) festen Rahmen zeichnen
$pdf->SetLineWidth(0.3);
$pdf->Rect($addrX, $addrY, $addrBoxW, $addrBoxH, 'D');
// 2) HTML-Inhalt in die Box schreiben, mit Clipping (Überhang wird abgeschnitten)
$pdf->StartTransform();
$pdf->Rect($addrX, $addrY, $addrBoxW, $addrBoxH, 'CNZ'); // Clip auf Box setzen
$pdf->writeHTMLCell(
$addrBoxW - 2*$addrPad, // feste Breite des Inhalts
$addrBoxH - 2*$addrPad, // feste Höhe des Inhalts
$addrX + $addrPad, // x mit Innenabstand
$addrY + $addrPad, // y mit Innenabstand
$addrHtml, // dein HTML
0, // kein Border (Rahmen haben wir schon)
0, // ln
false, // fill
true, // reseth
'L', // align
true // autopadding
);
$pdf->StopTransform();
// Cursor steht bereits auf: $pdf->SetXY($addrX + $addrBoxW, $addrY);
// 1) HTML OHNE border (sonst Doppelrahmen)
$productionHtml = '
<div style="line-height:1.3; font-size:8px; margin:0; padding:0;">
<b>Produktion</b><br/><br><br><br>
</div>';
// 2) Feste Box rechts neben der Adress-Box
$prodX = $addrX + $addrBoxW; // bündig rechts neben Adresse
$prodY = $addrY; // gleiche Oberkante
// Breite: restliche Seitenbreite bis zum rechten Rand
$prodW = $pdf->getPageWidth() - $MARGIN_RIGHT - $prodX+2;
// Höhe: fix (anpassen nach Wunsch)
$prodH = 20.0; // mm
$prodPad = 0; // Innenabstand
// 3) EIN sichtbarer Rahmen zeichnen
$pdf->SetLineWidth(0.3);
$pdf->Rect($prodX, $prodY, $prodW, $prodH, 'D');
// 4) Inhalt in die Box schreiben und clippen (Überhang abgeschnitten)
$pdf->StartTransform();
$pdf->Rect($prodX, $prodY, $prodW, $prodH, 'CNZ'); // Clip auf Box setzen
$pdf->writeHTMLCell(
$prodW - 2*$prodPad, // feste Breite des Inhalts
$prodH - 2*$prodPad, // feste Höhe des Inhalts
$prodX + $prodPad, // x mit Innenabstand
$prodY + $prodPad, // y mit Innenabstand
$productionHtml, // Inhalt (ohne border!)
0, // kein Border (Rahmen haben wir schon)
0, // ln
false, // fill
true, // reseth
'L', // align
true // autopadding
);
$pdf->StopTransform();
// 5) (Optional) Cursor direkt rechts neben der Box positionieren
$pdf->SetXY($prodX + $prodW, $prodY);
// === Name-Box: feste Größe, Text bricht, kein Doppelrahmen ===
// Breite: gesamte Restbreite bis zum rechten Rand (unter Adresse + Produktion)
$nameX = $frameX; // beginnt rechts neben dem Logo/Adressbereich
$nameW = $pdf->getPageWidth() - $MARGIN_RIGHT - $nameX-65.2;
// Höhe & Padding der Box (anpassen nach Wunsch)
$nameBoxH = 18.2; // mm - feste Höhe
$namePad = 1.5; // mm - Innenabstand
// Y: direkt unter die höhere der beiden Boxen (Adresse vs. Produktion)
$yAfterRow = max($addrY + $addrBoxH, $prodY + $prodH) + 0.0; // +2 mm Abstand
$nameY = $yAfterRow;
// Inhalt OHNE border (sonst Doppelrahmen)
$nameHtml = '
<div style="font-size:15px; font-weight:700; line-height:1.3; margin:0; padding:0;">' . htmlspecialchars($productName) . '
</div>';
// 1) Sichtbaren Rahmen zeichnen (feste Größe)
$pdf->SetLineWidth(0.3);
$pdf->Rect($nameX, $nameY, $nameW, $nameBoxH, 'D');
// 2) Inhalt in die Box schreiben und clippen
$pdf->StartTransform();
$pdf->Rect($nameX, $nameY, $nameW, $nameBoxH, 'CNZ'); // Clip auf Box setzen
$pdf->writeHTMLCell(
$nameW - 2*$namePad, // feste Breite für den Text
$nameBoxH - 2*$namePad, // feste Höhe für den Text
$nameX + $namePad, // x mit Innenabstand
$nameY + $namePad, // y mit Innenabstand
$nameHtml,
0, // kein HTML-Border
0, // ln
false, // fill
true, // reseth
'L', // align
true // autopadding
);
$pdf->StopTransform();
// Optional: Cursor unter die Box setzen (falls du danach im Fluss weitermachen willst)
$pdf->SetXY($nameX, $nameY + $nameBoxH);
// 1) Box-Geometrie (immer gleich groß)
$pnBoxW = 67.0; // mm Gesamtbreite der Box
$pnBoxH = 18.2; // mm Gesamthöhe der Box
$pnBoxPad = 2.0; // mm Innenabstand innerhalb der Box
// Position der Box (aktuell am rechten Rand oben; passe frei an)
$pnBoxX = $pdf->getPageWidth() - $MARGIN_RIGHT - $pnBoxW+1.8; // bündig am rechten Rand
$pnBoxY = $startY+21.5; // gleiche Oberkante wie Kopf
// 2) Sichtbaren Rahmen zeichnen (einmalig)
$pdf->SetLineWidth(0.3);
$pdf->Rect($pnBoxX, $pnBoxY, $pnBoxW, $pnBoxH, 'D');
// 3) Clipping aktivieren, damit Inhalt in der Box bleibt
$pdf->StartTransform();
$pdf->Rect($pnBoxX, $pnBoxY, $pnBoxW, $pnBoxH, 'CNZ'); // Clip auf die Box
// 4) Produktnummer: frei verschiebbar innerhalb der Box
$pnCombined = trim($productNumber . $deviceNumber);
$pnText = htmlspecialchars($pnCombined !== '' ? $pnCombined : 'N/A');
// Offsets innerhalb der Box hier stellst du die Position ein
$pnTextOffsetX = 2.0; // mm von linker Innenkante
$pnTextOffsetY = 1; // mm von oberer Innenkante
$pnTextW = $pnBoxW - 2*$pnBoxPad; // nutzbare Breite
$pnTextH = 8.0; // feste Höhe für den PN-Textbereich (mm)
$pnHtml = '
<div style="font-size:20px; line-height:1.25; margin:0; padding:0;">
'.$pnText.'
</div>';
// PN-Text rendern (ohne HTML-Border!)
$pdf->writeHTMLCell(
$pnTextW, // Breite Textbereich
$pnTextH, // Höhe Textbereich
$pnBoxX + $pnBoxPad + $pnTextOffsetX, // X in der Box
$pnBoxY + $pnBoxPad + $pnTextOffsetY, // Y in der Box
$pnHtml,
0, 0, false, true, 'L', true
);
// 5) QR-Code: frei verschiebbar innerhalb der Box, verzerrungsfrei per size
$qrData = trim((string)($productNumber . $deviceNumber));
if ($qrData === '') {
$qrData = 'N/A';
}
$qrSize = 16.0; // mm Kantenlänge des QR
$qrOffsetX = $pnBoxW - $pnBoxPad - $qrSize - 2.0; // mm von linker Innenkante
$qrOffsetY = $pnBoxH - $pnBoxPad - $qrSize +0.6; // mm von oberer Innenkante
$qrX = $pnBoxX + $qrOffsetX;
$qrY = $pnBoxY + $qrOffsetY;
$qrStyle = [
'border' => 0,
'vpadding' => 0,
'hpadding' => 0,
'fgcolor' => [0,0,0],
'bgcolor' => false,
];
// QR rendern (Warnings/Deprecated für diesen Block stumm schalten)
$_old_reporting = error_reporting();
error_reporting($_old_reporting & ~(E_WARNING | E_DEPRECATED | E_NOTICE));
$pdf->write2DBarcode($qrData, 'QRCODE,H', $qrX, $qrY, $qrSize, $qrSize, $qrStyle, 'N');
error_reporting($_old_reporting);
// 6) Clipping beenden
$pdf->StopTransform();
// 7) Optional: Cursor unter die PN-Box setzen, falls du im Fluss weiter willst
$pdf->SetXY($pnBoxX, $pnBoxY + $pnBoxH);
/** Bundle */
$bundleHeader = '<div style=" background-color: black; color: white; font-size:10px; font-weight:700;line-height:1.4; margin:4px 0 3px 0;">&nbsp;Bundleinhalt</div>';
$pdf->writeHTMLCell(70, 50, 7.65, 46, $bundleHeader, 0, 0, 0, true, 'L', true);
$bundleHeaderFillable = '<div style="border: 1px solid black; line-heigt:0.6"></div>';
//$pdf->writeHTMLCell(130.8, 39.8, 74.5, 46, $bundleHeaderFillable, 0, 0, 0, false, 'L', true);
drawFixedBox($pdf, 76.0, 47.7, 127.8, 4.65);
$bundleRows = '';
$BASE_FONT_PX = 10.0; // Level 1
$FONT_STEP_PX = 1.5; // je Ebene kleiner
$MIN_FONT_PX = 6.0;
$LINE_HEIGHT_BASE = 1.15; // Basis Zeilenhöhe
$LINE_HEIGHT_STEP = 0.05; // je Ebene etwas kompakter
$ROW_PAD_V_MM = 0.5; // vertikales Padding je Zeile
$ROW_PAD_H_MM = 0.8; // horizontales Padding
$WEIGHT_PAD_RIGHT_MM = 1.6; // mehr Abstand zum rechten Rand
$INDENT_MM_PER_LVL = 2.8; // Einrückung je Ebene in mm (stabil!)
foreach ($bundle as $item) {
$qty = (int)($item['qty'] ?? 1);
$text = htmlspecialchars($item['text'] ?? '');
$lvl = max($LEVEL_MIN, min($LEVEL_MAX, (int)($item['level'] ?? 1)));
$wkg = isset($item['weight_kg']) ? (float)$item['weight_kg'] : null;
$textFontPx = max($MIN_FONT_PX, $BASE_FONT_PX - ($lvl - 1) * $FONT_STEP_PX);
$weightFontPx = max($MIN_FONT_PX, $BASE_FONT_PX - ($lvl - 1) * $FONT_STEP_PX);
$lineHeight = max(1.0, $LINE_HEIGHT_BASE - ($lvl - 1) * $LINE_HEIGHT_STEP);
// Einrückung robust über mm aber nur Tabelle nutzen, wenn > 0 mm
$indentMm = max(0, ($lvl - 1) * $INDENT_MM_PER_LVL);
// Name-Spalte: ohne Spacer-Tabelle bei Level 1 (indent=0), sonst mit
if ($indentMm > 0) {
$nameCellHtml = '
<table cellspacing="0" cellpadding="0" width="100%">
<tr>
<td style="width:'.$indentMm.'mm; padding:0; margin:0;"></td>
<td style="font-size:'.$textFontPx.'px; line-height:'.$lineHeight.'; padding:0; margin:0;">'.$text.'</td>
</tr>
</table>';
} else {
// Level 1: direkt rendern
$nameCellHtml = '<span style="font-size:'.$textFontPx.'px; line-height:'.$lineHeight.';">'.$text.'</span>';
}
$bundleRows .= '
<tr>
<!-- Anzahl -->
<td style="
width:12%;
text-align:right;
vertical-align:middle;
font-size:'.$textFontPx.'px;
line-height:'.$lineHeight.';
padding:'.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm '.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
'.$qty.'
</td>
<!-- Name mit Einrückung -->
<td style="
width:68%;
vertical-align:middle;
padding:'.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
'.$nameCellHtml.'
</td>
<!-- Gewicht rechts mit extra Innenabstand -->
<td style="
width:20%;
text-align:right;
vertical-align:middle;
font-size:'.$weightFontPx.'px;
line-height:'.$lineHeight.';
padding:'.$ROW_PAD_V_MM.'mm '.$WEIGHT_PAD_RIGHT_MM.'mm '.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
'.($wkg !== null ? fnum($wkg).' kg' : '').'
</td>
</tr>';
}
$bundleHtml = '
<table cellspacing="0" cellpadding="0" style="border:1px solid #000; border-collapse:collapse;" width="100%">
<tbody>'.$bundleRows.'</tbody>
</table>';
$pdf->writeHTMLCell(
$pdf->getPageWidth() - 12.4,
0,
7.8,
50.8,
$bundleHtml,
0,
0,
0,
true,
'L',
true
);
/** ---------- Meta-Block dynamisch unterhalb der Bundle-Tabelle ---------- */
// Höhe der zuletzt gerenderten Bundle-Tabelle holen
$bundleH = $pdf->getLastH();
// Y-Position direkt nach der Bundle-Tabelle (mit 3 mm Abstand)
$metaY = 47.6 + $bundleH; // 50 = dein bisheriges Y der Bundle-Tabelle
$metaX = $MARGIN_LEFT;
$metaW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT;
// Falls Meta-Block zu nah am Seitenende wäre → neue Seite
$usableBottom = $pdf->getPageHeight() - $MARGIN_BOTTOM;
if ($metaY > $usableBottom - 40) { // 40 mm Puffer für Meta
$pdf->AddPage();
$metaY = $MARGIN_TOP;
}
// --- Inhalt wie gehabt ---
$leftMeta = '
<table cellspacing="0" cellpadding="'.$CELL_PAD.'" width="100%">
<tbody>
<!-- Maße -->
<tr>
<!-- Zusammengefasste linke Zelle: hat Rahmen, innen KEIN Rahmen -->
<td width="58%" border="1" style="padding-left:20px;">
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td width="64%">Maße <small>L x B x H</small></td>
<td width="36%">in cm</td>
</tr>
</table>
</td>
<!-- rechte Zelle mit Wert -->
<td width="42%" border="1"><b> '.htmlspecialchars($dimsBHT).'</b></td>
</tr>
<!-- Gewicht -->
<tr>
<td width="58%" border="1" style="padding-left:20px;">
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td width="64%">Gewicht</td>
<td width="36%">in Kg</td>
</tr>
</table>
</td>
<td width="42%" border="1"><b> '.fnum($bruttoWeightSum).' Kg</b></td>
</tr>
<!-- Volumen -->
<tr>
<td width="58%" border="1" style="padding-left:20px;">
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td width="64%">Volumen</td>
<td width="36%">in m³</td>
</tr>
</table>
</td>
<td width="42%" border="1"><b> '.fnum($volume, 2).' m³</b></td>
</tr>
<!-- Lagerplatz mit innerer Tabelle (optisch perfekt ausgerichtet) -->
<tr>
<td width="30%" border="1" style="padding-left:20px;">
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td width="100%">Lagerplatz</td>
</tr>
</table>
</td>
<td width="70%" border="1"><b> '.htmlspecialchars($storageLoc).'</b></td>
</tr>
</tbody>
</table>';
// ---- Koordinaten der Meta-Zeile bestimmen (nimm deine Werte) ----
// Falls du sie schon berechnet hast, nutze deine $metaX, $metaY, $metaW.
// Beispiel, wie du sie meist setzt:
$metaX = $MARGIN_LEFT;
$metaW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT;
// $metaY hast du zuvor als Ziel-Y für den Meta-Block berechnet:
/// $metaY = ... (z.B. 50 + $bundleH + 3);
// ---- Spaltenbreiten wie in deinem metaWrap: 62% | 2% | 36% ----
$leftW = $metaW * 0.62;
$gapW = $metaW * 0.02;
$rightW = $metaW * 0.36;
// Obere linke Ecke der rechten Spalte:
$rightX = $metaX + $leftW + $gapW;
$rightY = $metaY;
// ---- Fester Rahmen in der rechten Spalte ----
$frameOuterPad = 1.0; // mm: Abstand vom Spaltenrand
$frameW = $rightW - 2*$frameOuterPad+8; // Rahmenbreite
$frameH = 24.38; // mm: feste Rahmenhöhe, nach Wunsch
$frameX = $rightX + $frameOuterPad-5.2;
$frameY = $rightY + $frameOuterPad+0.5;
$pdf->SetLineWidth(0.3);
$pdf->Rect($frameX, $frameY, $frameW, $frameH, 'D'); // der feste Rahmen
// ---- Base64-Bild zentriert & verzerrungsfrei in den Rahmen ----
$tmpImg = tempnam(sys_get_temp_dir(), 'epi_') . '.png';
file_put_contents($tmpImg, base64_decode($productImage));
list($imgWpx, $imgHpx) = getimagesize($tmpImg);
$imgRatio = ($imgHpx > 0) ? ($imgWpx / $imgHpx) : 1.0;
$innerPad = 2.0; // mm: Innenabstand im Rahmen
$maxW = $frameW - 2*$innerPad;
$maxH = $frameH - 2*$innerPad;
// proportional einpassen
if ($maxW / $maxH > $imgRatio) {
$drawH = $maxH;
$drawW = $maxH * $imgRatio;
} else {
$drawW = $maxW;
$drawH = $maxW / $imgRatio;
}
// zentrieren
$imgX = $frameX + ($frameW - $drawW) / 2;
$imgY = $frameY + ($frameH - $drawH) / 2;
$pdf->Image($tmpImg, $imgX, $imgY, $drawW, $drawH, '', '', '', false, 300);
@unlink($tmpImg);
$metaWrap = '
<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-top:2px;">
<tr>
<td width="62%">'.$leftMeta.'</td>
<td width="2%"></td>
<td width="36%">'.$rightMeta.'</td>
</tr>
</table>';
// Meta-Block exakt an berechneter Position ausgeben
$pdf->writeHTMLCell($metaW, 0, $metaX, $metaY, $metaWrap, 0, 0, 0, true, 'L', true);
/** Output */
// Falls trotz allem vorher etwas ausgegeben wurde (Warnings), Buffer leeren:
if (ob_get_length()) { ob_end_clean(); }
// Äußerer Rahmen, Größenberechnung
// 1) Höhe der META-Zelle (zuletzt geschrieben) holen:
$metaH = $pdf->getLastH();
// 2) Y der Bundle-Tabelle kennst du (50.8 in deinem Code) + deren Höhe:
$bundleY = 50.8; // dein fixer Y für die Bundle-Tabelle
// $bundleH hast du bereits oben: $bundleH = $pdf->getLastH();
// 3) Bottom-Kanten aller Boxen berechnen:
$bottoms = [
$frameY + $frameH, // Logo-Box
$addrY + $addrBoxH, // Adress-Box
$prodY + $prodH, // Produktion-Box
$nameY + $nameBoxH, // Name-Box
$pnBoxY + $pnBoxH, // PN/QR-Box
$bundleY + $bundleH, // Bundle-Tabelle
$metaY + $metaH, // Meta-Block (linke Tabelle + rechter Bildrahmen)
];
// 4) Start-Y des Contentbereichs: oben an deinem Kopf beginnen
$outerY = min($frameY, $addrY, $prodY, $nameY, $pnBoxY, 46.0 /*Bundle-Header-Y*/, $metaY);
// 5) Untere Kante ist die größte Bottom-Kante:
$outerBottom = max($bottoms);
// 6) Außenrahmen-Geometrie:
$outerX = $MARGIN_LEFT+1.2;
$outerW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT+1;
$outerH = max(2, $outerBottom - $outerY)-1.2; // Mindeshöhe absichern
// 7) Dicke Linie zeichnen (z.B. 1.2 mm)
drawFixedBox($pdf, $outerX, $outerY, $outerW, $outerH, 1, '', 0, false);
$filename = 'Kistenetikett_'.preg_replace('/\s+/', '_', trim($productName)).'_'.$productNumber.$deviceNumber.'_'.date('Ymd_His').'.pdf';
$pdf->Output($filename, 'I');
/**
* Zeichnet eine flexible, leere oder beschriftete Box mit TCPDF.
*
* @param TCPDF $pdf Referenz auf dein TCPDF-Objekt
* @param float $x Linke obere Ecke (mm)
* @param float $y Obere Position (mm)
* @param float $w Breite (mm)
* @param float $h Höhe (mm)
* @param float $border Linienstärke in mm (z. B. 0.3)
* @param string $text (Optional) Inhalt oder Beschriftung
* @param float $pad Innenabstand in mm
* @param bool $fill true = grau hinterlegt, false = leer
* @param string|null $style (Optional) Linienstil 'solid', 'dashed', 'dotted'
*/
function drawFixedBox(
TCPDF $pdf,
float $x,
float $y,
float $w,
float $h,
float $border = 0.3,
string $text = '',
float $pad = 1.5,
bool $fill = false,
?string $style = 'solid'
): void {
// Farbe für Füllung (hellgrau, wenn aktiviert)
$fillColor = [240, 240, 240];
// Linienstil (falls angegeben)
switch ($style) {
case 'dashed':
$pdf->SetLineStyle(['width' => $border, 'dash' => '3,2']);
break;
case 'dotted':
$pdf->SetLineStyle(['width' => $border, 'dash' => '1,2']);
break;
default:
$pdf->SetLineWidth($border);
$pdf->SetLineStyle(['width' => $border]);
break;
}
// 1) Rahmen (und ggf. Füllung)
if ($fill) {
$pdf->SetFillColorArray($fillColor);
$pdf->Rect($x, $y, $w, $h, 'DF'); // Draw + Fill
} else {
$pdf->Rect($x, $y, $w, $h, 'D'); // nur Rahmen
}
// 2) Optionaler Textinhalt sauber eingepasst
if (trim($text) !== '') {
$pdf->StartTransform();
$pdf->Rect($x, $y, $w, $h, 'CNZ'); // Clip auf Box setzen
$pdf->writeHTMLCell(
$w - 2*$pad, $h - 2*$pad,
$x + $pad, $y + $pad,
'<div style="font-size:9px; line-height:1.2;">'.htmlspecialchars($text).'</div>',
0, 0, false, true, 'L', true
);
$pdf->StopTransform();
}
// Linienstil zurücksetzen
$pdf->SetLineStyle(['width' => 0.3, 'dash' => 0]);
}
/**
* Produkt-Details mit Cache holen (vermeidet doppelte API-Calls).
*
* @param Epirent $Epi
* @param int|string $productPk
* @param array $cache Referenz-Array für Produkt-Cache
* @return object|null
*/
function fetchProductDetailCached($Epi, $productPk, array &$cache) {
$key = (string)$productPk;
if (isset($cache[$key])) {
return $cache[$key];
}
try {
$res = $Epi->requestEpiApi('/v1/product/' . $key . '?cl=' . Epirent_Mandant);
$payload = json_decode($res, false);
$prod = $payload->payload[0] ?? null;
if ($prod) {
$cache[$key] = $prod;
return $prod;
}
} catch (\Throwable $e) {
// optional: loggen
}
$cache[$key] = null;
return null;
}
/**
* Rekursive Hilfsfunktion: fügt Materialien (und deren Submaterialien) zu $bundle hinzu
* und summiert alle Bruttogewichte in $bruttoWeightSum.
*
* @param Epirent $Epi
* @param array $materials Materialliste (vom Produkt)
* @param int $level aktuelle Ebene (1..MAX_BUNDLE_LEVEL)
* @param array &$bundle Ergebnisliste (wird befüllt)
* @param array &$cache Produkt-Cache
* @param array &$seenStack Schutz gegen Zyklen (Stack von Produkt-Keys)
* @param float $qtyMultiplier Multiplikator für Mengen aus übergeordneten Ebenen
* @param float &$bruttoWeightSum Summiert alle weight_gro-Werte × Menge
*/
function addMaterialsRecursive($Epi, array $materials, int $level, array &$bundle, array &$cache, array &$seenStack, float $qtyMultiplier = 1.0): void
{
global $bruttoWeightSum;
$MAX_BUNDLE_LEVEL = 5;
if ($level > $MAX_BUNDLE_LEVEL || empty($materials)) {
return;
}
foreach ($materials as $mat) {
$isFree = !empty($mat->is_free_material);
$amount = isset($mat->amount) ? (float)$mat->amount : 1.0;
$effQty = $amount * $qtyMultiplier;
// 🟢 1. FREIES MATERIAL → nur Name + Menge, kein weiterer Drilldown
if ($isFree) {
$name = isset($mat->name) ? (string)$mat->name : '(Unbenanntes freies Material)';
$bundle[] = [
'qty' => $effQty,
'text' => $name,
'level' => $level,
'weight_kg' => null,
];
continue; // ⬅️ ganz wichtig: hier abbrechen
}
// 🟡 2. "Normales" Produkt-Material
$childPk = $mat->mat_product_pk ?? null;
if (!$childPk) {
$bundle[] = [
'qty' => $effQty,
'text' => '(Unbekanntes Material)',
'level' => $level,
'weight_kg' => null,
];
continue;
}
// Zyklen-Schutz
$childKey = (string)$childPk;
if (in_array($childKey, $seenStack, true)) {
$bundle[] = [
'qty' => $effQty,
'text' => '[Zyklus erkannt bei Produkt ' . $childKey . ']',
'level' => $level,
'weight_kg' => null,
];
continue;
}
// Produktdetail abrufen (Cache-basiert)
$child = fetchProductDetailCached($Epi, $childPk, $cache);
// Basisinfos
$childName = $child->name ?? ('Produkt ' . $childKey);
$childWeightNet = $child->tech_data->weight_net ?? null;
$childWeightGross = $child->tech_data->weight_gro ?? null;
$childNumber = $child->product_no ?? null;
// 🧮 Bruttogewicht zur globalen Summe addieren
if (!empty($childWeightGross)) {
$bruttoWeightSum += $effQty * (float)$childWeightGross;
}
// 📦 Anzeige: Artikelnummer in Klammern voranstellen
$displayName = $childNumber ? '(' . $childNumber . ') ' . $childName : $childName;
$bundle[] = [
'qty' => $effQty,
'text' => (string)$displayName,
'level' => $level,
'weight_kg' => $childWeightNet ? (float)$childWeightNet : null,
];
// 🔁 Rekursion für Sub-Materialien
if ($child && !empty($child->materials) && $level < $MAX_BUNDLE_LEVEL) {
$seenStack[] = $childKey;
addMaterialsRecursive($Epi, (array)$child->materials, $level + 1, $bundle, $cache, $seenStack, $effQty);
array_pop($seenStack);
}
}
}
/**
* Einstieg: Baut das Bundle eines Produkts bis Tiefe MAX_BUNDLE_LEVEL
* und liefert zusätzlich die Bruttogewichtsumme zurück.
*
* @param Epirent $Epi
* @param object $product
* @param float &$bruttoWeightSum Rückgabe der aufsummierten weight_gro
* @return array
*/
function buildBundleForProduct($Epi, $product, float &$bruttoWeightSum = 0.0): array
{
$bundle = [];
$cache = [];
$seen = [];
$materials = isset($product->materials) ? (array)$product->materials : [];
addMaterialsRecursive($Epi, $materials, 1, $bundle, $cache, $seen, 1.0, $bruttoWeightSum);
return $bundle;
}

View File

@@ -8,9 +8,17 @@
Dashboard Dashboard
</a> </a>
<a class="nav-link" href=orders.php> <a class="nav-link" href=orders.php>
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div> <div class="sb-nav-link-icon"><i class="fas fa-money-bill-wave"></i></div>
Aufträge Aufträge
</a> </a>
<a class="nav-link" href=insurance.php>
<div class="sb-nav-link-icon"><i class="fas fa-car-burst"></i></div>
Versicherung
</a>
<a class="nav-link" href=labelprint.php>
<div class="sb-nav-link-icon"><i class="fas fa-tags"></i></div>
Labelprint
</a>
<div class="sb-sidenav-menu-heading">Addons</div> <div class="sb-sidenav-menu-heading">Addons</div>
<a class="nav-link" target="_blank" href="../Packmonitor.php"> <a class="nav-link" target="_blank" href="../Packmonitor.php">
@@ -25,6 +33,13 @@
<div class="sb-nav-link-icon"><i class="fas fa-list"></i></div> <div class="sb-nav-link-icon"><i class="fas fa-list"></i></div>
Pack- & Aufgabenmonitor Pack- & Aufgabenmonitor
</a> </a>
<div class="sb-sidenav-menu-heading">Config</div>
<a class="nav-link" href=editconfig.php>
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
Config
</a>
</div> </div>
</div> </div>
<div class="sb-sidenav-footer"> <div class="sb-sidenav-footer">

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

10663
src/fa/css/all.css Normal file

File diff suppressed because it is too large Load Diff

9
src/fa/css/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2219
src/fa/css/brands.css Normal file

File diff suppressed because it is too large Load Diff

6
src/fa/css/brands.min.css vendored Normal file

File diff suppressed because one or more lines are too long

8361
src/fa/css/fontawesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

8
src/fa/css/fontawesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

31
src/fa/css/regular.css Normal file
View File

@@ -0,0 +1,31 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:root, :host {
--fa-family-classic: "Font Awesome 7 Free";
--fa-font-regular: normal 400 1em/1 var(--fa-family-classic);
/* deprecated: this older custom property will be removed next major release */
--fa-style-family-classic: var(--fa-family-classic);
}
@font-face {
font-family: "Font Awesome 7 Free";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-regular-400.woff2");
}
.far {
--fa-family: var(--fa-family-classic);
--fa-style: 400;
}
.fa-classic {
--fa-family: var(--fa-family-classic);
}
.fa-regular {
--fa-style: 400;
}

6
src/fa/css/regular.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:host,:root{--fa-family-classic:"Font Awesome 7 Free";--fa-font-regular:normal 400 1em/1 var(--fa-family-classic);--fa-style-family-classic:var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2)}.far{--fa-style:400}.fa-classic,.far{--fa-family:var(--fa-family-classic)}.fa-regular{--fa-style:400}

31
src/fa/css/solid.css Normal file
View File

@@ -0,0 +1,31 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:root, :host {
--fa-family-classic: "Font Awesome 7 Free";
--fa-font-solid: normal 900 1em/1 var(--fa-family-classic);
/* deprecated: this older custom property will be removed next major release */
--fa-style-family-classic: var(--fa-family-classic);
}
@font-face {
font-family: "Font Awesome 7 Free";
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2");
}
.fas {
--fa-family: var(--fa-family-classic);
--fa-style: 900;
}
.fa-classic {
--fa-family: var(--fa-family-classic);
}
.fa-solid {
--fa-style: 900;
}

6
src/fa/css/solid.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:host,:root{--fa-family-classic:"Font Awesome 7 Free";--fa-font-solid:normal 900 1em/1 var(--fa-family-classic);--fa-style-family-classic:var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2)}.fas{--fa-style:900}.fa-classic,.fas{--fa-family:var(--fa-family-classic)}.fa-solid{--fa-style:900}

556
src/fa/css/svg-with-js.css Normal file
View File

@@ -0,0 +1,556 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:root, :host {
--fa-font-solid: normal 900 1em/1 "Font Awesome 7 Free";
--fa-font-regular: normal 400 1em/1 "Font Awesome 7 Free";
--fa-font-light: normal 300 1em/1 "Font Awesome 7 Pro";
--fa-font-thin: normal 100 1em/1 "Font Awesome 7 Pro";
--fa-font-duotone: normal 900 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-regular: normal 400 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-light: normal 300 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-thin: normal 100 1em/1 "Font Awesome 7 Duotone";
--fa-font-brands: normal 400 1em/1 "Font Awesome 7 Brands";
--fa-font-sharp-solid: normal 900 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-regular: normal 400 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-light: normal 300 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-thin: normal 100 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-duotone-solid: normal 900 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-regular: normal 400 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-light: normal 300 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-thin: normal 100 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-slab-regular: normal 400 1em/1 "Font Awesome 7 Slab";
--fa-font-slab-press-regular: normal 400 1em/1 "Font Awesome 7 Slab Press";
--fa-font-whiteboard-semibold: normal 600 1em/1 "Font Awesome 7 Whiteboard";
--fa-font-thumbprint-light: normal 300 1em/1 "Font Awesome 7 Thumbprint";
--fa-font-notdog-solid: normal 900 1em/1 "Font Awesome 7 Notdog";
--fa-font-notdog-duo-solid: normal 900 1em/1 "Font Awesome 7 Notdog Duo";
--fa-font-etch-solid: normal 900 1em/1 "Font Awesome 7 Etch";
--fa-font-jelly-regular: normal 400 1em/1 "Font Awesome 7 Jelly";
--fa-font-jelly-fill-regular: normal 400 1em/1 "Font Awesome 7 Jelly Fill";
--fa-font-jelly-duo-regular: normal 400 1em/1 "Font Awesome 7 Jelly Duo";
--fa-font-chisel-regular: normal 400 1em/1 "Font Awesome 7 Chisel";
--fa-font-utility-semibold: normal 600 1em/1 "Font Awesome 7 Utility";
--fa-font-utility-duo-semibold: normal 600 1em/1 "Font Awesome 7 Utility Duo";
--fa-font-utility-fill-semibold: normal 600 1em/1 "Font Awesome 7 Utility Fill";
}
.svg-inline--fa {
box-sizing: content-box;
display: var(--fa-display, inline-block);
height: 1em;
overflow: visible;
vertical-align: -0.125em;
width: var(--fa-width, 1.25em);
}
.svg-inline--fa.fa-2xs {
vertical-align: 0.1em;
}
.svg-inline--fa.fa-xs {
vertical-align: 0em;
}
.svg-inline--fa.fa-sm {
vertical-align: -0.0714285714em;
}
.svg-inline--fa.fa-lg {
vertical-align: -0.2em;
}
.svg-inline--fa.fa-xl {
vertical-align: -0.25em;
}
.svg-inline--fa.fa-2xl {
vertical-align: -0.3125em;
}
.svg-inline--fa.fa-pull-left,
.svg-inline--fa .fa-pull-start {
float: inline-start;
margin-inline-end: var(--fa-pull-margin, 0.3em);
}
.svg-inline--fa.fa-pull-right,
.svg-inline--fa .fa-pull-end {
float: inline-end;
margin-inline-start: var(--fa-pull-margin, 0.3em);
}
.svg-inline--fa.fa-li {
width: var(--fa-li-width, 2em);
inset-inline-start: calc(-1 * var(--fa-li-width, 2em));
inset-block-start: 0.25em; /* syncing vertical alignment with Web Font rendering */
}
.fa-layers-counter, .fa-layers-text {
display: inline-block;
position: absolute;
text-align: center;
}
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -0.125em;
width: var(--fa-width, 1.25em);
}
.fa-layers .svg-inline--fa {
inset: 0;
margin: auto;
position: absolute;
transform-origin: center center;
}
.fa-layers-text {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transform-origin: center center;
}
.fa-layers-counter {
background-color: var(--fa-counter-background-color, #ff253a);
border-radius: var(--fa-counter-border-radius, 1em);
box-sizing: border-box;
color: var(--fa-inverse, #fff);
line-height: var(--fa-counter-line-height, 1);
max-width: var(--fa-counter-max-width, 5em);
min-width: var(--fa-counter-min-width, 1.5em);
overflow: hidden;
padding: var(--fa-counter-padding, 0.25em 0.5em);
right: var(--fa-right, 0);
text-overflow: ellipsis;
top: var(--fa-top, 0);
transform: scale(var(--fa-counter-scale, 0.25));
transform-origin: top right;
}
.fa-layers-bottom-right {
bottom: var(--fa-bottom, 0);
right: var(--fa-right, 0);
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom right;
}
.fa-layers-bottom-left {
bottom: var(--fa-bottom, 0);
left: var(--fa-left, 0);
right: auto;
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom left;
}
.fa-layers-top-right {
top: var(--fa-top, 0);
right: var(--fa-right, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top right;
}
.fa-layers-top-left {
left: var(--fa-left, 0);
right: auto;
top: var(--fa-top, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top left;
}
.fa-1x {
font-size: 1em;
}
.fa-2x {
font-size: 2em;
}
.fa-3x {
font-size: 3em;
}
.fa-4x {
font-size: 4em;
}
.fa-5x {
font-size: 5em;
}
.fa-6x {
font-size: 6em;
}
.fa-7x {
font-size: 7em;
}
.fa-8x {
font-size: 8em;
}
.fa-9x {
font-size: 9em;
}
.fa-10x {
font-size: 10em;
}
.fa-2xs {
font-size: calc(10 / 16 * 1em); /* converts a 10px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 10 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 10 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-xs {
font-size: calc(12 / 16 * 1em); /* converts a 12px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 12 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 12 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-sm {
font-size: calc(14 / 16 * 1em); /* converts a 14px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 14 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 14 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-lg {
font-size: calc(20 / 16 * 1em); /* converts a 20px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 20 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 20 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-xl {
font-size: calc(24 / 16 * 1em); /* converts a 24px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 24 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 24 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-2xl {
font-size: calc(32 / 16 * 1em); /* converts a 32px size into an em-based value that's relative to the scale's 16px base */
line-height: calc(1 / 32 * 1em); /* sets the line-height of the icon back to that of it's parent */
vertical-align: calc((6 / 32 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text's descender */
}
.fa-width-auto {
--fa-width: auto;
}
.fa-fw,
.fa-width-fixed {
--fa-width: 1.25em;
}
.fa-ul {
list-style-type: none;
margin-inline-start: var(--fa-li-margin, 2.5em);
padding-inline-start: 0;
}
.fa-ul > li {
position: relative;
}
.fa-li {
inset-inline-start: calc(-1 * var(--fa-li-width, 2em));
position: absolute;
text-align: center;
width: var(--fa-li-width, 2em);
line-height: inherit;
}
/* Heads Up: Bordered Icons will not be supported in the future!
- This feature will be deprecated in the next major release of Font Awesome (v8)!
- You may continue to use it in this version *v7), but it will not be supported in Font Awesome v8.
*/
/* Notes:
* --@{v.$css-prefix}-border-width = 1/16 by default (to render as ~1px based on a 16px default font-size)
* --@{v.$css-prefix}-border-padding =
** 3/16 for vertical padding (to give ~2px of vertical whitespace around an icon considering it's vertical alignment)
** 4/16 for horizontal padding (to give ~4px of horizontal whitespace around an icon)
*/
.fa-border {
border-color: var(--fa-border-color, #eee);
border-radius: var(--fa-border-radius, 0.1em);
border-style: var(--fa-border-style, solid);
border-width: var(--fa-border-width, 0.0625em);
box-sizing: var(--fa-border-box-sizing, content-box);
padding: var(--fa-border-padding, 0.1875em 0.25em);
}
.fa-pull-left,
.fa-pull-start {
float: inline-start;
margin-inline-end: var(--fa-pull-margin, 0.3em);
}
.fa-pull-right,
.fa-pull-end {
float: inline-end;
margin-inline-start: var(--fa-pull-margin, 0.3em);
}
.fa-beat {
animation-name: fa-beat;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out);
}
.fa-bounce {
animation-name: fa-bounce;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));
}
.fa-fade {
animation-name: fa-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
}
.fa-beat-fade {
animation-name: fa-beat-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
}
.fa-flip {
animation-name: fa-flip;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out);
}
.fa-shake {
animation-name: fa-shake;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear);
}
.fa-spin {
animation-name: fa-spin;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 2s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear);
}
.fa-spin-reverse {
--fa-animation-direction: reverse;
}
.fa-pulse,
.fa-spin-pulse {
animation-name: fa-spin;
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, steps(8));
}
@media (prefers-reduced-motion: reduce) {
.fa-beat,
.fa-bounce,
.fa-fade,
.fa-beat-fade,
.fa-flip,
.fa-pulse,
.fa-shake,
.fa-spin,
.fa-spin-pulse {
animation: none !important;
transition: none !important;
}
}
@keyframes fa-beat {
0%, 90% {
transform: scale(1);
}
45% {
transform: scale(var(--fa-beat-scale, 1.25));
}
}
@keyframes fa-bounce {
0% {
transform: scale(1, 1) translateY(0);
}
10% {
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
}
30% {
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
}
50% {
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
}
57% {
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
}
64% {
transform: scale(1, 1) translateY(0);
}
100% {
transform: scale(1, 1) translateY(0);
}
}
@keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4);
}
}
@keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(var(--fa-beat-fade-scale, 1.125));
}
}
@keyframes fa-flip {
50% {
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
}
}
@keyframes fa-shake {
0% {
transform: rotate(-15deg);
}
4% {
transform: rotate(15deg);
}
8%, 24% {
transform: rotate(-18deg);
}
12%, 28% {
transform: rotate(18deg);
}
16% {
transform: rotate(-22deg);
}
20% {
transform: rotate(22deg);
}
32% {
transform: rotate(-12deg);
}
36% {
transform: rotate(12deg);
}
40%, 100% {
transform: rotate(0deg);
}
}
@keyframes fa-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.fa-rotate-90 {
transform: rotate(90deg);
}
.fa-rotate-180 {
transform: rotate(180deg);
}
.fa-rotate-270 {
transform: rotate(270deg);
}
.fa-flip-horizontal {
transform: scale(-1, 1);
}
.fa-flip-vertical {
transform: scale(1, -1);
}
.fa-flip-both,
.fa-flip-horizontal.fa-flip-vertical {
transform: scale(-1, -1);
}
.fa-rotate-by {
transform: rotate(var(--fa-rotate-angle, 0));
}
.svg-inline--fa .fa-primary {
fill: var(--fa-primary-color, currentColor);
opacity: var(--fa-primary-opacity, 1);
}
.svg-inline--fa .fa-secondary {
fill: var(--fa-secondary-color, currentColor);
opacity: var(--fa-secondary-opacity, 0.4);
}
.svg-inline--fa.fa-swap-opacity .fa-primary {
opacity: var(--fa-secondary-opacity, 0.4);
}
.svg-inline--fa.fa-swap-opacity .fa-secondary {
opacity: var(--fa-primary-opacity, 1);
}
.svg-inline--fa mask .fa-primary,
.svg-inline--fa mask .fa-secondary {
fill: black;
}
.svg-inline--fa.fa-inverse {
fill: var(--fa-inverse, #fff);
}
.fa-stack {
display: inline-block;
height: 2em;
line-height: 2em;
position: relative;
vertical-align: middle;
width: 2.5em;
}
.fa-inverse {
color: var(--fa-inverse, #fff);
}
.svg-inline--fa.fa-stack-1x {
--fa-width: 1.25em;
height: 1em;
width: var(--fa-width);
}
.svg-inline--fa.fa-stack-2x {
--fa-width: 2.5em;
height: 2em;
width: var(--fa-width);
}
.fa-stack-1x,
.fa-stack-2x {
inset: 0;
margin: auto;
position: absolute;
z-index: var(--fa-stack-z-index, auto);
}

6
src/fa/css/svg-with-js.min.css vendored Normal file

File diff suppressed because one or more lines are too long

182
src/fa/css/svg.css Normal file
View File

@@ -0,0 +1,182 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:root, :host {
--fa-font-solid: normal 900 1em/1 "Font Awesome 7 Free";
--fa-font-regular: normal 400 1em/1 "Font Awesome 7 Free";
--fa-font-light: normal 300 1em/1 "Font Awesome 7 Pro";
--fa-font-thin: normal 100 1em/1 "Font Awesome 7 Pro";
--fa-font-duotone: normal 900 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-regular: normal 400 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-light: normal 300 1em/1 "Font Awesome 7 Duotone";
--fa-font-duotone-thin: normal 100 1em/1 "Font Awesome 7 Duotone";
--fa-font-brands: normal 400 1em/1 "Font Awesome 7 Brands";
--fa-font-sharp-solid: normal 900 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-regular: normal 400 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-light: normal 300 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-thin: normal 100 1em/1 "Font Awesome 7 Sharp";
--fa-font-sharp-duotone-solid: normal 900 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-regular: normal 400 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-light: normal 300 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-sharp-duotone-thin: normal 100 1em/1 "Font Awesome 7 Sharp Duotone";
--fa-font-slab-regular: normal 400 1em/1 "Font Awesome 7 Slab";
--fa-font-slab-press-regular: normal 400 1em/1 "Font Awesome 7 Slab Press";
--fa-font-whiteboard-semibold: normal 600 1em/1 "Font Awesome 7 Whiteboard";
--fa-font-thumbprint-light: normal 300 1em/1 "Font Awesome 7 Thumbprint";
--fa-font-notdog-solid: normal 900 1em/1 "Font Awesome 7 Notdog";
--fa-font-notdog-duo-solid: normal 900 1em/1 "Font Awesome 7 Notdog Duo";
--fa-font-etch-solid: normal 900 1em/1 "Font Awesome 7 Etch";
--fa-font-jelly-regular: normal 400 1em/1 "Font Awesome 7 Jelly";
--fa-font-jelly-fill-regular: normal 400 1em/1 "Font Awesome 7 Jelly Fill";
--fa-font-jelly-duo-regular: normal 400 1em/1 "Font Awesome 7 Jelly Duo";
--fa-font-chisel-regular: normal 400 1em/1 "Font Awesome 7 Chisel";
--fa-font-utility-semibold: normal 600 1em/1 "Font Awesome 7 Utility";
--fa-font-utility-duo-semibold: normal 600 1em/1 "Font Awesome 7 Utility Duo";
--fa-font-utility-fill-semibold: normal 600 1em/1 "Font Awesome 7 Utility Fill";
}
.svg-inline--fa {
box-sizing: content-box;
display: var(--fa-display, inline-block);
height: 1em;
overflow: visible;
vertical-align: -0.125em;
width: var(--fa-width, 1.25em);
}
.svg-inline--fa.fa-2xs {
vertical-align: 0.1em;
}
.svg-inline--fa.fa-xs {
vertical-align: 0em;
}
.svg-inline--fa.fa-sm {
vertical-align: -0.0714285714em;
}
.svg-inline--fa.fa-lg {
vertical-align: -0.2em;
}
.svg-inline--fa.fa-xl {
vertical-align: -0.25em;
}
.svg-inline--fa.fa-2xl {
vertical-align: -0.3125em;
}
.svg-inline--fa.fa-li {
width: var(--fa-li-width, 2em);
inset-inline-start: calc(-1 * var(--fa-li-width, 2em));
inset-block-start: 0.25em; /* syncing vertical alignment with Web Font rendering */
}
.fa-layers-counter, .fa-layers-text {
display: inline-block;
position: absolute;
text-align: center;
}
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -0.125em;
width: var(--fa-width, 1.25em);
}
.fa-layers .svg-inline--fa {
inset: 0;
margin: auto;
position: absolute;
transform-origin: center center;
}
.fa-layers-text {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transform-origin: center center;
}
.fa-layers-counter {
background-color: var(--fa-counter-background-color, #ff253a);
border-radius: var(--fa-counter-border-radius, 1em);
box-sizing: border-box;
color: var(--fa-inverse, #fff);
line-height: var(--fa-counter-line-height, 1);
max-width: var(--fa-counter-max-width, 5em);
min-width: var(--fa-counter-min-width, 1.5em);
overflow: hidden;
padding: var(--fa-counter-padding, 0.25em 0.5em);
right: var(--fa-right, 0);
text-overflow: ellipsis;
top: var(--fa-top, 0);
transform: scale(var(--fa-counter-scale, 0.25));
transform-origin: top right;
}
.fa-layers-bottom-right {
bottom: var(--fa-bottom, 0);
right: var(--fa-right, 0);
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom right;
}
.fa-layers-bottom-left {
bottom: var(--fa-bottom, 0);
left: var(--fa-left, 0);
right: auto;
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom left;
}
.fa-layers-top-right {
top: var(--fa-top, 0);
right: var(--fa-right, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top right;
}
.fa-layers-top-left {
left: var(--fa-left, 0);
right: auto;
top: var(--fa-top, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top left;
}
.svg-inline--fa .fa-primary {
fill: var(--fa-primary-color, currentColor);
opacity: var(--fa-primary-opacity, 1);
}
.svg-inline--fa .fa-secondary {
fill: var(--fa-secondary-color, currentColor);
opacity: var(--fa-secondary-opacity, 0.4);
}
.svg-inline--fa.fa-swap-opacity .fa-primary {
opacity: var(--fa-secondary-opacity, 0.4);
}
.svg-inline--fa.fa-swap-opacity .fa-secondary {
opacity: var(--fa-primary-opacity, 1);
}
.svg-inline--fa mask .fa-primary,
.svg-inline--fa mask .fa-secondary {
fill: black;
}
.svg-inline--fa.fa-inverse {
fill: var(--fa-inverse, #fff);
}
.fa-stack-1x,
.fa-stack-2x {
inset: 0;
margin: auto;
position: absolute;
z-index: var(--fa-stack-z-index, auto);
}

6
src/fa/css/svg.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:host,:root{--fa-font-solid:normal 900 1em/1 "Font Awesome 7 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 7 Free";--fa-font-light:normal 300 1em/1 "Font Awesome 7 Pro";--fa-font-thin:normal 100 1em/1 "Font Awesome 7 Pro";--fa-font-duotone:normal 900 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-regular:normal 400 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-light:normal 300 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-thin:normal 100 1em/1 "Font Awesome 7 Duotone";--fa-font-brands:normal 400 1em/1 "Font Awesome 7 Brands";--fa-font-sharp-solid:normal 900 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-regular:normal 400 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-light:normal 300 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-thin:normal 100 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-duotone-solid:normal 900 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-regular:normal 400 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-light:normal 300 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-thin:normal 100 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-slab-regular:normal 400 1em/1 "Font Awesome 7 Slab";--fa-font-slab-press-regular:normal 400 1em/1 "Font Awesome 7 Slab Press";--fa-font-whiteboard-semibold:normal 600 1em/1 "Font Awesome 7 Whiteboard";--fa-font-thumbprint-light:normal 300 1em/1 "Font Awesome 7 Thumbprint";--fa-font-notdog-solid:normal 900 1em/1 "Font Awesome 7 Notdog";--fa-font-notdog-duo-solid:normal 900 1em/1 "Font Awesome 7 Notdog Duo";--fa-font-etch-solid:normal 900 1em/1 "Font Awesome 7 Etch";--fa-font-jelly-regular:normal 400 1em/1 "Font Awesome 7 Jelly";--fa-font-jelly-fill-regular:normal 400 1em/1 "Font Awesome 7 Jelly Fill";--fa-font-jelly-duo-regular:normal 400 1em/1 "Font Awesome 7 Jelly Duo";--fa-font-chisel-regular:normal 400 1em/1 "Font Awesome 7 Chisel";--fa-font-utility-semibold:normal 600 1em/1 "Font Awesome 7 Utility";--fa-font-utility-duo-semibold:normal 600 1em/1 "Font Awesome 7 Utility Duo";--fa-font-utility-fill-semibold:normal 600 1em/1 "Font Awesome 7 Utility Fill"}.svg-inline--fa{box-sizing:content-box;display:var(--fa-display,inline-block);height:1em;overflow:visible;vertical-align:-.125em;width:var(--fa-width,1.25em)}.svg-inline--fa.fa-2xs{vertical-align:.1em}.svg-inline--fa.fa-xs{vertical-align:0}.svg-inline--fa.fa-sm{vertical-align:-.0714285714em}.svg-inline--fa.fa-lg{vertical-align:-.2em}.svg-inline--fa.fa-xl{vertical-align:-.25em}.svg-inline--fa.fa-2xl{vertical-align:-.3125em}.svg-inline--fa.fa-li{width:var(--fa-li-width,2em);inset-inline-start:calc(var(--fa-li-width, 2em)*-1);inset-block-start:.25em}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:var(--fa-width,1.25em)}.fa-layers .svg-inline--fa{inset:0;margin:auto;position:absolute;transform-origin:center center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:var(--fa-counter-background-color,#ff253a);border-radius:var(--fa-counter-border-radius,1em);box-sizing:border-box;color:var(--fa-inverse,#fff);line-height:var(--fa-counter-line-height,1);max-width:var(--fa-counter-max-width,5em);min-width:var(--fa-counter-min-width,1.5em);overflow:hidden;padding:var(--fa-counter-padding,.25em .5em);right:var(--fa-right,0);text-overflow:ellipsis;top:var(--fa-top,0);transform:scale(var(--fa-counter-scale,.25));transform-origin:top right}.fa-layers-bottom-right{bottom:var(--fa-bottom,0);right:var(--fa-right,0);top:auto;transform:scale(var(--fa-layers-scale,.25));transform-origin:bottom right}.fa-layers-bottom-left{bottom:var(--fa-bottom,0);left:var(--fa-left,0);right:auto;top:auto;transform:scale(var(--fa-layers-scale,.25));transform-origin:bottom left}.fa-layers-top-right{top:var(--fa-top,0);right:var(--fa-right,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:top right}.fa-layers-top-left{left:var(--fa-left,0);right:auto;top:var(--fa-top,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:top left}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor)}.svg-inline--fa .fa-secondary,.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.svg-inline--fa.fa-inverse{fill:var(--fa-inverse,#fff)}.fa-stack-1x,.fa-stack-2x{inset:0;margin:auto;position:absolute;z-index:var(--fa-stack-z-index,auto)}

View File

@@ -0,0 +1,27 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
@font-face {
font-family: "FontAwesome";
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2");
}
@font-face {
font-family: "FontAwesome";
font-display: block;
src: url("../webfonts/fa-brands-400.woff2") format("woff2");
}
@font-face {
font-family: "FontAwesome";
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2");
unicode-range: U+F003, U+F006, U+F014, U+F016-F017, U+F01A-F01B, U+F01D, U+F022, U+F03E, U+F044, U+F046, U+F05C-F05D, U+F06E, U+F070, U+F087-F088, U+F08A, U+F094, U+F096-F097, U+F09D, U+F0A0, U+F0A2, U+F0A4-F0A7, U+F0C5, U+F0C7, U+F0E5-F0E6, U+F0EB, U+F0F6-F0F8, U+F10C, U+F114-F115, U+F118-F11A, U+F11C-F11D, U+F133, U+F147, U+F14E, U+F150-F152, U+F185-F186, U+F18E, U+F190-F192, U+F196, U+F1C1-F1C9, U+F1D9, U+F1DB, U+F1E3, U+F1EA, U+F1F7, U+F1F9, U+F20A, U+F247-F248, U+F24A, U+F24D, U+F255-F25B, U+F25D, U+F271-F274, U+F278, U+F27B, U+F28C, U+F28E, U+F29C, U+F2B5, U+F2B7, U+F2BA, U+F2BC, U+F2BE, U+F2C0-F2C1, U+F2C3, U+F2D0, U+F2D2, U+F2D4, U+F2DC;
}
@font-face {
font-family: "FontAwesome";
font-display: block;
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2");
unicode-range: U+F041, U+F047, U+F065-F066, U+F07D-F07E, U+F080, U+F08B, U+F08E, U+F090, U+F09A, U+F0AC, U+F0AE, U+F0B2, U+F0D0, U+F0D6, U+F0E4, U+F0EC, U+F10A-F10B, U+F123, U+F13E, U+F148-F149, U+F14C, U+F156, U+F15E, U+F160-F161, U+F163, U+F175-F178, U+F195, U+F1F8, U+F219, U+F27A;
}

6
src/fa/css/v4-font-face.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}

2818
src/fa/css/v4-shims.css Normal file

File diff suppressed because it is too large Load Diff

6
src/fa/css/v4-shims.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
@font-face {
font-family: "Font Awesome 5 Brands";
font-display: block;
font-weight: 400;
src: url("../webfonts/fa-brands-400.woff2") format("woff2");
}
@font-face {
font-family: "Font Awesome 5 Free";
font-display: block;
font-weight: 900;
src: url("../webfonts/fa-solid-900.woff2") format("woff2");
}
@font-face {
font-family: "Font Awesome 5 Free";
font-display: block;
font-weight: 400;
src: url("../webfonts/fa-regular-400.woff2") format("woff2");
}

6
src/fa/css/v5-font-face.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2")}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

18
vendor/autoload.php vendored
View File

@@ -2,6 +2,24 @@
// autoload.php @generated by Composer // 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;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php'; require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitab00f29709bdbfe3a1abd7d2d66d569c::getLoader(); return ComposerAutoloaderInitab00f29709bdbfe3a1abd7d2d66d569c::getLoader();

View File

@@ -1,851 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</value>
</option>
<option name="RIGHT_MARGIN" value="130" />
<CssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
<option name="HEX_COLOR_SHORT_FORMAT" value="true" />
<option name="KEEP_SINGLE_LINE_BLOCKS" value="true" />
<option name="SPACE_BEFORE_OPENING_BRACE" value="false" />
</CssCodeStyleSettings>
<DB2CodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</DB2CodeStyleSettings>
<DerbyCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</DerbyCodeStyleSettings>
<H2CodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</H2CodeStyleSettings>
<HSQLCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</HSQLCodeStyleSettings>
<HTMLCodeStyleSettings>
<option name="HTML_ATTRIBUTE_WRAP" value="0" />
<option name="HTML_TEXT_WRAP" value="0" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="ALIGN_OBJECT_PROPERTIES" value="2" />
<option name="ALIGN_VAR_STATEMENTS" value="1" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="SPACE_BEFORE_CLASS_LBRACE" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="IMPORTS_WRAP" value="1" />
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
</JSCodeStyleSettings>
<MSSQLCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</MSSQLCodeStyleSettings>
<MySQLCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</MySQLCodeStyleSettings>
<OracleCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</OracleCodeStyleSettings>
<PHPCodeStyleSettings>
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
<option name="ALIGN_PHPDOC_PARAM_NAMES" value="true" />
<option name="ALIGN_PHPDOC_COMMENTS" value="true" />
<option name="ALIGN_ASSIGNMENTS" value="true" />
<option name="CONCAT_SPACES" value="false" />
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
<option name="LOWER_CASE_NULL_CONST" value="true" />
<option name="ELSE_IF_STYLE" value="COMBINE" />
<option name="FIELDS_DEFAULT_VISIBILITY" value="protected" />
<option name="BLANK_LINES_BEFORE_RETURN_STATEMENT" value="1" />
<option name="KEEP_RPAREN_AND_LBRACE_ON_ONE_LINE" value="true" />
<option name="ALIGN_CLASS_CONSTANTS" value="true" />
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="1" />
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
<option name="SPACE_AFTER_COLON_IN_RETURN_TYPE" value="false" />
<option name="PHPDOC_USE_FQCN" value="true" />
<option name="MULTILINE_CHAINED_CALLS_SEMICOLON_ON_NEW_LINE" value="true" />
<option name="PREFER_TEMPLATE_INDENTS" value="true" />
</PHPCodeStyleSettings>
<PostgresCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</PostgresCodeStyleSettings>
<SQLiteCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</SQLiteCodeStyleSettings>
<SqlCodeStyleSettings version="6">
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="SELECT_ALIGN_AS" value="false" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_INDENT_THEN_ELSE" value="true" />
<option name="IMP_IF_THEN_INDENT_END" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
<option name="ALIGN_AS_IN_SELECT_STATEMENT" value="false" />
<option name="NEW_LINE_BEFORE_THEN" value="false" />
<option name="INDENT_SELECT_INTO_CLAUSE" value="true" />
</SqlCodeStyleSettings>
<SybaseCodeStyleSettings version="6">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
<option name="BUILT_IN_CASE" value="0" />
<option name="QUERY_TRUE_INDENT" value="false" />
<option name="QUERY_ALIGN_ELEMENTS" value="false" />
<option name="QUERY_ALIGN_LINE_COMMENTS" value="false" />
<option name="INSERT_EL_COMMA" value="2" />
<option name="SET_EL_WRAP" value="0" />
<option name="SET_EL_COMMA" value="0" />
<option name="WITH_EL_WRAP" value="0" />
<option name="WITH_EL_COMMA" value="0" />
<option name="SELECT_EL_WRAP" value="3" />
<option name="SELECT_EL_COMMA" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_EL_COMMA" value="2" />
<option name="FROM_PLACE_ON" value="10" />
<option name="WHERE_EL_WRAP" value="3" />
<option name="WHERE_EL_BOUND" value="2" />
<option name="ORDER_EL_COMMA" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_ALTER_INSTRUCTION_ALIGN" value="false" />
<option name="POST_OPT_WRAP_1" value="true" />
<option name="POST_OPT_ALIGN" value="false" />
<option name="ROUTINE_ARG_COMMA" value="2" />
<option name="ROUTINE_ARG_ALIGN_TYPES" value="true" />
<option name="IMP_DECLARE_EL_WRAP" value="1" />
<option name="IMP_IF_THEN_WRAP_THEN" value="true" />
<option name="CORTEGE_SPACE_BEFORE_L_PAREN" value="false" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</SybaseCodeStyleSettings>
<XML>
<option name="XML_ATTRIBUTE_WRAP" value="0" />
<option name="XML_TEXT_WRAP" value="0" />
<option name="XML_KEEP_WHITE_SPACES_INSIDE_CDATA" value="true" />
</XML>
<codeStyleSettings language="DB2">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Derby">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="H2">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HSQLDB">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="130" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_METHOD_LBRACE" value="false" />
<option name="SPACE_BEFORE_IF_LBRACE" value="false" />
<option name="SPACE_BEFORE_ELSE_LBRACE" value="false" />
<option name="SPACE_BEFORE_WHILE_LBRACE" value="false" />
<option name="SPACE_BEFORE_FOR_LBRACE" value="false" />
<option name="SPACE_BEFORE_DO_LBRACE" value="false" />
<option name="SPACE_BEFORE_SWITCH_LBRACE" value="false" />
<option name="SPACE_BEFORE_TRY_LBRACE" value="false" />
<option name="SPACE_BEFORE_CATCH_LBRACE" value="false" />
<option name="SPACE_BEFORE_FINALLY_LBRACE" value="false" />
<option name="SPACE_BEFORE_ELSE_KEYWORD" value="false" />
<option name="SPACE_BEFORE_WHILE_KEYWORD" value="false" />
<option name="SPACE_BEFORE_CATCH_KEYWORD" value="false" />
<option name="SPACE_BEFORE_FINALLY_KEYWORD" value="false" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Markdown">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="MySQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Oracle">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="BLANK_LINES_BEFORE_CLASS_END" value="1" />
<option name="CLASS_BRACE_STYLE" value="1" />
<option name="METHOD_BRACE_STYLE" value="1" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_CLASS_LBRACE" value="false" />
<option name="SPACE_BEFORE_METHOD_LBRACE" value="false" />
<option name="SPACE_BEFORE_IF_LBRACE" value="false" />
<option name="SPACE_BEFORE_ELSE_LBRACE" value="false" />
<option name="SPACE_BEFORE_WHILE_LBRACE" value="false" />
<option name="SPACE_BEFORE_FOR_LBRACE" value="false" />
<option name="SPACE_BEFORE_DO_LBRACE" value="false" />
<option name="SPACE_BEFORE_SWITCH_LBRACE" value="false" />
<option name="SPACE_BEFORE_TRY_LBRACE" value="false" />
<option name="SPACE_BEFORE_CATCH_LBRACE" value="false" />
<option name="SPACE_BEFORE_FINALLY_LBRACE" value="false" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="PARENTHESES_EXPRESSION_LPAREN_WRAP" value="true" />
<option name="PARENTHESES_EXPRESSION_RPAREN_WRAP" value="true" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_RPAREN_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<CONST />
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PUBLIC />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PROTECTED />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PRIVATE />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PUBLIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PROTECTED />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD />
<PRIVATE />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CONSTRUCTOR />
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PUBLIC />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PROTECTED />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PRIVATE />
<STATIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PUBLIC />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PROTECTED />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD />
<PRIVATE />
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<TRAIT />
</match>
</rule>
</section>
<section>
<rule>
<match>
<INTERFACE />
</match>
</rule>
</section>
<section>
<rule>
<match>
<CLASS />
</match>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="PostgreSQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SQLite">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Sybase">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TSQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,27 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JSAccessibilityCheck" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" />
<inspection_tool class="MessDetectorValidationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpCSValidationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpComposerExtensionStubsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpInternalEntityUsedInspection" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingDocCommentInspection" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="ENABLE_FOR_SLEEP" value="false" />
<option name="ENABLE_FOR_WAKEUP" value="false" />
</inspection_tool>
<inspection_tool class="PhpMultipleClassesDeclarationsInOneFile" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUsageOfSilenceOperatorInspection" enabled="true" level="STFU!" enabled_by_default="true" />
<inspection_tool class="PhpVoidFunctionResultUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -13,24 +13,24 @@ It also features a QR Code reader based on a [PHP port](https://github.com/khana
[![Packagist downloads][downloads-badge]][downloads] [![Packagist downloads][downloads-badge]][downloads]
[![Documentation][readthedocs-badge]][readthedocs] [![Documentation][readthedocs-badge]][readthedocs]
[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF [php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF&logoColor=fff
[php]: https://www.php.net/supported-versions.php [php]: https://www.php.net/supported-versions.php
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist [packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist&logoColor=fff
[packagist]: https://packagist.org/packages/chillerlan/php-qrcode [packagist]: https://packagist.org/packages/chillerlan/php-qrcode
[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/ci.yml?branch=v5.0.x&logo=github [gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/ci.yml?branch=v5.0.x&logo=github&logoColor=fff
[gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/ci.yml?query=branch%3Amain [gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/ci.yml?query=branch%3Amain
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v5.0.x?logo=codecov [coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v5.0.x?logo=codecov&logoColor=fff
[coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v5.0.x [coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v5.0.x
[codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v5.0.x?logo=codacy [codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v5.0.x?logo=codacy&logoColor=fff
[codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v5.0.x [codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v5.0.x
[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist [downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist&logoColor=fff
[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats [downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats
[readthedocs-badge]: https://img.shields.io/readthedocs/php-qrcode/v5.0.x?logo=readthedocs [readthedocs-badge]: https://img.shields.io/readthedocs/php-qrcode/v5.0.x?logo=readthedocs&logoColor=fff
[readthedocs]: https://php-qrcode.readthedocs.io/en/v5.0.x/ [readthedocs]: https://php-qrcode.readthedocs.io/en/v5.0.x/
## Overview # Overview
### Features ## Features
- Creation of [Model 2 QR Codes](https://www.qrcode.com/en/codes/model12.html), [Version 1 to 40](https://www.qrcode.com/en/about/version.html) - Creation of [Model 2 QR Codes](https://www.qrcode.com/en/codes/model12.html), [Version 1 to 40](https://www.qrcode.com/en/about/version.html)
- [ECC Levels](https://www.qrcode.com/en/about/error_correction.html) L/M/Q/H supported - [ECC Levels](https://www.qrcode.com/en/about/error_correction.html) L/M/Q/H supported
@@ -52,7 +52,7 @@ It also features a QR Code reader based on a [PHP port](https://github.com/khana
- QR Code reader (via GD and ImageMagick) - QR Code reader (via GD and ImageMagick)
### Requirements ## Requirements
- PHP 7.4+ - PHP 7.4+
- [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php) - [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php)
@@ -65,16 +65,21 @@ It also features a QR Code reader based on a [PHP port](https://github.com/khana
For the QRCode reader, either `ext-gd` or `ext-imagick` is required! For the QRCode reader, either `ext-gd` or `ext-imagick` is required!
## Documentation # Documentation
- The user manual is at https://php-qrcode.readthedocs.io/ ([sources](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs)) - The user manual is at https://php-qrcode.readthedocs.io/ ([sources](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs))
- An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/ - An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/
- The documentation for the `QROptions` container can be found here: [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container#readme) - The documentation for the `QROptions` container can be found here: [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container#readme)
**Important: Please use the examples from the branch that matches your installed php-qrcode version (
[v4.x](https://github.com/chillerlan/php-qrcode/tree/v4.3.x/examples),
[v5.x](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/examples),
[dev-main](https://github.com/chillerlan/php-qrcode/tree/main/examples)
)!**
## Installation with [composer](https://getcomposer.org) ## Installation with [composer](https://getcomposer.org)
See [the installation guide](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage-Installation.html) for more info! See [the installation guide](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Installation.html) for more info!
### Terminal ### Terminal
@@ -117,7 +122,7 @@ Also, have a look [in the examples folder](https://github.com/chillerlan/php-qrc
</p> </p>
### Reading QR Codes ## Reading QR Codes
Using the built-in QR Code reader is pretty straight-forward: Using the built-in QR Code reader is pretty straight-forward:
@@ -139,30 +144,30 @@ catch(Throwable $e){
``` ```
## Shameless advertising # Shameless advertising
Hi, please check out some of my other projects that are way cooler than qrcodes! Hi, please check out some of my other projects that are way cooler than qrcodes!
- [js-qrcode](https://github.com/chillerlan/js-qrcode) - a javascript port of this library - [js-qrcode](https://github.com/chillerlan/js-qrcode) - a javascript port of this library
- [php-authenticator](https://github.com/chillerlan/php-authenticator) - a Google Authenticator implementation (see [authenticator example](https://github.com/chillerlan/php-qrcode/blob/v5.0.x/examples/authenticator.php)) - [php-authenticator](https://github.com/chillerlan/php-authenticator) - a Google Authenticator implementation (see [authenticator example](https://github.com/chillerlan/php-qrcode/blob/v5.0.x/examples/authenticator.php))
- [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation - [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation
- [php-oauth-core](https://github.com/chillerlan/php-oauth-core) - an OAuth 1/2 client library along with a bunch of [providers](https://github.com/chillerlan/php-oauth-providers) - [php-oauth](https://github.com/chillerlan/php-oauth) - an OAuth 1/2 client library, fully PSR-7/PSR-17/PSR-18 compatible
- [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird - [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird
- [php-tootbot](https://github.com/php-tootbot/tootbot-template) - a Mastodon bot library (see [@dwil](https://github.com/php-tootbot/dwil)) - [php-tootbot](https://github.com/php-tootbot/tootbot-template) - a Mastodon bot library (see [@dwil](https://github.com/php-tootbot/dwil))
## Disclaimer! # Disclaimer!
I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk! I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk!
### License notice ## License notice
- Parts of this code are [ported to PHP](https://github.com/codemasher/php-qrcode-decoder) from the [ZXing project](https://github.com/zxing/zxing) and licensed under the [Apache License, Version 2.0](./NOTICE). - Parts of this code are [ported to PHP](https://github.com/codemasher/php-qrcode-decoder) from the [ZXing project](https://github.com/zxing/zxing) and licensed under the [Apache License, Version 2.0](./NOTICE).
- [The documentation](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs) is licensed under the [Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/). - [The documentation](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs) is licensed under the [Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/).
### Trademark Notice ## Trademark Notice
The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*<br> The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*<br>
https://www.qrcode.com/en/faq.html#patentH2Title https://www.qrcode.com/en/faq.html#patentH2Title

View File

@@ -1,6 +1,7 @@
{ {
"$schema": "https://getcomposer.org/schema.json",
"name": "chillerlan/php-qrcode", "name": "chillerlan/php-qrcode",
"description": "A QR code generator and reader with a user friendly API. PHP 7.4+", "description": "A QR Code generator and reader with a user-friendly API. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode", "homepage": "https://github.com/chillerlan/php-qrcode",
"license": [ "license": [
"MIT", "Apache-2.0" "MIT", "Apache-2.0"
@@ -32,6 +33,12 @@
"homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors" "homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors"
} }
], ],
"funding": [
{
"type": "Ko-Fi",
"url": "https://ko-fi.com/codemasher"
}
],
"support": { "support": {
"docs": "https://php-qrcode.readthedocs.io", "docs": "https://php-qrcode.readthedocs.io",
"issues": "https://github.com/chillerlan/php-qrcode/issues", "issues": "https://github.com/chillerlan/php-qrcode/issues",
@@ -42,15 +49,18 @@
"require": { "require": {
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
"ext-mbstring": "*", "ext-mbstring": "*",
"chillerlan/php-settings-container": "^2.1.4 || ^3.1" "chillerlan/php-settings-container": "^2.1.6 || ^3.2.1"
}, },
"require-dev": { "require-dev": {
"chillerlan/php-authenticator": "^4.1 || ^5.1", "ext-fileinfo": "*",
"phan/phan": "^5.4", "chillerlan/php-authenticator": "^4.3.1 || ^5.2.1",
"phan/phan": "^5.5.1",
"phpcompatibility/php-compatibility": "10.x-dev",
"phpunit/phpunit": "^9.6", "phpunit/phpunit": "^9.6",
"phpmd/phpmd": "^2.15", "phpmd/phpmd": "^2.15",
"setasign/fpdf": "^1.8.2", "setasign/fpdf": "^1.8.2",
"squizlabs/php_codesniffer": "^3.8" "slevomat/coding-standard": "^8.23.0",
"squizlabs/php_codesniffer": "^4.0.0"
}, },
"suggest": { "suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
@@ -59,21 +69,26 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"chillerlan\\QRCode\\": "src/" "chillerlan\\QRCode\\": "src"
} }
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"chillerlan\\QRCodeTest\\": "tests/" "chillerlan\\QRCodeTest\\": "tests"
} }
}, },
"scripts": { "scripts": {
"phpunit": "@php vendor/bin/phpunit", "phan": "@php vendor/bin/phan",
"phan": "@php vendor/bin/phan" "phpcs": "@php vendor/bin/phpcs",
"phpmd": "@php vendor/bin/phpmd src text ./phpmd.xml.dist",
"phpunit": "@php vendor/bin/phpunit"
}, },
"config": { "config": {
"lock": false, "lock": false,
"sort-packages": true, "sort-packages": true,
"platform-check": true "platform-check": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
} }
} }

View File

@@ -38,7 +38,7 @@ class GDLuminanceSource extends LuminanceSourceAbstract{
* *
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
*/ */
public function __construct($gdImage, SettingsContainerInterface $options = null){ public function __construct($gdImage, ?SettingsContainerInterface $options = null){
/** @noinspection PhpFullyQualifiedNameUsageInspection */ /** @noinspection PhpFullyQualifiedNameUsageInspection */
if( if(
@@ -85,12 +85,12 @@ class GDLuminanceSource extends LuminanceSourceAbstract{
} }
/** @inheritDoc */ /** @inheritDoc */
public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options); return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options);
} }
/** @inheritDoc */ /** @inheritDoc */
public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
return new self(imagecreatefromstring($blob), $options); return new self(imagecreatefromstring($blob), $options);
} }

View File

@@ -35,7 +35,7 @@ final class GenericGFPoly{
* @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this * @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this
* is not a constant polynomial (that is, it is not the monomial "0") * is not a constant polynomial (that is, it is not the monomial "0")
*/ */
public function __construct(array $coefficients, int $degree = null){ public function __construct(array $coefficients, ?int $degree = null){
$degree ??= 0; $degree ??= 0;
if(empty($coefficients)){ if(empty($coefficients)){

View File

@@ -28,7 +28,7 @@ class IMagickLuminanceSource extends LuminanceSourceAbstract{
/** /**
* IMagickLuminanceSource constructor. * IMagickLuminanceSource constructor.
*/ */
public function __construct(Imagick $imagick, SettingsContainerInterface $options = null){ public function __construct(Imagick $imagick, ?SettingsContainerInterface $options = null){
parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options); parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options);
$this->imagick = $imagick; $this->imagick = $imagick;
@@ -63,12 +63,12 @@ class IMagickLuminanceSource extends LuminanceSourceAbstract{
} }
/** @inheritDoc */ /** @inheritDoc */
public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
return new self(new Imagick(self::checkFile($path)), $options); return new self(new Imagick(self::checkFile($path)), $options);
} }
/** @inheritDoc */ /** @inheritDoc */
public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
$im = new Imagick; $im = new Imagick;
$im->readImageBlob($blob); $im->readImageBlob($blob);

View File

@@ -34,7 +34,7 @@ abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
/** /**
* *
*/ */
public function __construct(int $width, int $height, SettingsContainerInterface $options = null){ public function __construct(int $width, int $height, ?SettingsContainerInterface $options = null){
$this->width = $width; $this->width = $width;
$this->height = $height; $this->height = $height;
$this->options = ($options ?? new QROptions); $this->options = ($options ?? new QROptions);
@@ -57,7 +57,10 @@ abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
return $this->height; return $this->height;
} }
/** @inheritDoc */ /**
* @inheritDoc
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
*/
public function getRow(int $y):array{ public function getRow(int $y):array{
if($y < 0 || $y >= $this->getHeight()){ if($y < 0 || $y >= $this->getHeight()){

View File

@@ -77,7 +77,7 @@ final class MaskPattern{
*/ */
public function __construct(int $maskPattern){ public function __construct(int $maskPattern){
if((0b111 & $maskPattern) !== $maskPattern){ if(($maskPattern & 0b111) !== $maskPattern){
throw new QRCodeException('invalid mask pattern'); throw new QRCodeException('invalid mask pattern');
} }

View File

@@ -11,7 +11,7 @@
namespace chillerlan\QRCode\Data; namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, Mode}; use chillerlan\QRCode\Common\{BitBuffer, Mode};
use function array_flip, ceil, intdiv, str_split; use function ceil, intdiv, preg_match, strpos;
/** /**
* Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
@@ -24,16 +24,9 @@ final class AlphaNum extends QRDataModeAbstract{
/** /**
* ISO/IEC 18004:2000 Table 5 * ISO/IEC 18004:2000 Table 5
* *
* @var int[] * @var string
*/ */
private const CHAR_TO_ORD = [ private const CHAR_MAP = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7,
'8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15,
'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23,
'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31,
'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39,
'+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44,
];
/** /**
* @inheritDoc * @inheritDoc
@@ -51,18 +44,7 @@ final class AlphaNum extends QRDataModeAbstract{
* @inheritDoc * @inheritDoc
*/ */
public static function validateString(string $string):bool{ public static function validateString(string $string):bool{
return (bool)preg_match('/^[A-Z\d %$*+\-.:\/]+$/', $string);
if($string === ''){
return false;
}
foreach(str_split($string) as $chr){
if(!isset(self::CHAR_TO_ORD[$chr])){
return false;
}
}
return true;
} }
/** /**
@@ -78,12 +60,15 @@ final class AlphaNum extends QRDataModeAbstract{
// encode 2 characters in 11 bits // encode 2 characters in 11 bits
for($i = 0; ($i + 1) < $len; $i += 2){ for($i = 0; ($i + 1) < $len; $i += 2){
$bitBuffer->put((self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]), 11); $bitBuffer->put(
($this->ord($this->data[$i]) * 45 + $this->ord($this->data[($i + 1)])),
11,
);
} }
// encode a remaining character in 6 bits // encode a remaining character in 6 bits
if($i < $len){ if($i < $len){
$bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]], 6); $bitBuffer->put($this->ord($this->data[$i]), 6);
} }
return $this; return $this;
@@ -95,19 +80,7 @@ final class AlphaNum extends QRDataModeAbstract{
* @throws \chillerlan\QRCode\Data\QRCodeDataException * @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
$length = $bitBuffer->read(self::getLengthBits($versionNumber)); $length = $bitBuffer->read(self::getLengthBits($versionNumber));
$charmap = array_flip(self::CHAR_TO_ORD);
// @todo
$toAlphaNumericChar = function(int $ord) use ($charmap):string{
if(isset($charmap[$ord])){
return $charmap[$ord];
}
throw new QRCodeDataException('invalid character value: '.$ord);
};
$result = ''; $result = '';
// Read two characters at a time // Read two characters at a time
while($length > 1){ while($length > 1){
@@ -116,9 +89,9 @@ final class AlphaNum extends QRDataModeAbstract{
throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
} }
$nextTwoCharsBits = $bitBuffer->read(11); $nextTwoCharsBits = $bitBuffer->read(11);
$result .= $toAlphaNumericChar(intdiv($nextTwoCharsBits, 45)); $result .= self::chr(intdiv($nextTwoCharsBits, 45));
$result .= $toAlphaNumericChar($nextTwoCharsBits % 45); $result .= self::chr($nextTwoCharsBits % 45);
$length -= 2; $length -= 2;
} }
@@ -128,10 +101,36 @@ final class AlphaNum extends QRDataModeAbstract{
throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
} }
$result .= $toAlphaNumericChar($bitBuffer->read(6)); $result .= self::chr($bitBuffer->read(6));
} }
return $result; return $result;
} }
/**
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
private function ord(string $chr):int{
/** @phan-suppress-next-line PhanParamSuspiciousOrder */
$ord = strpos(self::CHAR_MAP, $chr);
if($ord === false){
throw new QRCodeDataException('invalid character'); // @codeCoverageIgnore
}
return $ord;
}
/**
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
private static function chr(int $ord):string{
if($ord < 0 || $ord > 44){
throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore
}
return self::CHAR_MAP[$ord];
}
} }

View File

@@ -34,6 +34,7 @@ final class ECI extends QRDataModeAbstract{
/** /**
* @inheritDoc * @inheritDoc
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @noinspection PhpMissingParentConstructorInspection * @noinspection PhpMissingParentConstructorInspection
*/ */
public function __construct(int $encoding){ public function __construct(int $encoding){
@@ -107,7 +108,7 @@ final class ECI extends QRDataModeAbstract{
$id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16)); $id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16));
} }
else{ else{
throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte)); // @codeCoverageIgnore throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte));// @codeCoverageIgnore
} }
return new ECICharset($id); return new ECICharset($id);
@@ -128,17 +129,12 @@ final class ECI extends QRDataModeAbstract{
public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
$eciCharset = self::parseValue($bitBuffer); $eciCharset = self::parseValue($bitBuffer);
$nextMode = $bitBuffer->read(4); $nextMode = $bitBuffer->read(4);
$data = self::decodeModeSegment($nextMode, $bitBuffer, $versionNumber);
if($nextMode !== Mode::BYTE){ $encoding = $eciCharset->getName();
throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $nextMode));
}
$data = Byte::decodeSegment($bitBuffer, $versionNumber);
$encoding = $eciCharset->getName();
if($encoding === null){ if($encoding === null){
// The spec isn't clear on this mode; see // The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming // section 6.4.5: it does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as // upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to // Shift_JIS -- without anything like an ECI designator to
// give a hint. // give a hint.
@@ -152,4 +148,18 @@ final class ECI extends QRDataModeAbstract{
return mb_convert_encoding($data, mb_internal_encoding(), $encoding); return mb_convert_encoding($data, mb_internal_encoding(), $encoding);
} }
/**
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
private static function decodeModeSegment(int $mode, BitBuffer $bitBuffer, int $versionNumber):string{
switch(true){
case $mode === Mode::NUMBER: return Number::decodeSegment($bitBuffer, $versionNumber);
case $mode === Mode::ALPHANUM: return AlphaNum::decodeSegment($bitBuffer, $versionNumber);
case $mode === Mode::BYTE: return Byte::decodeSegment($bitBuffer, $versionNumber);
}
throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $mode));
}
} }

View File

@@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, Mode}; use chillerlan\QRCode\Common\{BitBuffer, Mode};
use Throwable; use Throwable;
use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding,
mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; mb_internal_encoding, mb_strlen, ord, sprintf, strlen;
/** /**
* Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set * Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set
@@ -64,11 +64,15 @@ final class Hanzi extends QRDataModeAbstract{
/** /**
* @inheritDoc * @inheritDoc
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public static function convertEncoding(string $string):string{ public static function convertEncoding(string $string):string{
mb_detect_order([mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ']);
$detected = mb_detect_encoding($string, null, true); $detected = mb_detect_encoding(
$string,
[mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ'],
true,
);
if($detected === false){ if($detected === false){
throw new QRCodeDataException('mb_detect_encoding error'); throw new QRCodeDataException('mb_detect_encoding error');
@@ -199,7 +203,7 @@ final class Hanzi extends QRDataModeAbstract{
$length--; $length--;
} }
return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); return mb_convert_encoding(implode('', $buffer), mb_internal_encoding(), self::ENCODING);
} }
} }

View File

@@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, Mode}; use chillerlan\QRCode\Common\{BitBuffer, Mode};
use Throwable; use Throwable;
use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding,
mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; mb_internal_encoding, mb_strlen, ord, sprintf, strlen;
/** /**
* Kanji mode: 13-bit double-byte characters from the Shift-JIS character set * Kanji mode: 13-bit double-byte characters from the Shift-JIS character set
@@ -57,11 +57,10 @@ final class Kanji extends QRDataModeAbstract{
/** /**
* @inheritDoc * @inheritDoc
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public static function convertEncoding(string $string):string{ public static function convertEncoding(string $string):string{
mb_detect_order([mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004']); $detected = mb_detect_encoding($string, [mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004'], true);
$detected = mb_detect_encoding($string, null, true);
if($detected === false){ if($detected === false){
throw new QRCodeDataException('mb_detect_encoding error'); throw new QRCodeDataException('mb_detect_encoding error');
@@ -185,7 +184,7 @@ final class Kanji extends QRDataModeAbstract{
$length--; $length--;
} }
return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); return mb_convert_encoding(implode('', $buffer), mb_internal_encoding(), self::ENCODING);
} }
} }

View File

@@ -11,7 +11,7 @@
namespace chillerlan\QRCode\Data; namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, Mode}; use chillerlan\QRCode\Common\{BitBuffer, Mode};
use function array_flip, ceil, intdiv, str_split, substr, unpack; use function ceil, intdiv, substr, unpack;
/** /**
* Numeric mode: decimal digits 0 to 9 * Numeric mode: decimal digits 0 to 9
@@ -21,13 +21,6 @@ use function array_flip, ceil, intdiv, str_split, substr, unpack;
*/ */
final class Number extends QRDataModeAbstract{ final class Number extends QRDataModeAbstract{
/**
* @var int[]
*/
private const NUMBER_TO_ORD = [
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
];
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -44,18 +37,7 @@ final class Number extends QRDataModeAbstract{
* @inheritDoc * @inheritDoc
*/ */
public static function validateString(string $string):bool{ public static function validateString(string $string):bool{
return (bool)preg_match('/^\d+$/', $string);
if($string === ''){
return false;
}
foreach(str_split($string) as $chr){
if(!isset(self::NUMBER_TO_ORD[$chr])){
return false;
}
}
return true;
} }
/** /**
@@ -95,12 +77,20 @@ final class Number extends QRDataModeAbstract{
/** /**
* get the code for the given numeric string * get the code for the given numeric string
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
private function parseInt(string $string):int{ private function parseInt(string $string):int{
$num = 0; $num = 0;
foreach(unpack('C*', $string) as $chr){ $ords = unpack('C*', $string);
$num = ($num * 10 + $chr - 48);
if($ords === false){
throw new QRCodeDataException('unpack() error');
}
foreach($ords as $ord){
$num = ($num * 10 + $ord - 48);
} }
return $num; return $num;
@@ -112,19 +102,7 @@ final class Number extends QRDataModeAbstract{
* @throws \chillerlan\QRCode\Data\QRCodeDataException * @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
$length = $bitBuffer->read(self::getLengthBits($versionNumber)); $length = $bitBuffer->read(self::getLengthBits($versionNumber));
$charmap = array_flip(self::NUMBER_TO_ORD);
// @todo
$toNumericChar = function(int $ord) use ($charmap):string{
if(isset($charmap[$ord])){
return $charmap[$ord];
}
throw new QRCodeDataException('invalid character value: '.$ord);
};
$result = ''; $result = '';
// Read three digits at a time // Read three digits at a time
while($length >= 3){ while($length >= 3){
@@ -139,9 +117,9 @@ final class Number extends QRDataModeAbstract{
throw new QRCodeDataException('error decoding numeric value'); throw new QRCodeDataException('error decoding numeric value');
} }
$result .= $toNumericChar(intdiv($threeDigitsBits, 100)); $result .= intdiv($threeDigitsBits, 100);
$result .= $toNumericChar(intdiv($threeDigitsBits, 10) % 10); $result .= (intdiv($threeDigitsBits, 10) % 10);
$result .= $toNumericChar($threeDigitsBits % 10); $result .= ($threeDigitsBits % 10);
$length -= 3; $length -= 3;
} }
@@ -158,8 +136,8 @@ final class Number extends QRDataModeAbstract{
throw new QRCodeDataException('error decoding numeric value'); throw new QRCodeDataException('error decoding numeric value');
} }
$result .= $toNumericChar(intdiv($twoDigitsBits, 10)); $result .= intdiv($twoDigitsBits, 10);
$result .= $toNumericChar($twoDigitsBits % 10); $result .= ($twoDigitsBits % 10);
} }
elseif($length === 1){ elseif($length === 1){
// One digit left over to read // One digit left over to read
@@ -173,7 +151,7 @@ final class Number extends QRDataModeAbstract{
throw new QRCodeDataException('error decoding numeric value'); throw new QRCodeDataException('error decoding numeric value');
} }
$result .= $toNumericChar($digitBits); $result .= $digitBits;
} }
return $result; return $result;

View File

@@ -195,7 +195,7 @@ final class QRData{
// guess the version number within the given range // guess the version number within the given range
for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){ for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){
if($total <= $this->maxBitsForEcc[$version]){ if($total <= ($this->maxBitsForEcc[$version] - 4)){
return new Version($version); return new Version($version);
} }
} }
@@ -207,7 +207,7 @@ final class QRData{
/** /**
* creates a BitBuffer and writes the string data to it * creates a BitBuffer and writes the string data to it
* *
* @throws \chillerlan\QRCode\QRCodeException on data overflow * @throws \chillerlan\QRCode\Data\QRCodeDataException on data overflow
*/ */
private function writeBitBuffer():void{ private function writeBitBuffer():void{
$MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version); $MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version);

View File

@@ -175,7 +175,7 @@ class QRMatrix{
* *
* @return int[][]|bool[][] * @return int[][]|bool[][]
*/ */
public function getMatrix(bool $boolean = null):array{ public function getMatrix(?bool $boolean = null):array{
if($boolean !== true){ if($boolean !== true){
return $this->matrix; return $this->matrix;
@@ -195,7 +195,7 @@ class QRMatrix{
* @see \chillerlan\QRCode\Data\QRMatrix::getMatrix() * @see \chillerlan\QRCode\Data\QRMatrix::getMatrix()
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function matrix(bool $boolean = null):array{ public function matrix(?bool $boolean = null):array{
return $this->getMatrix($boolean); return $this->getMatrix($boolean);
} }
@@ -387,7 +387,7 @@ class QRMatrix{
* 7 # 3 * 7 # 3
* 6 5 4 * 6 5 4
*/ */
public function checkNeighbours(int $x, int $y, int $M_TYPE = null):int{ public function checkNeighbours(int $x, int $y, ?int $M_TYPE = null):int{
$bits = 0; $bits = 0;
foreach($this::neighbours as $bit => [$ix, $iy]){ foreach($this::neighbours as $bit => [$ix, $iy]){
@@ -463,6 +463,7 @@ class QRMatrix{
for($c = 0; $c < 3; $c++){ for($c = 0; $c < 3; $c++){
for($i = 0; $i < 8; $i++){ for($i = 0; $i < 8; $i++){
// phpcs:ignore
$this->set( $h[$c][0] , ($h[$c][1] + $i), false, $this::M_SEPARATOR); $this->set( $h[$c][0] , ($h[$c][1] + $i), false, $this::M_SEPARATOR);
$this->set(($v[$c][0] - $i), $v[$c][1] , false, $this::M_SEPARATOR); $this->set(($v[$c][0] - $i), $v[$c][1] , false, $this::M_SEPARATOR);
} }
@@ -552,7 +553,7 @@ class QRMatrix{
* *
* ISO/IEC 18004:2000 Section 8.9 * ISO/IEC 18004:2000 Section 8.9
*/ */
public function setFormatInfo(MaskPattern $maskPattern = null):self{ public function setFormatInfo(?MaskPattern $maskPattern = null):self{
$this->maskPattern = $maskPattern; $this->maskPattern = $maskPattern;
$bits = 0; // sets all format fields to false (test mode) $bits = 0; // sets all format fields to false (test mode)
@@ -678,7 +679,7 @@ class QRMatrix{
* *
* @throws \chillerlan\QRCode\Data\QRCodeDataException * @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ public function setLogoSpace(int $width, ?int $height = null, ?int $startX = null, ?int $startY = null):self{
$height ??= $width; $height ??= $width;
// if width and height happen to be negative or 0 (default value), just return - nothing to do // if width and height happen to be negative or 0 (default value), just return - nothing to do

View File

@@ -415,7 +415,7 @@ final class BitMatrix extends QRMatrix{
* @codeCoverageIgnore * @codeCoverageIgnore
* @throws \chillerlan\QRCode\Data\QRCodeDataException * @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public function setQuietZone(int $quietZoneSize = null):self{ public function setQuietZone(?int $quietZoneSize = null):self{
throw new QRCodeDataException('not supported'); throw new QRCodeDataException('not supported');
} }
@@ -423,7 +423,7 @@ final class BitMatrix extends QRMatrix{
* @codeCoverageIgnore * @codeCoverageIgnore
* @throws \chillerlan\QRCode\Data\QRCodeDataException * @throws \chillerlan\QRCode\Data\QRCodeDataException
*/ */
public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ public function setLogoSpace(int $width, ?int $height = null, ?int $startX = null, ?int $startY = null):self{
throw new QRCodeDataException('not supported'); throw new QRCodeDataException('not supported');
} }

View File

@@ -29,6 +29,7 @@ final class Decoder{
private ?EccLevel $eccLevel = null; private ?EccLevel $eccLevel = null;
private ?MaskPattern $maskPattern = null; private ?MaskPattern $maskPattern = null;
private BitBuffer $bitBuffer; private BitBuffer $bitBuffer;
private Detector $detector;
/** /**
* Decodes a QR Code represented as a BitMatrix. * Decodes a QR Code represented as a BitMatrix.
@@ -37,7 +38,8 @@ final class Decoder{
* @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException
*/ */
public function decode(LuminanceSourceInterface $source):DecoderResult{ public function decode(LuminanceSourceInterface $source):DecoderResult{
$matrix = (new Detector($source))->detect(); $this->detector = new Detector($source);
$matrix = $this->detector->detect();
try{ try{
// clone the BitMatrix to avoid errors in case we run into mirroring // clone the BitMatrix to avoid errors in case we run into mirroring
@@ -148,6 +150,7 @@ final class Decoder{
'data' => $result, 'data' => $result,
'version' => $this->version, 'version' => $this->version,
'eccLevel' => $this->eccLevel, 'eccLevel' => $this->eccLevel,
'finderPatterns' => $this->detector->getFinderPatterns(),
'maskPattern' => $this->maskPattern, 'maskPattern' => $this->maskPattern,
'structuredAppendParity' => $parityData, 'structuredAppendParity' => $parityData,
'structuredAppendSequence' => $symbolSequence, 'structuredAppendSequence' => $symbolSequence,

View File

@@ -20,13 +20,14 @@ use function property_exists;
* applies to 2D barcode formats. For now, it contains the raw bytes obtained * applies to 2D barcode formats. For now, it contains the raw bytes obtained
* as well as a String interpretation of those bytes, if applicable. * as well as a String interpretation of those bytes, if applicable.
* *
* @property \chillerlan\QRCode\Common\BitBuffer $rawBytes * @property \chillerlan\QRCode\Common\BitBuffer $rawBytes
* @property string $data * @property string $data
* @property \chillerlan\QRCode\Common\Version $version * @property \chillerlan\QRCode\Common\Version $version
* @property \chillerlan\QRCode\Common\EccLevel $eccLevel * @property \chillerlan\QRCode\Common\EccLevel $eccLevel
* @property \chillerlan\QRCode\Common\MaskPattern $maskPattern * @property \chillerlan\QRCode\Common\MaskPattern $maskPattern
* @property int $structuredAppendParity * @property int $structuredAppendParity
* @property int $structuredAppendSequence * @property int $structuredAppendSequence
* @property \chillerlan\QRCode\Detector\FinderPattern[] $finderPatterns
*/ */
final class DecoderResult{ final class DecoderResult{
@@ -37,11 +38,13 @@ final class DecoderResult{
private string $data = ''; private string $data = '';
private int $structuredAppendParity = -1; private int $structuredAppendParity = -1;
private int $structuredAppendSequence = -1; private int $structuredAppendSequence = -1;
/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
private array $finderPatterns = [];
/** /**
* DecoderResult constructor. * DecoderResult constructor.
*/ */
public function __construct(iterable $properties = null){ public function __construct(?iterable $properties = null){
if(!empty($properties)){ if(!empty($properties)){

View File

@@ -94,7 +94,7 @@ final class ReedSolomonDecoder{
while($longerBlocksStartAt >= 0){ while($longerBlocksStartAt >= 0){
$numCodewords = count($result[$longerBlocksStartAt][1]); $numCodewords = count($result[$longerBlocksStartAt][1]);
if($numCodewords == $shorterBlocksTotalCodewords){ if($numCodewords === $shorterBlocksTotalCodewords){
break; break;
} }

View File

@@ -256,7 +256,7 @@ final class AlignmentPatternFinder{
$i++; $i++;
} }
if($i == $maxI || $stateCount[1] > $maxCount){ if($i === $maxI || $stateCount[1] > $maxCount){
return null; return null;
} }
@@ -269,6 +269,7 @@ final class AlignmentPatternFinder{
return null; return null;
} }
// phpcs:ignore
if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){ if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){
return null; return null;
} }

View File

@@ -25,6 +25,8 @@ use const NAN;
final class Detector{ final class Detector{
private BitMatrix $matrix; private BitMatrix $matrix;
/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
private array $finderPatterns = [];
/** /**
* Detector constructor. * Detector constructor.
@@ -33,11 +35,20 @@ final class Detector{
$this->matrix = (new Binarizer($source))->getBlackMatrix(); $this->matrix = (new Binarizer($source))->getBlackMatrix();
} }
/**
* @return \chillerlan\QRCode\Detector\FinderPattern[]
*/
public function getFinderPatterns():array{
return $this->finderPatterns;
}
/** /**
* Detects a QR Code in an image. * Detects a QR Code in an image.
*/ */
public function detect():BitMatrix{ public function detect():BitMatrix{
[$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find(); $this->finderPatterns = (new FinderPatternFinder($this->matrix))->find();
[$bottomLeft, $topLeft, $topRight] = $this->finderPatterns;
$moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft); $moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
$dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize); $dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
@@ -305,11 +316,11 @@ final class Detector{
* *
*/ */
private function createTransform( private function createTransform(
FinderPattern $nw, FinderPattern $nw,
FinderPattern $ne, FinderPattern $ne,
FinderPattern $sw, FinderPattern $sw,
int $size, int $size,
AlignmentPattern $ap = null ?AlignmentPattern $ap = null
):PerspectiveTransform{ ):PerspectiveTransform{
$dimMinusThree = ($size - 3.5); $dimMinusThree = ($size - 3.5);

View File

@@ -27,7 +27,7 @@ final class FinderPattern extends ResultPoint{
/** /**
* *
*/ */
public function __construct(float $posX, float $posY, float $estimatedModuleSize, int $count = null){ public function __construct(float $posX, float $posY, float $estimatedModuleSize, ?int $count = null){
parent::__construct($posX, $posY, $estimatedModuleSize); parent::__construct($posX, $posY, $estimatedModuleSize);
$this->count = ($count ?? 1); $this->count = ($count ?? 1);

View File

@@ -290,11 +290,13 @@ final class FinderPatternFinder{
// Now also count down, right from center // Now also count down, right from center
$i = 1; $i = 1;
// phpcs:ignore
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
$stateCount[2]++; $stateCount[2]++;
$i++; $i++;
} }
// phpcs:ignore
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){
$stateCount[3]++; $stateCount[3]++;
$i++; $i++;
@@ -304,6 +306,7 @@ final class FinderPatternFinder{
return false; return false;
} }
// phpcs:ignore
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
$stateCount[4]++; $stateCount[4]++;
$i++; $i++;

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