All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Neuer "Dokument scannen" Button in der Mobile-Upload-Seite: - Eigene Scanner-View für mehrseitige Dokumente - Seiten-Vorschau-Leiste mit Thumbnails - Seiten hinzufügen/löschen vor dem Upload - Backend-Verarbeitung zu PDF OCR-Verarbeitung (wenn verfügbar): - ocrmypdf für durchsuchbares PDF (deu+eng) - Automatische Rotation und Schräglagekorrektur - Fallback auf ImageMagick oder TCPDF Server-Konfiguration (optional für OCR): apt install tesseract-ocr tesseract-ocr-deu ocrmypdf [deploy] Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
249 lines
8.4 KiB
PHP
249 lines
8.4 KiB
PHP
<?php
|
|
/* Verarbeitet gescannte Dokumentseiten zu einem durchsuchbaren PDF.
|
|
* KEIN Dolibarr-Login nötig — Authentifizierung über Token in der URL.
|
|
* Verwendet ocrmypdf für OCR (optional), Fallback auf einfaches Bild-PDF.
|
|
*
|
|
* POST: token, pages[] (multipart)
|
|
*/
|
|
|
|
if (!defined('NOLOGIN')) define('NOLOGIN', '1');
|
|
if (!defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1');
|
|
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
|
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
|
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
|
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
|
|
|
$res = 0;
|
|
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
|
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
|
|
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
|
|
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
|
|
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
|
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
|
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
|
if (!$res) die("Include of main fails");
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
|
require_once __DIR__.'/../class/upload_token.class.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
function fail($msg, $code = 400)
|
|
{
|
|
http_response_code($code);
|
|
echo json_encode(array('success' => false, 'error' => $msg));
|
|
exit;
|
|
}
|
|
|
|
// Token validieren
|
|
$token = (string) ($_REQUEST['token'] ?? '');
|
|
$tok = BerichtUploadToken::fetchValid($db, $token);
|
|
if (!$tok) {
|
|
fail('Token ungültig oder abgelaufen', 403);
|
|
}
|
|
|
|
// Upload-Ziel ermitteln
|
|
$upload_dir = $tok->getUploadDir();
|
|
if (!$upload_dir) {
|
|
fail('Objekt nicht gefunden', 404);
|
|
}
|
|
|
|
// Seiten prüfen
|
|
if (empty($_FILES['pages']) || !is_array($_FILES['pages']['tmp_name'])) {
|
|
fail('Keine Seiten hochgeladen');
|
|
}
|
|
|
|
$pages = $_FILES['pages'];
|
|
$page_count = count($pages['tmp_name']);
|
|
|
|
if ($page_count === 0) {
|
|
fail('Keine Seiten hochgeladen');
|
|
}
|
|
|
|
// Temp-Verzeichnis für Verarbeitung
|
|
$tmpdir = sys_get_temp_dir().'/bericht_scan_'.uniqid();
|
|
if (!mkdir($tmpdir, 0755, true)) {
|
|
fail('Temp-Verzeichnis konnte nicht erstellt werden');
|
|
}
|
|
|
|
$cleanup = function() use ($tmpdir) {
|
|
// Temp-Dateien aufräumen
|
|
array_map('unlink', glob($tmpdir.'/*'));
|
|
@rmdir($tmpdir);
|
|
};
|
|
|
|
// Seiten speichern und optimieren
|
|
$image_files = array();
|
|
for ($i = 0; $i < $page_count; $i++) {
|
|
if (empty($pages['tmp_name'][$i])) continue;
|
|
|
|
$ext = strtolower(pathinfo($pages['name'][$i], PATHINFO_EXTENSION));
|
|
if (!in_array($ext, array('jpg', 'jpeg', 'png'))) {
|
|
$cleanup();
|
|
fail('Nur JPG/PNG erlaubt');
|
|
}
|
|
|
|
// Seite nummeriert speichern
|
|
$page_file = sprintf('%s/page_%03d.jpg', $tmpdir, $i + 1);
|
|
|
|
// Mit ImageMagick optimieren (Kontrast, Größe)
|
|
$tmp_file = $pages['tmp_name'][$i];
|
|
|
|
// Prüfen ob convert (ImageMagick) verfügbar ist
|
|
$convert_bin = '/usr/bin/convert';
|
|
if (!file_exists($convert_bin)) {
|
|
$convert_bin = trim(shell_exec('which convert 2>/dev/null'));
|
|
}
|
|
|
|
if ($convert_bin && file_exists($convert_bin)) {
|
|
// Bild optimieren: Kontrast erhöhen, Größe begrenzen, als JPG speichern
|
|
$cmd = escapeshellcmd($convert_bin).' '
|
|
.escapeshellarg($tmp_file)
|
|
.' -resize 2400x2400\> ' // Max 2400px, Seitenverhältnis beibehalten
|
|
.' -normalize ' // Kontrast optimieren
|
|
.' -quality 92 '
|
|
.escapeshellarg($page_file).' 2>&1';
|
|
exec($cmd, $output, $ret);
|
|
|
|
if ($ret !== 0 || !file_exists($page_file)) {
|
|
// Fallback: einfach kopieren
|
|
copy($tmp_file, $page_file);
|
|
}
|
|
} else {
|
|
// Kein ImageMagick: einfach kopieren
|
|
copy($tmp_file, $page_file);
|
|
}
|
|
|
|
if (file_exists($page_file)) {
|
|
$image_files[] = $page_file;
|
|
}
|
|
}
|
|
|
|
if (empty($image_files)) {
|
|
$cleanup();
|
|
fail('Keine gültigen Seiten verarbeitet');
|
|
}
|
|
|
|
// Ziel-Dateiname
|
|
if (!is_dir($upload_dir)) dol_mkdir($upload_dir);
|
|
$timestamp = dol_print_date(dol_now(), '%Y%m%d_%H%M%S');
|
|
$pdf_filename = 'scan_'.$timestamp.'_'.uniqid().'.pdf';
|
|
$pdf_path = $upload_dir.'/'.$pdf_filename;
|
|
|
|
// PDF erstellen
|
|
$pdf_created = false;
|
|
|
|
// Methode 1: ocrmypdf (bevorzugt - erzeugt durchsuchbares PDF)
|
|
$ocrmypdf_bin = getDolGlobalString('BERICHT_OCRMYPDF_BIN', '/usr/bin/ocrmypdf');
|
|
if (!file_exists($ocrmypdf_bin)) {
|
|
$ocrmypdf_bin = trim(shell_exec('which ocrmypdf 2>/dev/null'));
|
|
}
|
|
|
|
if ($ocrmypdf_bin && file_exists($ocrmypdf_bin)) {
|
|
// Erst Bilder zu PDF zusammenfügen mit img2pdf oder convert
|
|
$img2pdf_bin = trim(shell_exec('which img2pdf 2>/dev/null'));
|
|
$temp_pdf = $tmpdir.'/temp.pdf';
|
|
|
|
if ($img2pdf_bin && file_exists($img2pdf_bin)) {
|
|
// img2pdf ist schneller und verlustfrei
|
|
$cmd = escapeshellcmd($img2pdf_bin).' -o '.escapeshellarg($temp_pdf);
|
|
foreach ($image_files as $f) {
|
|
$cmd .= ' '.escapeshellarg($f);
|
|
}
|
|
exec($cmd.' 2>&1', $output, $ret);
|
|
} elseif ($convert_bin && file_exists($convert_bin)) {
|
|
// Fallback: ImageMagick convert
|
|
$cmd = escapeshellcmd($convert_bin);
|
|
foreach ($image_files as $f) {
|
|
$cmd .= ' '.escapeshellarg($f);
|
|
}
|
|
$cmd .= ' '.escapeshellarg($temp_pdf);
|
|
exec($cmd.' 2>&1', $output, $ret);
|
|
}
|
|
|
|
if (file_exists($temp_pdf)) {
|
|
// OCR mit ocrmypdf
|
|
$lang = getDolGlobalString('BERICHT_OCR_LANG', 'deu+eng');
|
|
$cmd = escapeshellcmd($ocrmypdf_bin)
|
|
.' -l '.escapeshellarg($lang)
|
|
.' --rotate-pages ' // Automatische Rotation
|
|
.' --deskew ' // Schräglage korrigieren
|
|
.' --clean ' // Hintergrund bereinigen
|
|
.' --optimize 1 ' // Leichte Optimierung
|
|
.' '.escapeshellarg($temp_pdf)
|
|
.' '.escapeshellarg($pdf_path)
|
|
.' 2>&1';
|
|
exec($cmd, $output, $ret);
|
|
|
|
if ($ret === 0 && file_exists($pdf_path)) {
|
|
$pdf_created = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Methode 2: Fallback mit ImageMagick (kein OCR)
|
|
if (!$pdf_created && $convert_bin && file_exists($convert_bin)) {
|
|
$cmd = escapeshellcmd($convert_bin);
|
|
foreach ($image_files as $f) {
|
|
$cmd .= ' '.escapeshellarg($f);
|
|
}
|
|
$cmd .= ' -quality 92 '.escapeshellarg($pdf_path).' 2>&1';
|
|
exec($cmd, $output, $ret);
|
|
|
|
if ($ret === 0 && file_exists($pdf_path)) {
|
|
$pdf_created = true;
|
|
}
|
|
}
|
|
|
|
// Methode 3: Fallback mit TCPDF (PHP-only)
|
|
if (!$pdf_created) {
|
|
// TCPDF sollte in Dolibarr verfügbar sein
|
|
if (file_exists(DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf.php')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf.php';
|
|
|
|
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
|
$pdf->SetCreator('Dolibarr Bericht-Modul');
|
|
$pdf->SetAuthor('Dokumenten-Scanner');
|
|
$pdf->SetTitle('Gescanntes Dokument');
|
|
$pdf->SetMargins(0, 0, 0);
|
|
$pdf->SetAutoPageBreak(false);
|
|
$pdf->setPrintHeader(false);
|
|
$pdf->setPrintFooter(false);
|
|
|
|
foreach ($image_files as $img) {
|
|
$pdf->AddPage();
|
|
$size = getimagesize($img);
|
|
if ($size) {
|
|
$w = $size[0];
|
|
$h = $size[1];
|
|
// Bild auf A4 skalieren (210x297mm)
|
|
$ratio = min(210 / ($w * 0.264583), 297 / ($h * 0.264583));
|
|
$pdf->Image($img, 0, 0, $w * 0.264583 * $ratio, $h * 0.264583 * $ratio);
|
|
}
|
|
}
|
|
|
|
$pdf->Output($pdf_path, 'F');
|
|
|
|
if (file_exists($pdf_path)) {
|
|
$pdf_created = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Aufräumen
|
|
$cleanup();
|
|
|
|
if (!$pdf_created) {
|
|
fail('PDF konnte nicht erstellt werden. Bitte prüfen Sie die Server-Konfiguration (ImageMagick/ocrmypdf).');
|
|
}
|
|
|
|
// Token-Counter erhöhen
|
|
$tok->incrementCount();
|
|
|
|
// Erfolg
|
|
echo json_encode(array(
|
|
'success' => true,
|
|
'filename' => $pdf_filename,
|
|
'pages' => count($image_files),
|
|
'ocr' => ($ocrmypdf_bin && file_exists($ocrmypdf_bin)) ? true : false,
|
|
));
|