diff --git a/ChangeLog.md b/ChangeLog.md index c9ebc82..40450f7 100755 --- a/ChangeLog.md +++ b/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 diff --git a/import.php b/import.php index 529d1c1..f97b6c1 100755 --- a/import.php +++ b/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 '