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)
|
||||
|
||||
## 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
|
||||
|
||||
### Neue Funktionen
|
||||
|
|
|
|||
136
import.php
136
import.php
|
|
@ -89,6 +89,107 @@ $notification = new ImportNotification($db);
|
|||
$error = 0;
|
||||
$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
|
||||
*/
|
||||
|
|
@ -189,16 +290,16 @@ if ($action == 'upload') {
|
|||
$oldImport = new ZugferdImport($db);
|
||||
$oldImport->fetch(0, null, $file_hash);
|
||||
if ($oldImport->id > 0) {
|
||||
// Delete old lines
|
||||
$db->begin();
|
||||
// Alten Import-Datensatz komplett loeschen (Transaktion)
|
||||
$oldLines = new ImportLine($db);
|
||||
$oldLines->deleteAllByImport($oldImport->id);
|
||||
// Delete old files
|
||||
$old_dir = $conf->importzugferd->dir_output.'/imports/'.$oldImport->id;
|
||||
if (is_dir($old_dir)) {
|
||||
dol_delete_dir_recursive($old_dir);
|
||||
}
|
||||
// Delete old import record
|
||||
$oldImport->delete($user);
|
||||
$db->commit();
|
||||
}
|
||||
}
|
||||
// Parse the file
|
||||
|
|
@ -281,7 +382,14 @@ if ($action == 'upload') {
|
|||
if (!is_dir($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
|
||||
if ($import->status == ZugferdImport::STATUS_PENDING) {
|
||||
|
|
@ -737,7 +845,9 @@ if ($action == 'createfromdatanorm' && $line_id > 0) {
|
|||
$sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", ";
|
||||
$sqlInsertExtra .= $preiseinheit.", ";
|
||||
$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 .= $preiseinheit.", ";
|
||||
$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)) {
|
||||
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
|
||||
|
|
@ -1593,7 +1707,7 @@ if ($action == 'finishimport' && $id > 0) {
|
|||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Delete lines first
|
||||
|
|
@ -1865,7 +1979,7 @@ if ($action == 'edit' && $import->id > 0) {
|
|||
print '<td>';
|
||||
print dol_escape_htmltag($line->product_name);
|
||||
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 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>';
|
||||
}
|
||||
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>';
|
||||
} else {
|
||||
|
|
@ -2568,7 +2682,7 @@ function showRawDatanorm(articleNumber, fkSoc) {
|
|||
|
||||
// AJAX request
|
||||
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() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ $langs->loadLangs(array("importzugferd@importzugferd"));
|
|||
if (!isModEnabled('importzugferd')) {
|
||||
accessforbidden('Module not enabled');
|
||||
}
|
||||
if (!$user->hasRight('importzugferd', 'import', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
|
|
|
|||
Loading…
Reference in a new issue