hasRight('bericht', 'write')) bericht_ajax_fail('Permission denied', 403); $berichtid = (int) ($_POST['berichtid'] ?? 0); $bericht = new Bericht($db); if ($bericht->fetch($berichtid) <= 0) bericht_ajax_fail('Bericht nicht gefunden', 404); $parent = bericht_fetch_parent($db, $bericht->element_type, $bericht->fk_element); if (!$parent) bericht_ajax_fail('Parent nicht gefunden', 404); $pages = BerichtPage::fetchAllForBericht($db, $bericht->id); if (empty($pages)) bericht_ajax_fail('Bericht enthält keine Seiten'); // TCPDF + FPDI laden $tcpdf_loaded = false; foreach (array( DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf.php', DOL_DOCUMENT_ROOT.'/includes/tcpdf/tcpdf.php', ) as $p) { if (file_exists($p)) { require_once $p; $tcpdf_loaded = true; break; } } if (!$tcpdf_loaded) bericht_ajax_fail('TCPDF nicht gefunden'); $fpdi_loaded = false; foreach (array( DOL_DOCUMENT_ROOT.'/includes/setasign/vendor/setasign/fpdi/src/Tcpdf/Fpdi.php', DOL_DOCUMENT_ROOT.'/includes/fpdi/src/Tcpdf/Fpdi.php', ) as $p) { if (file_exists($p)) { require_once $p; $fpdi_loaded = true; break; } } // FPDI ist optional — wenn fehlt, können wir keine bestehenden PDFs einbetten, // aber Bilder + Annotationen funktionieren weiterhin. $ori = in_array($bericht->page_orientation, array('P','L'), true) ? $bericht->page_orientation : 'P'; $fmt = in_array($bericht->page_format, array('A4','A3','A5','Letter'), true) ? $bericht->page_format : 'A4'; if ($fpdi_loaded) { $pdf = new \setasign\Fpdi\Tcpdf\Fpdi($ori, 'mm', $fmt, true, 'UTF-8', false); } else { $pdf = new TCPDF($ori, 'mm', $fmt, true, 'UTF-8', false); } $pdf->SetCreator('Dolibarr Bericht-Modul'); $pdf->SetAuthor($user->getFullName($langs)); $pdf->SetTitle($bericht->titel ?: $bericht->ref); $pdf->SetMargins(10, 10, 10); $pdf->SetAutoPageBreak(true, 10); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); // --- Deckblatt aus ODT-Template --- $tempdir = DOL_DATA_ROOT.'/bericht/temp/'.$berichtid; if (!is_dir($tempdir)) dol_mkdir($tempdir); if (!empty($bericht->template_odt)) { $template_path = DOL_DATA_ROOT.'/bericht/templates/'.dol_sanitizeFileName($bericht->template_odt); if (file_exists($template_path)) { $cover_pdf = bericht_render_cover($template_path, $bericht, $parent, $tempdir); if ($cover_pdf && file_exists($cover_pdf) && $fpdi_loaded) { $cover_pages = $pdf->setSourceFile($cover_pdf); for ($cp = 1; $cp <= $cover_pages; $cp++) { $tpl = $pdf->importPage($cp); $size = $pdf->getTemplateSize($tpl); $pdf->AddPage($size['orientation'] ?? 'P', array($size['width'], $size['height'])); $pdf->useTemplate($tpl); } } } } // --- Seiten --- foreach ($pages as $page) { $full = bericht_resolve_data_path($page->source_path); if (!$full || !file_exists($full)) continue; if ($page->source_type === 'image' || preg_match('/\.(png|jpe?g)$/i', $full)) { // Bild als Seite im konfigurierten Format/Orientation $pdf->AddPage($ori, $fmt); list($iw, $ih) = @getimagesize($full); if ($iw && $ih) { $pageW = $pdf->getPageWidth(); $pageH = $pdf->getPageHeight(); $margin = 10; $maxW = $pageW - 2 * $margin; $maxH = $pageH - 2 * $margin - 10; // 10mm für Notiz unten $ratio = min($maxW / $iw, $maxH / $ih); $w = $iw * $ratio; $h = $ih * $ratio; $x = ($pageW - $w) / 2; $y = $margin; $pdf->Image($full, $x, $y, $w, $h); bericht_burn_annotations($pdf, $page->fabric_json, $x, $y, $w, $h); } } elseif ($fpdi_loaded && preg_match('/\.pdf$/i', $full)) { try { $pdf->setSourceFile($full); $tpl = $pdf->importPage($page->source_page ?: 1); $size = $pdf->getTemplateSize($tpl); $pdf->AddPage($size['orientation'] ?? 'P', array($size['width'], $size['height'])); $pdf->useTemplate($tpl); // Annotationen über volle Seitenfläche bericht_burn_annotations($pdf, $page->fabric_json, 0, 0, $size['width'], $size['height']); } catch (Throwable $e) { // Seite überspringen } } // Notiz unten if (!empty($page->note)) { $pdf->SetY(-20); $pdf->SetFont('helvetica', 'I', 9); $pdf->MultiCell(0, 5, $page->note, 0, 'L'); } } // --- Speichern --- $dir_key = bericht_element_to_dir_key($bericht->element_type); $target_dir = $conf->{$dir_key}->multidir_output[$parent->entity].'/'.dol_sanitizeFileName($parent->ref); if (!is_dir($target_dir)) dol_mkdir($target_dir); $filename = 'Bericht_'.dol_sanitizeFileName($bericht->auftragsnummer ?: $bericht->ref).'_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'.pdf'; $target_path = $target_dir.'/'.$filename; $pdf->Output($target_path, 'F'); if (!file_exists($target_path)) bericht_ajax_fail('PDF wurde nicht erzeugt'); // In ECM registrieren (taucht unter Verknüpfte Dokumente auf) require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; $register_ecm = function ($obj_type, $obj_id, $filepath_dir, $filename, $target_path) use ($db, $user) { $ecmfile = new EcmFiles($db); $ecmfile->filepath = $filepath_dir; $ecmfile->filename = $filename; $ecmfile->fullpath_orig = $target_path; $ecmfile->src_object_type = $obj_type; $ecmfile->src_object_id = $obj_id; $ecmfile->label = md5_file($target_path); @$ecmfile->create($user); }; $register_ecm($dir_key, $parent->id, $dir_key.'/'.dol_sanitizeFileName($parent->ref), $filename, $target_path); // Wenn der Bericht zusätzlich per llx_element_element verknüpfte Elemente hat, // dort ebenfalls ECM-Eintrag anlegen + PDF-Kopie ablegen. $res_links = $db->query("SELECT targettype, fk_target FROM ".$db->prefix()."element_element WHERE sourcetype='bericht' AND fk_source=".((int) $bericht->id)); if ($res_links) { while ($lnk = $db->fetch_object($res_links)) { $tdir = bericht_element_to_dir_key($lnk->targettype === 'facture' ? 'invoice' : ($lnk->targettype === 'commande' ? 'order' : ($lnk->targettype === 'propal' ? 'propal' : $lnk->targettype))); if (!$tdir) continue; // Ziel-Objekt fetchen für ref $linked_obj = bericht_fetch_parent($db, $tdir === 'facture' ? 'invoice' : ($tdir === 'commande' ? 'order' : 'propal'), (int) $lnk->fk_target); if (!$linked_obj) continue; $linked_dir = $conf->{$tdir}->multidir_output[$linked_obj->entity].'/'.dol_sanitizeFileName($linked_obj->ref); if (!is_dir($linked_dir)) dol_mkdir($linked_dir); $linked_target = $linked_dir.'/'.$filename; @copy($target_path, $linked_target); $register_ecm($tdir, $linked_obj->id, $tdir.'/'.dol_sanitizeFileName($linked_obj->ref), $filename, $linked_target); } } // Bericht-Status auf Final $bericht->status = Bericht::STATUS_FINAL; $bericht->final_pdf_path = str_replace(DOL_DATA_ROOT.'/', '', $target_path); $bericht->update($user); bericht_ajax_ok(array( 'filename' => $filename, 'path' => $bericht->final_pdf_path, )); // bericht_render_cover, bericht_burn_annotations und bericht_hex_to_rgb // liegen jetzt in lib/bericht.lib.php (gemeinsam genutzt von generate_pdf und preview_pdf) function bericht_render_cover($template_path, $bericht, $parent, $tempdir) { return bericht_render_cover_internal($template_path, $bericht, $parent, $tempdir); }