Version 3.3: Sicherheit, Error-Handling und Berechtigungen
- XSS Fixes: $_SERVER['PHP_SELF'] und EAN-Ausgabe escaped - Error-Handling fuer rename()/copy() Dateioperationen - DB-Transaktion bei Force Reimport (Race Condition) - db->query() Rueckgabewerte bei Extrafields geprueft - Berechtigungspruefung fuer Index-Seite und Loeschen - Helper-Funktionen fuer Lieferantenpreis-Erstellung (DRY) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
06acc0b2f9
commit
1b2357a2aa
3 changed files with 140 additions and 11 deletions
12
ChangeLog.md
12
ChangeLog.md
|
|
@ -1,5 +1,17 @@
|
||||||
# CHANGELOG MODULE IMPORTZUGFERD FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
# CHANGELOG MODULE IMPORTZUGFERD FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||||
|
|
||||||
|
## 3.3
|
||||||
|
|
||||||
|
### Sicherheit und Code-Qualitaet
|
||||||
|
- XSS Fix: $_SERVER['PHP_SELF'] in JavaScript escaped (dol_escape_js)
|
||||||
|
- XSS Fix: EAN-Ausgabe in HTML escaped (dol_escape_htmltag)
|
||||||
|
- Error-Handling: rename()/copy() Dateioperationen mit Fehlerbehandlung
|
||||||
|
- Race Condition: DB-Transaktion bei Force Reimport hinzugefuegt
|
||||||
|
- Error-Handling: db->query() Rueckgabewerte bei Extrafields-Insert geprueft
|
||||||
|
- Berechtigungspruefung: Index-Seite prueft jetzt import:read Recht
|
||||||
|
- Berechtigungspruefung: Loeschen prueft jetzt import:delete Recht
|
||||||
|
- Helper-Funktionen fuer Lieferantenpreis-Erstellung (DRY)
|
||||||
|
|
||||||
## 3.2
|
## 3.2
|
||||||
|
|
||||||
### Neue Funktionen
|
### Neue Funktionen
|
||||||
|
|
|
||||||
136
import.php
136
import.php
|
|
@ -89,6 +89,107 @@ $notification = new ImportNotification($db);
|
||||||
$error = 0;
|
$error = 0;
|
||||||
$message = '';
|
$message = '';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper-Funktionen (DRY)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrafields fuer Lieferantenpreis aus Datanorm-Daten zusammenstellen
|
||||||
|
*
|
||||||
|
* @param Datanorm $datanorm Datanorm-Objekt
|
||||||
|
* @param ImportLine|null $lineObj Import-Zeile (optional, fuer ZUGFeRD-Daten)
|
||||||
|
* @return array Extrafields-Array
|
||||||
|
*/
|
||||||
|
function datanormBuildSupplierPriceExtrafields($datanorm, $lineObj = null)
|
||||||
|
{
|
||||||
|
$extrafields = array();
|
||||||
|
// Kupferzuschlag
|
||||||
|
if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) {
|
||||||
|
$extrafields['options_kupferzuschlag'] = $datanorm->metal_surcharge;
|
||||||
|
} elseif ($lineObj && !empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) {
|
||||||
|
$extrafields['options_kupferzuschlag'] = $lineObj->copper_surcharge;
|
||||||
|
}
|
||||||
|
// Preiseinheit
|
||||||
|
if (!empty($datanorm->price_unit) && $datanorm->price_unit > 1) {
|
||||||
|
$extrafields['options_preiseinheit'] = $datanorm->price_unit;
|
||||||
|
} elseif ($lineObj && !empty($lineObj->basis_quantity) && $lineObj->basis_quantity > 1) {
|
||||||
|
$extrafields['options_preiseinheit'] = $lineObj->basis_quantity;
|
||||||
|
}
|
||||||
|
// Warengruppe
|
||||||
|
if (!empty($datanorm->product_group)) {
|
||||||
|
$extrafields['options_warengruppe'] = $datanorm->product_group;
|
||||||
|
}
|
||||||
|
return $extrafields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lieferantenpreis aus Datanorm hinzufuegen
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Datenbank
|
||||||
|
* @param int $productId Produkt-ID
|
||||||
|
* @param Datanorm $datanorm Datanorm-Objekt
|
||||||
|
* @param Societe $supplier Lieferant-Objekt
|
||||||
|
* @param User $user Benutzer
|
||||||
|
* @param float $purchasePrice Einkaufspreis
|
||||||
|
* @param float $taxPercent MwSt-Satz
|
||||||
|
* @param array $extrafields Extrafields
|
||||||
|
* @return int >0 bei Erfolg, <0 bei Fehler
|
||||||
|
*/
|
||||||
|
function datanormAddSupplierPrice($db, $productId, $datanorm, $supplier, $user, $purchasePrice, $taxPercent = 19, $extrafields = array())
|
||||||
|
{
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
|
||||||
|
|
||||||
|
$prodfourn = new ProductFournisseur($db);
|
||||||
|
$prodfourn->id = $productId;
|
||||||
|
|
||||||
|
$supplierEan = !empty($datanorm->ean) ? $datanorm->ean : '';
|
||||||
|
$supplierEanType = !empty($datanorm->ean) ? 2 : 0;
|
||||||
|
$description = trim($datanorm->short_text1 . ($datanorm->short_text2 ? ' ' . $datanorm->short_text2 : ''));
|
||||||
|
|
||||||
|
return $prodfourn->update_buyprice(
|
||||||
|
1, $purchasePrice, $user, 'HT', $supplier, 0,
|
||||||
|
$datanorm->article_number, $taxPercent,
|
||||||
|
0, 0, 0, 0, 0, 0, array(), '',
|
||||||
|
0, 'HT', 1, '',
|
||||||
|
$description, $supplierEan, $supplierEanType,
|
||||||
|
$extrafields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrafields in product_fournisseur_price_extrafields einfuegen
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Datenbank
|
||||||
|
* @param int $priceId ID des Lieferantenpreises
|
||||||
|
* @param array $extrafields Extrafields-Array
|
||||||
|
*/
|
||||||
|
function datanormInsertPriceExtrafields($db, $priceId, $extrafields)
|
||||||
|
{
|
||||||
|
if (empty($priceId) || empty($extrafields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$kupferzuschlag = !empty($extrafields['options_kupferzuschlag']) ? (float)$extrafields['options_kupferzuschlag'] : 'NULL';
|
||||||
|
$preiseinheit = !empty($extrafields['options_preiseinheit']) ? (int)$extrafields['options_preiseinheit'] : 1;
|
||||||
|
$warengruppe = !empty($extrafields['options_warengruppe']) ? "'".$db->escape($extrafields['options_warengruppe'])."'" : 'NULL';
|
||||||
|
|
||||||
|
// Pruefen ob bereits vorhanden
|
||||||
|
$sqlCheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields WHERE fk_object = ".(int)$priceId;
|
||||||
|
$resCheck = $db->query($sqlCheck);
|
||||||
|
if ($resCheck && $db->num_rows($resCheck) > 0) {
|
||||||
|
return; // Bereits vorhanden
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields";
|
||||||
|
$sql .= " (fk_object, kupferzuschlag, preiseinheit, warengruppe) VALUES (";
|
||||||
|
$sql .= (int)$priceId.", ";
|
||||||
|
$sql .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
||||||
|
$sql .= $preiseinheit.", ";
|
||||||
|
$sql .= $warengruppe.")";
|
||||||
|
if (!$db->query($sql)) {
|
||||||
|
dol_syslog('ImportZugferd: Fehler beim Einfuegen der Extrafields: '.$db->lasterror(), LOG_ERR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actions
|
* Actions
|
||||||
*/
|
*/
|
||||||
|
|
@ -189,16 +290,16 @@ if ($action == 'upload') {
|
||||||
$oldImport = new ZugferdImport($db);
|
$oldImport = new ZugferdImport($db);
|
||||||
$oldImport->fetch(0, null, $file_hash);
|
$oldImport->fetch(0, null, $file_hash);
|
||||||
if ($oldImport->id > 0) {
|
if ($oldImport->id > 0) {
|
||||||
// Delete old lines
|
$db->begin();
|
||||||
|
// Alten Import-Datensatz komplett loeschen (Transaktion)
|
||||||
$oldLines = new ImportLine($db);
|
$oldLines = new ImportLine($db);
|
||||||
$oldLines->deleteAllByImport($oldImport->id);
|
$oldLines->deleteAllByImport($oldImport->id);
|
||||||
// Delete old files
|
|
||||||
$old_dir = $conf->importzugferd->dir_output.'/imports/'.$oldImport->id;
|
$old_dir = $conf->importzugferd->dir_output.'/imports/'.$oldImport->id;
|
||||||
if (is_dir($old_dir)) {
|
if (is_dir($old_dir)) {
|
||||||
dol_delete_dir_recursive($old_dir);
|
dol_delete_dir_recursive($old_dir);
|
||||||
}
|
}
|
||||||
// Delete old import record
|
|
||||||
$oldImport->delete($user);
|
$oldImport->delete($user);
|
||||||
|
$db->commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parse the file
|
// Parse the file
|
||||||
|
|
@ -281,7 +382,14 @@ if ($action == 'upload') {
|
||||||
if (!is_dir($final_dir)) {
|
if (!is_dir($final_dir)) {
|
||||||
dol_mkdir($final_dir);
|
dol_mkdir($final_dir);
|
||||||
}
|
}
|
||||||
rename($destfile, $final_dir.'/'.$filename);
|
if (!@rename($destfile, $final_dir.'/'.$filename)) {
|
||||||
|
// Fallback: copy + delete (z.B. bei verschiedenen Dateisystemen)
|
||||||
|
if (@copy($destfile, $final_dir.'/'.$filename)) {
|
||||||
|
@unlink($destfile);
|
||||||
|
} else {
|
||||||
|
dol_syslog('ImportZugferd: Fehler beim Verschieben der PDF nach '.$final_dir, LOG_ERR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send notification if manual intervention required
|
// Send notification if manual intervention required
|
||||||
if ($import->status == ZugferdImport::STATUS_PENDING) {
|
if ($import->status == ZugferdImport::STATUS_PENDING) {
|
||||||
|
|
@ -737,7 +845,9 @@ if ($action == 'createfromdatanorm' && $line_id > 0) {
|
||||||
$sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
$sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
||||||
$sqlInsertExtra .= $preiseinheit.", ";
|
$sqlInsertExtra .= $preiseinheit.", ";
|
||||||
$sqlInsertExtra .= $warengruppe.")";
|
$sqlInsertExtra .= $warengruppe.")";
|
||||||
$db->query($sqlInsertExtra);
|
if (!$db->query($sqlInsertExtra)) {
|
||||||
|
dol_syslog('ImportZugferd: Fehler beim Einfuegen der Extrafields: '.$db->lasterror(), LOG_ERR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1149,7 +1259,9 @@ if ($action == 'createallfromdatanorm' && $id > 0) {
|
||||||
$sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
$sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
||||||
$sqlInsertExtra .= $preiseinheit.", ";
|
$sqlInsertExtra .= $preiseinheit.", ";
|
||||||
$sqlInsertExtra .= $warengruppe.")";
|
$sqlInsertExtra .= $warengruppe.")";
|
||||||
$db->query($sqlInsertExtra);
|
if (!$db->query($sqlInsertExtra)) {
|
||||||
|
dol_syslog('ImportZugferd: Fehler beim Einfuegen der Extrafields: '.$db->lasterror(), LOG_ERR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1507,7 +1619,9 @@ if ($action == 'createinvoice' && $id > 0) {
|
||||||
if (!is_dir($dest_dir)) {
|
if (!is_dir($dest_dir)) {
|
||||||
dol_mkdir($dest_dir);
|
dol_mkdir($dest_dir);
|
||||||
}
|
}
|
||||||
copy($source_pdf, $dest_dir.'/'.$import->pdf_filename);
|
if (!@copy($source_pdf, $dest_dir.'/'.$import->pdf_filename)) {
|
||||||
|
dol_syslog('ImportZugferd: Fehler beim Kopieren der PDF nach '.$dest_dir, LOG_ERR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update import record
|
// Update import record
|
||||||
|
|
@ -1593,7 +1707,7 @@ if ($action == 'finishimport' && $id > 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete import record
|
// Delete import record
|
||||||
if ($action == 'confirm_delete' && $confirm == 'yes' && $id > 0) {
|
if ($action == 'confirm_delete' && $confirm == 'yes' && $id > 0 && $user->hasRight('importzugferd', 'import', 'delete')) {
|
||||||
$import->fetch($id);
|
$import->fetch($id);
|
||||||
|
|
||||||
// Delete lines first
|
// Delete lines first
|
||||||
|
|
@ -1865,7 +1979,7 @@ if ($action == 'edit' && $import->id > 0) {
|
||||||
print '<td>';
|
print '<td>';
|
||||||
print dol_escape_htmltag($line->product_name);
|
print dol_escape_htmltag($line->product_name);
|
||||||
if (!empty($line->ean) && !$hasProduct) {
|
if (!empty($line->ean) && !$hasProduct) {
|
||||||
print '<br><span style="color: #666;">EAN: '.$line->ean.'</span>';
|
print '<br><span style="color: #666;">EAN: '.dol_escape_htmltag($line->ean).'</span>';
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
print '<td class="right">'.price2num($line->quantity, 'MS').' '.zugferdGetUnitLabel($line->unit_code).'</td>';
|
print '<td class="right">'.price2num($line->quantity, 'MS').' '.zugferdGetUnitLabel($line->unit_code).'</td>';
|
||||||
|
|
@ -1940,7 +2054,7 @@ if ($action == 'edit' && $import->id > 0) {
|
||||||
print '<br><span class="opacitymedium">'.$langs->trans('MatchMethod').': '.$line->match_method.'</span>';
|
print '<br><span class="opacitymedium">'.$langs->trans('MatchMethod').': '.$line->match_method.'</span>';
|
||||||
}
|
}
|
||||||
if (!empty($line->ean)) {
|
if (!empty($line->ean)) {
|
||||||
print '<br><span class="opacitymedium"><i class="fas fa-barcode"></i> '.$line->ean.'</span>';
|
print '<br><span class="opacitymedium"><i class="fas fa-barcode"></i> '.dol_escape_htmltag($line->ean).'</span>';
|
||||||
}
|
}
|
||||||
print ' <i class="fas fa-check-circle" style="color: green;"></i>';
|
print ' <i class="fas fa-check-circle" style="color: green;"></i>';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2568,7 +2682,7 @@ function showRawDatanorm(articleNumber, fkSoc) {
|
||||||
|
|
||||||
// AJAX request
|
// AJAX request
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("GET", "'.$_SERVER['PHP_SELF'].'?action=get_raw_lines&article_number=" + encodeURIComponent(articleNumber) + "&fk_soc=" + fkSoc + "&token='.newToken().'", true);
|
xhr.open("GET", "'.dol_escape_js($_SERVER['PHP_SELF']).'?action=get_raw_lines&article_number=" + encodeURIComponent(articleNumber) + "&fk_soc=" + fkSoc + "&token='.newToken().'", true);
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ $langs->loadLangs(array("importzugferd@importzugferd"));
|
||||||
if (!isModEnabled('importzugferd')) {
|
if (!isModEnabled('importzugferd')) {
|
||||||
accessforbidden('Module not enabled');
|
accessforbidden('Module not enabled');
|
||||||
}
|
}
|
||||||
|
if (!$user->hasRight('importzugferd', 'import', 'read')) {
|
||||||
|
accessforbidden();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* View
|
* View
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue