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;">✕</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).");
}