färbige Artikeldarstellung

Antwort erstellen

Bestätigungscode
Gib den Code genau so ein, wie du ihn siehst; Groß- und Kleinschreibung wird nicht unterschieden.
Smileys
:D :) ;) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :!: :?: :idea: :arrow: :| :mrgreen: :geek: :ugeek:

BBCode ist eingeschaltet
[img] ist eingeschaltet
[url] ist eingeschaltet
Smileys sind eingeschaltet

Die letzten Beiträge des Themas
   

Ansicht erweitern Die letzten Beiträge des Themas: färbige Artikeldarstellung

Re: färbige Artikeldarstellung

von Punkt6 » Fr 17. Jul 2026, 13:47

Hallo,

du hast den Patch noch nicht ausgeführt, sondern nur die "Vorschau". Da siehst du, welche Dateien bearbeitet werden und es wird auch von jeder Datei vor Änderung ein Backup angelegt.

Wenn du die Änderung einspeilen willst musst du http://[server]/patch_article_colors.php?apply=1 in den Broswer eintragen. [Server] mit deiner Server-IP-Adresse oder Servernamen ersetzen und der Patch ist aktiv.

Wenn Stefan die färbige Darstellung einbaut, ist das auch für die zukünftigen Versionen kein Problem, ansonsten werde ich hier die aktualisierten Codes zur Verfügung stellen. Ich befürchte, unsere Kellner beim Feuerwehrfest, werden ohne diese Darstellung nicht mehr arbeiten :lol:

Gruß Thomas

Re: färbige Artikeldarstellung

von pichel » Di 14. Jul 2026, 21:15

Hallo,

ich freue mich immer, wenn ich sehe, wie meine Software verwendet wird und wenn ich sehe, wieviel Energie Leute investieren, sie für sich noch besser zu machen.

Auf die Frage zur Kompatibilität mit zukünftigen Versionen möchte ich gerne kurz eingehen. Ich programmiere aktuell an der Version 3.0.0. Diese Version hat nicht ohne Grund eine neue Major-Nummer bekommen: Sie wird nicht mehr komplett abwärtskompatibel sein. Grund sind massive Umbauten in der internen Datenstruktur, die notwendig sind, um die Anforderungen an einen rechtssicheren DSFinvk-Export zu erfüllen. Es wird zwar eine Updatemöglichkeit geben, um die bestehende Konfiguration zu übernehmen, aber vor einem Update müssen Benutzer den DSFinv-k Export sichern, denn die neue Version macht erstellt den Export auf eine gänzlich neue Weise. Und da ich sowieso schon diesen Bruch habe, habe ich auch an anderen Stellen ein umfängliches Refactoring vorgenommen, z.B. bei den Druckvorlagen, den csv-Exports und auch beim Layout des User Interface.

Ich habe mir die Änderungen für die Farben angeschaut und so wie ich es einschätze, würden sie beim jetzigen Stand von 3.0.0 noch funktionieren (ich habs nicht wirklich getestet). Aber ich kann echt nicht versprechen, dass das auch nach Fertigstellung von 3.0.0 noch der Fall sein wird. Vielleicht baue ich die Artikelfarben auch selbst in die 3.0.0 noch ein. Selbst unabh. von den breaking changes in V3 ist es schwierig, wenn Änderungen von Benutzern in neue Versionen übernommen werden sollen: wenn sie einmal DB-Schemata geändert haben, kann das den Update-Prozess schnell korrumpieren.

Die Fertigstellung der 3.0.0 wird aber sowieso noch etwas dauern. Nicht, dass die Änderungen so unheimlich schwer sind, aber aufgrund der wenigen verfügbaren Zeit komme ich gerade kaum vom Fleck.

Gruß,

Stefan

Re: färbige Artikeldarstellung

von okto2026 » Di 14. Jul 2026, 11:06

Hallo punkt 6, das ist eine Super-Sache.
da ich Laie bin habe ich jetzt versucht das entsprechend einzubauen.
Hat auch alles soweit geklappt, Binn mir aber jetzt nicht mehr sicher alls richtig gemacht zu haben bzw. was muss ich jetzt noch machen.
Wir haben die Ordersprinter Version 2.9.12 und wie verhält sich dieser Patch wenn wir später auf die neue Version 3 updaten

OrderSprinter – Patcher 'Artikelfarben' [VORSCHAU]
================================================================

[DB] Spalte `color` in der products-Tabelle
~ wuerde hinzugefuegt: ALTER TABLE `ordersprinter_products` ADD `color` VARCHAR(7) NULL

[php/dbutils.php]
~ $prodCols: Farb-Definition: wuerde ergaenzt.
! $prodCols: color aus Historie ausnehmen (hist=0): Anker NICHT gefunden (andere Version?) – manuell pruefen.

[php/products.php]
~ $proddefs: color "get"-Spalte: wuerde ergaenzt.
~ Bestellliste SELECT (Standard): wuerde ergaenzt.
~ Bestellliste SELECT (Favoriten/UNION): wuerde ergaenzt.
~ JSON: color ans Frontend: wuerde ergaenzt.

[elements/productgui.js]
~ Editor: Colorpicker: wuerde eingefuegt.
! Editor: Colorpicker jQM-Fix (data-role=none): Anker NICHT gefunden (andere Version?) – manuell pruefen.
~ Admin-Liste: color-Parameter: wuerde ergaenzt.
~ Admin-Liste: Farb-Punkt im Header: wuerde ergaenzt.
~ Admin-Liste: color durchreichen (Aufruf): wuerde ergaenzt.

[waiterdesktop.php]
~ Desktop-Button (ohne Bild): wuerde ergaenzt.
~ Desktop-Button (mit Bild, Rahmen): wuerde ergaenzt.
~ Desktop: color durchreichen (getAllProds): wuerde ergaenzt.

[waiter.html]
~ Handheld _renderProdEntry: wuerde ergaenzt.
~ Handheld: color durchreichen (Liste): wuerde ergaenzt.
~ Handheld: color durchreichen (Favoriten): wuerde ergaenzt.

================================================================
Bereits vorhanden: 0 Ergaenzt: 0 Offen: 16 Anker fehlt: 2
Vorschau-Modus. Zum Anwenden: php patch_article_colors.php --apply (bzw. ?apply=1)
Hinweis: 'Anker fehlt' = diese Stelle weicht von der erwarteten Version ab; bitte manuell ergaenzen.
.

Re: färbige Artikeldarstellung

von Punkt6 » Di 30. Jun 2026, 19:08

Hallo,
ich habe jetzt noch die Admin Artikeverwaltung mit den Farben erweitert.
Damit man auch bei der Artikelanlage/Verwaltung sieht, ob eine Farbe und welche Farbe zugewiesen wurde.

Code: Alles auswählen

<?php
/**
 * OrderSprinter – Erweiterung "Artikelfarben"
 * ------------------------------------------------------------
 * Idempotenter Patcher. Prueft fuer JEDE Einzelaenderung, ob sie bereits
 * (ganz oder teilweise) vorhanden ist, und ergaenzt nur Fehlendes.
 * Mehrfaches Ausfuehren ist gefahrlos.
 *
 * ABLEGEN im webapp-Rootverzeichnis (dort wo waiter.html / waiterdesktop.php liegen).
 *
 * AUFRUF
 *   Vorschau (nichts wird geaendert):
 *       php patch_article_colors.php
 *   Anwenden:
 *       php patch_article_colors.php --apply
 *   Im Browser:  patch_article_colors.php          (Vorschau)
 *                patch_article_colors.php?apply=1   (Anwenden)
 *
 * Vor jeder ersten Aenderung einer Datei wird eine Sicherung <datei>.colorbak angelegt.
 */

error_reporting(E_ALL);
$APPLY = (PHP_SAPI === 'cli' && in_array('--apply', $argv, true)) || isset($_GET['apply']);
$isCli = (PHP_SAPI === 'cli');
if (!$isCli) { header('Content-Type: text/plain; charset=utf-8'); }
$BASE  = __DIR__;

function out($s) { echo $s . "\n"; }
out("OrderSprinter – Patcher 'Artikelfarben'   [" . ($APPLY ? "ANWENDEN" : "VORSCHAU") . "]");
out(str_repeat('=', 64));

$counters = ['already' => 0, 'applied' => 0, 'todo' => 0, 'missing' => 0];

/* ----------------------------------------------------------------
 * Schritt 1: Datenbankspalte  products.color
 * ---------------------------------------------------------------- */
out("\n[DB] Spalte `color` in der products-Tabelle");
$cfg = $BASE . '/php/config.php';
if (!is_file($cfg)) {
    out("   ! php/config.php nicht gefunden – DB-Schritt uebersprungen.");
} else {
    require_once $cfg;
    $pdo = null;
    $port = defined('MYSQL_PORT') ? MYSQL_PORT : 3306;
    // Verbindung versuchen; bei 'localhost' (Socket) zusaetzlich TCP-Fallback 127.0.0.1,
    // weil CLI- und Webserver-PHP oft unterschiedliche Sockets nutzen.
    $hosts = [MYSQL_HOST];
    if (strtolower(MYSQL_HOST) === 'localhost') { $hosts[] = '127.0.0.1'; }
    $connErr = '';
    foreach ($hosts as $h) {
        try {
            $dsn = 'mysql:host=' . $h . ';port=' . $port . ';dbname=' . MYSQL_DB;
            $pdo = new PDO($dsn, MYSQL_USER, MYSQL_PASSWORD);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            break;
        } catch (Throwable $e) { $connErr = $e->getMessage(); $pdo = null; }
    }
    try {
        if ($pdo === null) { throw new RuntimeException($connErr); }
        $table = (defined('TAB_PREFIX') ? TAB_PREFIX : '') . 'products';
        $has   = $pdo->query("SHOW COLUMNS FROM `$table` LIKE 'color'")->fetch();
        if ($has) {
            out("   = Spalte `color` in `$table` bereits vorhanden.");
            $counters['already']++;
        } elseif ($APPLY) {
            $pdo->exec("ALTER TABLE `$table` ADD `color` VARCHAR(7) NULL");
            out("   + Spalte `color` in `$table` hinzugefuegt.");
            $counters['applied']++;
        } else {
            out("   ~ wuerde hinzugefuegt: ALTER TABLE `$table` ADD `color` VARCHAR(7) NULL");
            $counters['todo']++;
        }
    } catch (Throwable $e) {
        out("   ! DB nicht erreichbar – Schritt uebersprungen (" . $e->getMessage() . ").");
        out("     Manuell (phpMyAdmin):  ALTER TABLE " . (defined('TAB_PREFIX') ? TAB_PREFIX : '<prefix>') . "products ADD color VARCHAR(7) NULL;");
        out("     Tipp: Patcher im BROWSER aufrufen (?apply=1) – dann nutzt er den MySQL-Zugang des Webservers.");
    }
}

/* ----------------------------------------------------------------
 * Schritt 2: Quelltext-Aenderungen
 * Jede Aenderung: present=Marker (schon erledigt?), search=Anker, replace=Ersatz.
 * Alle Ersetzungen sind einzeilig -> keine Einrueckungsprobleme.
 * ---------------------------------------------------------------- */
$edits = [];

// --- php/dbutils.php : $prodCols (Editor-Feld + Persistenz) ---
$edits[] = ['file' => 'php/dbutils.php', 'label' => '$prodCols: Farb-Definition',
 'present' => <<<'T'
"property" => "color"
T,
 'search'  => <<<'T'
array("col" => 'longname',
T,
 'replace' => <<<'T'
array("col" => 'color', "hist" => 0, "property" => "color", "default" => null, "type" => "color", "menu" => "Farbe", "guiname" => ["Farbe","Color","Color"]),
		array("col" => 'longname',
T];

// --- php/dbutils.php : Farbe NICHT historisieren (hist=0) ---
// Verhindert "Unknown column 'color'" beim Schreiben in die Historie-Tabelle histprod.
// Wirkt auch als Upgrade fuer bereits mit hist=1 gepatchte Installationen.
$edits[] = ['file' => 'php/dbutils.php', 'label' => '$prodCols: color aus Historie ausnehmen (hist=0)',
 'present' => <<<'T'
"col" => 'color', "hist" => 0,
T,
 'search'  => <<<'T'
"col" => 'color', "hist" => 1,
T,
 'replace' => <<<'T'
"col" => 'color', "hist" => 0,
T];

// --- php/products.php : $proddefs (Wert beim Bearbeiten laden) ---
$edits[] = ['file' => 'php/products.php', 'label' => '$proddefs: color "get"-Spalte',
 'present' => <<<'T'
"id" => "color"
T,
 'search'  => <<<'T'
array("id" => "longname", "get" => "longname",
T,
 'replace' => <<<'T'
array("id" => "color", "get" => "COALESCE(color,'') as color","histid" => "color","histget" => "color","histexportname" => "Farbe","isnumber" => "0"),
		array("id" => "longname", "get" => "longname",
T];

// --- php/products.php : Bestelllisten-SELECT (Zweig 1) ---
$edits[] = ['file' => 'php/products.php', 'label' => 'Bestellliste SELECT (Standard)',
 'present' => <<<'T'
COALESCE(color,'') as color,COALESCE(prodimageid,0) as prodimageid,sorting
T,
 'search'  => <<<'T'
COALESCE(prodimageid,0) as prodimageid,sorting
T,
 'replace' => <<<'T'
COALESCE(color,'') as color,COALESCE(prodimageid,0) as prodimageid,sorting
T];

// --- php/products.php : Bestelllisten-SELECT (Zweig 2, meistverkaufte) ---
$edits[] = ['file' => 'php/products.php', 'label' => 'Bestellliste SELECT (Favoriten/UNION)',
 'present' => <<<'T'
COALESCE(P.color,'') as color,COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T,
 'search'  => <<<'T'
COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T,
 'replace' => <<<'T'
COALESCE(P.color,'') as color,COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T];

// --- php/products.php : color ins JSON ---
$edits[] = ['file' => 'php/products.php', 'label' => 'JSON: color ans Frontend',
 'present' => <<<'T'
"color" => $row['color']
T,
 'search'  => <<<'T'
"prodimageid" => $row['prodimageid'],
T,
 'replace' => <<<'T'
"color" => $row['color'],"prodimageid" => $row['prodimageid'],
T];

// --- elements/productgui.js : Colorpicker im Editor (Render-Switch) ---
// Robust ggue. jQuery Mobile: alle Controls mit data-role="none" (kein Enhancement),
// IMMER sichtbare Swatch-Palette + Vorschaufeld + nativer Picker (umrandet) + Hex-Feld
// (= gespeichertes Feld, leer = keine Farbe) + Clear. Ohne Fremdbibliothek.
// mode 'block': ersetzt einen evtl. vorhandenen (aelteren) case "color" KOMPLETT,
// sonst Einfuegen vor case "yesno". So heilt es alte Patch-Staende selbst.
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Editor: Colorpicker',
 'mode'      => 'block',
 'present'   => <<<'T'
pv_'+idOfField
T,
 'blockstart' => <<<'T'
case "color":
T,
 'lookahead'  => <<<'T'
case "yesno":
T,
 'replace'  => <<<'T'
case "color": { var cv = (value && value !== 'null') ? value : ''; var pal=['#e74c3c','#e67e22','#f1c40f','#2ecc71','#1abc9c','#3498db','#9b59b6','#34495e','#e84393','#a29bfe','#00cec9','#fab1a0','#ffeaa7','#55efc4','#000000','#7f8c8d','#bdc3c7','#ffffff']; var sw=''; pal.forEach(function(col){ sw += '<span title="'+col+'" onclick="document.getElementById(\''+idOfField+'\').value=\''+col+'\';var p=document.getElementById(\'cp_'+idOfField+'\');if(p){p.value=\''+col+'\';}var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=\''+col+'\';}" style="display:inline-block;width:24px;height:24px;border:1px solid #555;background:'+col+';cursor:pointer;margin:2px;"></span>'; }); txt += '<p><div data-role="fieldcontain"><label for="'+idOfField+'">'+guiname+'</label><div style="margin:4px 0;"><input type="color" data-role="none" id="cp_'+idOfField+'" value="'+(cv||'#ffffff')+'" onchange="document.getElementById(\''+idOfField+'\').value=this.value;var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=this.value;}" style="width:60px;height:34px;border:1px solid #999;padding:0;cursor:pointer;vertical-align:middle;" /> <input type="text" data-role="none" name="'+idOfField+'" id="'+idOfField+'" value="'+cv+'" placeholder="#rrggbb" oninput="var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=this.value||\'transparent\';}" style="width:90px;height:30px;border:1px solid #999;background:white;color:black;vertical-align:middle;" /> <span id="pv_'+idOfField+'" style="display:inline-block;width:30px;height:30px;border:1px solid #999;vertical-align:middle;background:'+(cv||'transparent')+';"></span> <button type="button" data-role="none" onclick="document.getElementById(\''+idOfField+'\').value=\'\';var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=\'transparent\';}" style="height:32px;cursor:pointer;">&#10005;</button><br>'+sw+'</div></div></p>'; break; }
T];

// --- elements/productgui.js : jQuery-Mobile-Fix ---
// jQM (1.4) "enhanced" <input type="color"> und versteckt den nativen Picker.
// data-role="none" laesst das Element als natives Control stehen.
// Wirkt auch als Upgrade auf bereits gepatchte Installationen.
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Editor: Colorpicker jQM-Fix (data-role=none)',
 'present' => <<<'T'
<input type="color" data-role="none"
T,
 'search'  => <<<'T'
<input type="color"
T,
 'replace' => <<<'T'
<input type="color" data-role="none"
T];

// --- elements/productgui.js : Admin-Artikelliste – color-Parameter in Signatur ---
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Admin-Liste: color-Parameter',
 'present' => <<<'T'
createOneEntryOfProdInCat(divIdPrefix,prodid,longname,isNewProdEntry,isAvailable,color)
T,
 'search'  => <<<'T'
createOneEntryOfProdInCat(divIdPrefix,prodid,longname,isNewProdEntry,isAvailable) {
T,
 'replace' => <<<'T'
createOneEntryOfProdInCat(divIdPrefix,prodid,longname,isNewProdEntry,isAvailable,color) {
T];

// --- elements/productgui.js : Admin-Artikelliste – Farb-Punkt im Listenkopf ---
// Zeigt einen kleinen farbigen Kreis links vom Artikelnamen, sofern eine Farbe gesetzt ist.
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Admin-Liste: Farb-Punkt im Header',
 'present' => <<<'T'
+(color?'<span style="display:inline-block;width:14px;
T,
 'search'  => <<<'T'
+'<h3><span id=prodheader_' + prodid + ' class="' + isAvailableClass + '">' + toHtml(longname) + '</span>';
T,
 'replace' => <<<'T'
+'<h3>'+(color?'<span style="display:inline-block;width:14px;height:14px;border-radius:3px;border:1px solid #555;background:'+color+';vertical-align:middle;margin-right:5px;flex-shrink:0;"></span>':'')+'<span id=prodheader_' + prodid + ' class="' + isAvailableClass + '">' + toHtml(longname) + '</span>';
T];

// --- elements/productgui.js : Admin-Artikelliste – color an Zeilen-Builder uebergeben ---
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Admin-Liste: color durchreichen (Aufruf)',
 'present' => <<<'T'
aProd.color||''
T,
 'search'  => <<<'T'
txt += this.createOneEntryOfProdInCat("cont_",aProd.id,aProd.longname,false,aProd.available);
T,
 'replace' => <<<'T'
txt += this.createOneEntryOfProdInCat("cont_",aProd.id,aProd.longname,false,aProd.available,aProd.color||'');
T];

// --- waiterdesktop.php : Produktbutton ohne Bild ---
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop-Button (ohne Bild)',
 'present' => <<<'T'
var cscol=f.color
T,
 'search'  => <<<'T'
if(0==preferimgdesk)b+='<div id="prod_'+f.id+'" class="productitem '+a+'">'+f.name+e;
T,
 'replace' => <<<'T'
if(0==preferimgdesk){var cscol=f.color?' style="background:'+f.color+' !important;"':'';b+='<div id="prod_'+f.id+'" class="productitem '+a+'"'+cscol+'>'+f.name+e;}
T];

// --- waiterdesktop.php : Produktbutton mit Bild (Farbe als Rahmen) ---
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop-Button (mit Bild, Rahmen)',
 'present' => <<<'T'
border:4px solid '+f.color
T,
 'search'  => <<<'T'
createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m")
T,
 'replace' => <<<'T'
(f.color?createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m").replace('style="','style="border:4px solid '+f.color+'; '):createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m"))
T];

// --- waiterdesktop.php : color durchreichen (getAllProds baut Objekte mit Whitelist neu) ---
// Ohne dies ist f.color im Desktop-Button undefined -> keine Farbe. Deckt alle 3 Objekt-Stellen.
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop: color durchreichen (getAllProds)',
 'present' => <<<'T'
amount:f.amount,price:f.price,color:f.color}
T,
 'search'  => <<<'T'
amount:f.amount,price:f.price}
T,
 'replace' => <<<'T'
amount:f.amount,price:f.price,color:f.color}
T];

// --- waiter.html : zentraler Produkt-Renderer ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld _renderProdEntry',
 'present' => <<<'T'
var cscol=b.color
T,
 'search'  => <<<'T'
c='<li data-theme="c" data-icon="false"><a href="#" id="'+e+b.id+'" class="'+g+c+'">';
T,
 'replace' => <<<'T'
var cscol=b.color?' style="background:'+b.color+' !important;"':'';c='<li data-theme="c" data-icon="false"><a href="#" id="'+e+b.id+'" class="'+g+c+'"'+cscol+'>';
T];

// --- waiter.html : color in der Produktobjekt-Kopie behalten ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld: color durchreichen (Liste)',
 'present' => <<<'T'
,phase:f.phase,amount:f.amount,color:f.color}
T,
 'search'  => <<<'T'
,phase:f.phase,amount:f.amount}
T,
 'replace' => <<<'T'
,phase:f.phase,amount:f.amount,color:f.color}
T];

// --- waiter.html : color in der Favoriten-Kopie behalten ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld: color durchreichen (Favoriten)',
 'present' => <<<'T'
,amount:g.amount,color:g.color}
T,
 'search'  => <<<'T'
{id:g.id,longname:g.longname,unit:g.unit,amount:g.amount}
T,
 'replace' => <<<'T'
{id:g.id,longname:g.longname,unit:g.unit,amount:g.amount,color:g.color}
T];

/* ----------------------------------------------------------------
 * Verarbeitung pro Datei
 * ---------------------------------------------------------------- */
$byFile = [];
foreach ($edits as $e) { $byFile[$e['file']][] = $e; }

foreach ($byFile as $relFile => $fileEdits) {
    out("\n[" . $relFile . "]");
    $path = $BASE . '/' . $relFile;
    if (!is_file($path)) {
        out("   ! Datei nicht gefunden – uebersprungen.");
        foreach ($fileEdits as $e) { $counters['missing']++; }
        continue;
    }
    $content = file_get_contents($path);
    $orig    = $content;

    foreach ($fileEdits as $e) {
        // Block-Modus: vorhandenen Code-Block (z.B. alter case "color") komplett ersetzen,
        // sonst vor 'lookahead' einfuegen. Heilt aeltere Patch-Staende selbst.
        if (isset($e['mode']) && $e['mode'] === 'block') {
            if (strpos($content, $e['present']) !== false) {
                out("   = {$e['label']}: bereits aktuell.");
                $counters['already']++;
                continue;
            }
            $insert = $e['replace'] . "\n                                        ";
            $pat = '/' . preg_quote($e['blockstart'], '/') . '.*?(?=' . preg_quote($e['lookahead'], '/') . ')/s';
            if (preg_match($pat, $content)) {
                if ($APPLY) {
                    $content = preg_replace_callback($pat, function () use ($insert) { return $insert; }, $content, 1);
                    out("   + {$e['label']}: aeltere Variante ersetzt (Upgrade).");
                    $counters['applied']++;
                } else {
                    out("   ~ {$e['label']}: aeltere Variante wuerde ersetzt.");
                    $counters['todo']++;
                }
            } elseif (($pos = strpos($content, $e['lookahead'])) !== false) {
                if ($APPLY) {
                    $content = substr($content, 0, $pos) . $insert . substr($content, $pos);
                    out("   + {$e['label']}: eingefuegt.");
                    $counters['applied']++;
                } else {
                    out("   ~ {$e['label']}: wuerde eingefuegt.");
                    $counters['todo']++;
                }
            } else {
                out("   ! {$e['label']}: Anker NICHT gefunden (andere Version?) – manuell pruefen.");
                $counters['missing']++;
            }
            continue;
        }
        if (strpos($content, $e['present']) !== false) {
            out("   = {$e['label']}: bereits vorhanden.");
            $counters['already']++;
        } elseif (isset($e['conflict']) && strpos($content, $e['conflict']) !== false) {
            out("   ! {$e['label']}: AELTERE Variante erkannt. Bitte '$relFile' aus der Sicherung");
            out("     '$relFile.colorbak' wiederherstellen und Patcher erneut ausfuehren.");
            $counters['missing']++;
        } elseif (strpos($content, $e['search']) !== false) {
            if ($APPLY) {
                $content = str_replace($e['search'], $e['replace'], $content);
                out("   + {$e['label']}: ergaenzt.");
                $counters['applied']++;
            } else {
                out("   ~ {$e['label']}: wuerde ergaenzt.");
                $counters['todo']++;
            }
        } else {
            out("   ! {$e['label']}: Anker NICHT gefunden (andere Version?) – manuell pruefen.");
            $counters['missing']++;
        }
    }

    if ($APPLY && $content !== $orig) {
        $bak = $path . '.colorbak';
        if (!is_file($bak)) { copy($path, $bak); out("   (Sicherung: " . basename($bak) . ")"); }
        file_put_contents($path, $content);
    }
}

/* ----------------------------------------------------------------
 * Zusammenfassung
 * ---------------------------------------------------------------- */
out("\n" . str_repeat('=', 64));
out(sprintf("Bereits vorhanden: %d   Ergaenzt: %d   Offen: %d   Anker fehlt: %d",
    $counters['already'], $counters['applied'], $counters['todo'], $counters['missing']));
if (!$APPLY && ($counters['todo'] > 0)) {
    out("Vorschau-Modus. Zum Anwenden:  php " . basename(__FILE__) . " --apply   (bzw. ?apply=1)");
}
if ($counters['missing'] > 0) {
    out("Hinweis: 'Anker fehlt' = diese Stelle weicht von der erwarteten Version ab; bitte manuell ergaenzen.");
}
if ($APPLY && $counters['applied'] > 0) {
    out("Fertig. Bitte den Browser-Cache leeren (JS/HTML werden gecacht).");
}
Gruß Thomas
Dateianhänge
Screenshot 2026-06-30 190630.png
Screenshot 2026-06-30 190630.png (18.86 KiB) 729 mal betrachtet

Re: färbige Artikeldarstellung

von Punkt6 » Mo 29. Jun 2026, 21:28

Hallo,

bitte gerne. Du hast da ja schon so viel Arbeit rein gesteckt, und wenn ich mich da ein bisschen in deinen Code einlese und Anpassungen für mich mache, sollen auch alle Anderen die Möglichkeit haben, diese zu nutzen. Deswegen auch als Patch, damit da nicht allzuviel selbst geändert werden muss.

Ich werde mal den Beitrag mit den Gastbestellungen suchen, eventuell kann ich dir da auch was zur Verfügung stellen. Wenn es über Style Sheet erledigt wird, ist es kein Problem, sonst etwas Suche im Code.

Beim DSFinv-k Export kann ich leider nicht helfen, da kenne ich mich auch zu wenig aus...

Viel Freude mit dieser Adaptierung von dem genialen OrderSprinter von Stefan.

LG

Re: färbige Artikeldarstellung

von pichel » Sa 27. Jun 2026, 19:02

Hi,

Vielen Dank für die schöne Erweiterung, die du den Nutzern bereitstellst. Es dient auf jeden Fall der Übersicht. Wenn ich nicht meine ganze Energie noch in den DSFinv-k Export stecken würde (na gut, soviel Zeit ist es leider nicht, ich wünschte ich hätte mehr...), würde ich solche Änderungen auch gerne einbauen. Ihr habt alle so viele total nützliche Anregungen, aber wenn ich das rechtliche nicht bald fertig bekomme, ist alles andere dann auch egal...

Gruß,

Stefan

Re: färbige Artikeldarstellung

von xxxFB1907xxx » Do 25. Jun 2026, 18:41

Hallo,

erst einmal vielen Dank für deinen Patch – die farbliche Darstellung der Artikel ist wirklich eine tolle Verbesserung.

Ich hätte noch eine Frage: Aktuell werden Gastbestellungen ja kursiv und mit einer etwas helleren Schrift dargestellt. Ich würde diese gerne stattdessen mit einer eigenen Hintergrundfarbe hervorheben, z. B. roter Hintergrund mit weißer Schrift.

Stefan hatte mir zwar schon gesagt, an welcher Stelle man das ändern kann, aber ich habe es leider trotzdem nicht hinbekommen. Bevor ich ihn damit weiter belästige oder nerve, wollte ich dich fragen, ob du vielleicht einen Tipp hast oder ob sich das relativ einfach umsetzen lässt.

Vielen Dank schon mal!

färbige Artikeldarstellung

von Punkt6 » Mi 24. Jun 2026, 10:57

Hallo, bei unserem Einsatz von Ordersprinter kam der Wunsch auf, die Artikel zur besseren Übersicht farblich unterschiedlich zu gestalten.
Ich hab mich dann mit der Version 2.9.12 befasst und eine Patch Datei erstellt, dass die Artikel unterschiedliche Farben bekommen können. Die Datei kann im Root Verzeichnis abgelegt und per Web oder Commandline ausgeführt werden.
AUFRUF
Vorschau (nichts wird geaendert):
php patch_article_colors.php
Anwenden:
php patch_article_colors.php --apply
Im Browser: patch_article_colors.php (Vorschau)
patch_article_colors.php?apply=1 (Anwenden)

Vor jeder ersten Änderung einer Datei wird eine Sicherung <datei>.colorbak angelegt.
Leider kann ich hier keine php Datei einfügen.
Das ist der Code für die patch_article_colors.php

Code: Alles auswählen

<?php
/**
 * OrderSprinter – Erweiterung "Artikelfarben"
 * ------------------------------------------------------------
 * Idempotenter Patcher. Prueft fuer JEDE Einzelaenderung, ob sie bereits
 * (ganz oder teilweise) vorhanden ist, und ergaenzt nur Fehlendes.
 * Mehrfaches Ausfuehren ist gefahrlos.
 *
 * ABLEGEN im webapp-Rootverzeichnis (dort wo waiter.html / waiterdesktop.php liegen).
 *
 * AUFRUF
 *   Vorschau (nichts wird geaendert):
 *       php patch_article_colors.php
 *   Anwenden:
 *       php patch_article_colors.php --apply
 *   Im Browser:  patch_article_colors.php          (Vorschau)
 *                patch_article_colors.php?apply=1   (Anwenden)
 *
 * Vor jeder ersten Aenderung einer Datei wird eine Sicherung <datei>.colorbak angelegt.
 */

error_reporting(E_ALL);
$APPLY = (PHP_SAPI === 'cli' && in_array('--apply', $argv, true)) || isset($_GET['apply']);
$isCli = (PHP_SAPI === 'cli');
if (!$isCli) { header('Content-Type: text/plain; charset=utf-8'); }
$BASE  = __DIR__;

function out($s) { echo $s . "\n"; }
out("OrderSprinter – Patcher 'Artikelfarben'   [" . ($APPLY ? "ANWENDEN" : "VORSCHAU") . "]");
out(str_repeat('=', 64));

$counters = ['already' => 0, 'applied' => 0, 'todo' => 0, 'missing' => 0];

/* ----------------------------------------------------------------
 * Schritt 1: Datenbankspalte  products.color
 * ---------------------------------------------------------------- */
out("\n[DB] Spalte `color` in der products-Tabelle");
$cfg = $BASE . '/php/config.php';
if (!is_file($cfg)) {
    out("   ! php/config.php nicht gefunden – DB-Schritt uebersprungen.");
} else {
    require_once $cfg;
    $pdo = null;
    $port = defined('MYSQL_PORT') ? MYSQL_PORT : 3306;
    // Verbindung versuchen; bei 'localhost' (Socket) zusaetzlich TCP-Fallback 127.0.0.1,
    // weil CLI- und Webserver-PHP oft unterschiedliche Sockets nutzen.
    $hosts = [MYSQL_HOST];
    if (strtolower(MYSQL_HOST) === 'localhost') { $hosts[] = '127.0.0.1'; }
    $connErr = '';
    foreach ($hosts as $h) {
        try {
            $dsn = 'mysql:host=' . $h . ';port=' . $port . ';dbname=' . MYSQL_DB;
            $pdo = new PDO($dsn, MYSQL_USER, MYSQL_PASSWORD);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            break;
        } catch (Throwable $e) { $connErr = $e->getMessage(); $pdo = null; }
    }
    try {
        if ($pdo === null) { throw new RuntimeException($connErr); }
        $table = (defined('TAB_PREFIX') ? TAB_PREFIX : '') . 'products';
        $has   = $pdo->query("SHOW COLUMNS FROM `$table` LIKE 'color'")->fetch();
        if ($has) {
            out("   = Spalte `color` in `$table` bereits vorhanden.");
            $counters['already']++;
        } elseif ($APPLY) {
            $pdo->exec("ALTER TABLE `$table` ADD `color` VARCHAR(7) NULL");
            out("   + Spalte `color` in `$table` hinzugefuegt.");
            $counters['applied']++;
        } else {
            out("   ~ wuerde hinzugefuegt: ALTER TABLE `$table` ADD `color` VARCHAR(7) NULL");
            $counters['todo']++;
        }
    } catch (Throwable $e) {
        out("   ! DB nicht erreichbar – Schritt uebersprungen (" . $e->getMessage() . ").");
        out("     Manuell (phpMyAdmin):  ALTER TABLE " . (defined('TAB_PREFIX') ? TAB_PREFIX : '<prefix>') . "products ADD color VARCHAR(7) NULL;");
        out("     Tipp: Patcher im BROWSER aufrufen (?apply=1) – dann nutzt er den MySQL-Zugang des Webservers.");
    }
}

/* ----------------------------------------------------------------
 * Schritt 2: Quelltext-Aenderungen
 * Jede Aenderung: present=Marker (schon erledigt?), search=Anker, replace=Ersatz.
 * Alle Ersetzungen sind einzeilig -> keine Einrueckungsprobleme.
 * ---------------------------------------------------------------- */
$edits = [];

// --- php/dbutils.php : $prodCols (Editor-Feld + Persistenz) ---
$edits[] = ['file' => 'php/dbutils.php', 'label' => '$prodCols: Farb-Definition',
 'present' => <<<'T'
"property" => "color"
T,
 'search'  => <<<'T'
array("col" => 'longname',
T,
 'replace' => <<<'T'
array("col" => 'color', "hist" => 0, "property" => "color", "default" => null, "type" => "color", "menu" => "Farbe", "guiname" => ["Farbe","Color","Color"]),
		array("col" => 'longname',
T];

// --- php/dbutils.php : Farbe NICHT historisieren (hist=0) ---
// Verhindert "Unknown column 'color'" beim Schreiben in die Historie-Tabelle histprod.
// Wirkt auch als Upgrade fuer bereits mit hist=1 gepatchte Installationen.
$edits[] = ['file' => 'php/dbutils.php', 'label' => '$prodCols: color aus Historie ausnehmen (hist=0)',
 'present' => <<<'T'
"col" => 'color', "hist" => 0,
T,
 'search'  => <<<'T'
"col" => 'color', "hist" => 1,
T,
 'replace' => <<<'T'
"col" => 'color', "hist" => 0,
T];

// --- php/products.php : $proddefs (Wert beim Bearbeiten laden) ---
$edits[] = ['file' => 'php/products.php', 'label' => '$proddefs: color "get"-Spalte',
 'present' => <<<'T'
"id" => "color"
T,
 'search'  => <<<'T'
array("id" => "longname", "get" => "longname",
T,
 'replace' => <<<'T'
array("id" => "color", "get" => "COALESCE(color,'') as color","histid" => "color","histget" => "color","histexportname" => "Farbe","isnumber" => "0"),
		array("id" => "longname", "get" => "longname",
T];

// --- php/products.php : Bestelllisten-SELECT (Zweig 1) ---
$edits[] = ['file' => 'php/products.php', 'label' => 'Bestellliste SELECT (Standard)',
 'present' => <<<'T'
COALESCE(color,'') as color,COALESCE(prodimageid,0) as prodimageid,sorting
T,
 'search'  => <<<'T'
COALESCE(prodimageid,0) as prodimageid,sorting
T,
 'replace' => <<<'T'
COALESCE(color,'') as color,COALESCE(prodimageid,0) as prodimageid,sorting
T];

// --- php/products.php : Bestelllisten-SELECT (Zweig 2, meistverkaufte) ---
$edits[] = ['file' => 'php/products.php', 'label' => 'Bestellliste SELECT (Favoriten/UNION)',
 'present' => <<<'T'
COALESCE(P.color,'') as color,COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T,
 'search'  => <<<'T'
COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T,
 'replace' => <<<'T'
COALESCE(P.color,'') as color,COALESCE(P.prodimageid,0) as prodimageid,P.sorting as sorting
T];

// --- php/products.php : color ins JSON ---
$edits[] = ['file' => 'php/products.php', 'label' => 'JSON: color ans Frontend',
 'present' => <<<'T'
"color" => $row['color']
T,
 'search'  => <<<'T'
"prodimageid" => $row['prodimageid'],
T,
 'replace' => <<<'T'
"color" => $row['color'],"prodimageid" => $row['prodimageid'],
T];

// --- elements/productgui.js : Colorpicker im Editor (Render-Switch) ---
// Robust ggue. jQuery Mobile: alle Controls mit data-role="none" (kein Enhancement),
// IMMER sichtbare Swatch-Palette + Vorschaufeld + nativer Picker (umrandet) + Hex-Feld
// (= gespeichertes Feld, leer = keine Farbe) + Clear. Ohne Fremdbibliothek.
// mode 'block': ersetzt einen evtl. vorhandenen (aelteren) case "color" KOMPLETT,
// sonst Einfuegen vor case "yesno". So heilt es alte Patch-Staende selbst.
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Editor: Colorpicker',
 'mode'      => 'block',
 'present'   => <<<'T'
pv_'+idOfField
T,
 'blockstart' => <<<'T'
case "color":
T,
 'lookahead'  => <<<'T'
case "yesno":
T,
 'replace'  => <<<'T'
case "color": { var cv = (value && value !== 'null') ? value : ''; var pal=['#e74c3c','#e67e22','#f1c40f','#2ecc71','#1abc9c','#3498db','#9b59b6','#34495e','#e84393','#a29bfe','#00cec9','#fab1a0','#ffeaa7','#55efc4','#000000','#7f8c8d','#bdc3c7','#ffffff']; var sw=''; pal.forEach(function(col){ sw += '<span title="'+col+'" onclick="document.getElementById(\''+idOfField+'\').value=\''+col+'\';var p=document.getElementById(\'cp_'+idOfField+'\');if(p){p.value=\''+col+'\';}var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=\''+col+'\';}" style="display:inline-block;width:24px;height:24px;border:1px solid #555;background:'+col+';cursor:pointer;margin:2px;"></span>'; }); txt += '<p><div data-role="fieldcontain"><label for="'+idOfField+'">'+guiname+'</label><div style="margin:4px 0;"><input type="color" data-role="none" id="cp_'+idOfField+'" value="'+(cv||'#ffffff')+'" onchange="document.getElementById(\''+idOfField+'\').value=this.value;var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=this.value;}" style="width:60px;height:34px;border:1px solid #999;padding:0;cursor:pointer;vertical-align:middle;" /> <input type="text" data-role="none" name="'+idOfField+'" id="'+idOfField+'" value="'+cv+'" placeholder="#rrggbb" oninput="var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=this.value||\'transparent\';}" style="width:90px;height:30px;border:1px solid #999;background:white;color:black;vertical-align:middle;" /> <span id="pv_'+idOfField+'" style="display:inline-block;width:30px;height:30px;border:1px solid #999;vertical-align:middle;background:'+(cv||'transparent')+';"></span> <button type="button" data-role="none" onclick="document.getElementById(\''+idOfField+'\').value=\'\';var v=document.getElementById(\'pv_'+idOfField+'\');if(v){v.style.background=\'transparent\';}" style="height:32px;cursor:pointer;">&#10005;</button><br>'+sw+'</div></div></p>'; break; }
T];

// --- elements/productgui.js : jQuery-Mobile-Fix ---
// jQM (1.4) "enhanced" <input type="color"> und versteckt den nativen Picker.
// data-role="none" laesst das Element als natives Control stehen.
// Wirkt auch als Upgrade auf bereits gepatchte Installationen.
$edits[] = ['file' => 'elements/productgui.js', 'label' => 'Editor: Colorpicker jQM-Fix (data-role=none)',
 'present' => <<<'T'
<input type="color" data-role="none"
T,
 'search'  => <<<'T'
<input type="color"
T,
 'replace' => <<<'T'
<input type="color" data-role="none"
T];

// --- waiterdesktop.php : Produktbutton ohne Bild ---
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop-Button (ohne Bild)',
 'present' => <<<'T'
var cscol=f.color
T,
 'search'  => <<<'T'
if(0==preferimgdesk)b+='<div id="prod_'+f.id+'" class="productitem '+a+'">'+f.name+e;
T,
 'replace' => <<<'T'
if(0==preferimgdesk){var cscol=f.color?' style="background:'+f.color+' !important;"':'';b+='<div id="prod_'+f.id+'" class="productitem '+a+'"'+cscol+'>'+f.name+e;}
T];

// --- waiterdesktop.php : Produktbutton mit Bild (Farbe als Rahmen) ---
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop-Button (mit Bild, Rahmen)',
 'present' => <<<'T'
border:4px solid '+f.color
T,
 'search'  => <<<'T'
createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m")
T,
 'replace' => <<<'T'
(f.color?createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m").replace('style="','style="border:4px solid '+f.color+'; '):createImageStyle("php/contenthandler.php?module=products&command=getprodimage&prodid="+f.id+"&size=m"))
T];

// --- waiterdesktop.php : color durchreichen (getAllProds baut Objekte mit Whitelist neu) ---
// Ohne dies ist f.color im Desktop-Button undefined -> keine Farbe. Deckt alle 3 Objekt-Stellen.
$edits[] = ['file' => 'waiterdesktop.php', 'label' => 'Desktop: color durchreichen (getAllProds)',
 'present' => <<<'T'
amount:f.amount,price:f.price,color:f.color}
T,
 'search'  => <<<'T'
amount:f.amount,price:f.price}
T,
 'replace' => <<<'T'
amount:f.amount,price:f.price,color:f.color}
T];

// --- waiter.html : zentraler Produkt-Renderer ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld _renderProdEntry',
 'present' => <<<'T'
var cscol=b.color
T,
 'search'  => <<<'T'
c='<li data-theme="c" data-icon="false"><a href="#" id="'+e+b.id+'" class="'+g+c+'">';
T,
 'replace' => <<<'T'
var cscol=b.color?' style="background:'+b.color+' !important;"':'';c='<li data-theme="c" data-icon="false"><a href="#" id="'+e+b.id+'" class="'+g+c+'"'+cscol+'>';
T];

// --- waiter.html : color in der Produktobjekt-Kopie behalten ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld: color durchreichen (Liste)',
 'present' => <<<'T'
,phase:f.phase,amount:f.amount,color:f.color}
T,
 'search'  => <<<'T'
,phase:f.phase,amount:f.amount}
T,
 'replace' => <<<'T'
,phase:f.phase,amount:f.amount,color:f.color}
T];

// --- waiter.html : color in der Favoriten-Kopie behalten ---
$edits[] = ['file' => 'waiter.html', 'label' => 'Handheld: color durchreichen (Favoriten)',
 'present' => <<<'T'
,amount:g.amount,color:g.color}
T,
 'search'  => <<<'T'
{id:g.id,longname:g.longname,unit:g.unit,amount:g.amount}
T,
 'replace' => <<<'T'
{id:g.id,longname:g.longname,unit:g.unit,amount:g.amount,color:g.color}
T];

/* ----------------------------------------------------------------
 * Verarbeitung pro Datei
 * ---------------------------------------------------------------- */
$byFile = [];
foreach ($edits as $e) { $byFile[$e['file']][] = $e; }

foreach ($byFile as $relFile => $fileEdits) {
    out("\n[" . $relFile . "]");
    $path = $BASE . '/' . $relFile;
    if (!is_file($path)) {
        out("   ! Datei nicht gefunden – uebersprungen.");
        foreach ($fileEdits as $e) { $counters['missing']++; }
        continue;
    }
    $content = file_get_contents($path);
    $orig    = $content;

    foreach ($fileEdits as $e) {
        // Block-Modus: vorhandenen Code-Block (z.B. alter case "color") komplett ersetzen,
        // sonst vor 'lookahead' einfuegen. Heilt aeltere Patch-Staende selbst.
        if (isset($e['mode']) && $e['mode'] === 'block') {
            if (strpos($content, $e['present']) !== false) {
                out("   = {$e['label']}: bereits aktuell.");
                $counters['already']++;
                continue;
            }
            $insert = $e['replace'] . "\n                                        ";
            $pat = '/' . preg_quote($e['blockstart'], '/') . '.*?(?=' . preg_quote($e['lookahead'], '/') . ')/s';
            if (preg_match($pat, $content)) {
                if ($APPLY) {
                    $content = preg_replace_callback($pat, function () use ($insert) { return $insert; }, $content, 1);
                    out("   + {$e['label']}: aeltere Variante ersetzt (Upgrade).");
                    $counters['applied']++;
                } else {
                    out("   ~ {$e['label']}: aeltere Variante wuerde ersetzt.");
                    $counters['todo']++;
                }
            } elseif (($pos = strpos($content, $e['lookahead'])) !== false) {
                if ($APPLY) {
                    $content = substr($content, 0, $pos) . $insert . substr($content, $pos);
                    out("   + {$e['label']}: eingefuegt.");
                    $counters['applied']++;
                } else {
                    out("   ~ {$e['label']}: wuerde eingefuegt.");
                    $counters['todo']++;
                }
            } else {
                out("   ! {$e['label']}: Anker NICHT gefunden (andere Version?) – manuell pruefen.");
                $counters['missing']++;
            }
            continue;
        }
        if (strpos($content, $e['present']) !== false) {
            out("   = {$e['label']}: bereits vorhanden.");
            $counters['already']++;
        } elseif (isset($e['conflict']) && strpos($content, $e['conflict']) !== false) {
            out("   ! {$e['label']}: AELTERE Variante erkannt. Bitte '$relFile' aus der Sicherung");
            out("     '$relFile.colorbak' wiederherstellen und Patcher erneut ausfuehren.");
            $counters['missing']++;
        } elseif (strpos($content, $e['search']) !== false) {
            if ($APPLY) {
                $content = str_replace($e['search'], $e['replace'], $content);
                out("   + {$e['label']}: ergaenzt.");
                $counters['applied']++;
            } else {
                out("   ~ {$e['label']}: wuerde ergaenzt.");
                $counters['todo']++;
            }
        } else {
            out("   ! {$e['label']}: Anker NICHT gefunden (andere Version?) – manuell pruefen.");
            $counters['missing']++;
        }
    }

    if ($APPLY && $content !== $orig) {
        $bak = $path . '.colorbak';
        if (!is_file($bak)) { copy($path, $bak); out("   (Sicherung: " . basename($bak) . ")"); }
        file_put_contents($path, $content);
    }
}

/* ----------------------------------------------------------------
 * Zusammenfassung
 * ---------------------------------------------------------------- */
out("\n" . str_repeat('=', 64));
out(sprintf("Bereits vorhanden: %d   Ergaenzt: %d   Offen: %d   Anker fehlt: %d",
    $counters['already'], $counters['applied'], $counters['todo'], $counters['missing']));
if (!$APPLY && ($counters['todo'] > 0)) {
    out("Vorschau-Modus. Zum Anwenden:  php " . basename(__FILE__) . " --apply   (bzw. ?apply=1)");
}
if ($counters['missing'] > 0) {
    out("Hinweis: 'Anker fehlt' = diese Stelle weicht von der erwarteten Version ab; bitte manuell ergaenzen.");
}
if ($APPLY && $counters['applied'] > 0) {
    out("Fertig. Bitte den Browser-Cache leeren (JS/HTML werden gecacht).");
}
So sieht es aktuell bei unserer Konfiguration aus und die Kellner sind begeistert:
Dateianhänge
Artikel farblich
Artikel farblich
färbige Artike_Ordersprinter.jpg (133.5 KiB) 817 mal betrachtet

Nach oben