Foto-Upload: identische Datei kein zweites Mal ablegen (v1.6.0) [deploy]
An Auftrag (PROV105) lagen 34 Fotos, davon 11 exakte Wiederholungen: eine Serie lief bei ausgeschaltetem Display nur halb durch, der zweite Anlauf lud alles noch einmal hoch. - bericht_find_identical_file() vergleicht die hochgeladene Datei gegen den Zielordner (Groesse als Vorfilter, md5 nur bei Kandidaten) - eingebaut in beide Upload-Wege: api/orders.php?action=upload_photo (PWA) und mobile_upload.php (Token-Link) - bei Treffer wird nichts gespeichert, die Antwort traegt duplicate:true UND den relpath der vorhandenen Datei — ohne relpath wertet die PWA den Upload als nicht bestaetigt und wiederholt ihn endlos (lib/api.js) Funktion isoliert geprueft: identischer Inhalt wird gefunden, neuer Inhalt und gleiche Groesse mit anderem Inhalt liefern null (kein Fehlalarm). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d3edc6ebfc
commit
6949702f5a
4 changed files with 85 additions and 0 deletions
21
ChangeLog.md
21
ChangeLog.md
|
|
@ -1,5 +1,26 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.6.0 — 2026-08-28
|
||||||
|
|
||||||
|
### Dasselbe Foto kommt kein zweites Mal in den Auftragsordner
|
||||||
|
|
||||||
|
Am 28.08. lagen an Auftrag `(PROV105)` **34 Fotos, davon 11 exakte Wiederholungen** — eine
|
||||||
|
Serie war bei ausgeschaltetem Display nur halb durchgelaufen, der zweite Anlauf lud alles
|
||||||
|
noch einmal hoch. Der Upload ist bewusst *at-least-once* (KB #840: lieber ein Duplikat als
|
||||||
|
ein verlorenes Foto), aber er war eben auch duplikaterzeugend.
|
||||||
|
|
||||||
|
- **`bericht_find_identical_file()`** (lib/bericht.lib.php) vergleicht die hochgeladene Datei
|
||||||
|
mit dem Inhalt des Zielordners: erst die Dateigroesse, md5 nur bei einem Kandidaten.
|
||||||
|
- Eingebaut in **beide** Upload-Wege: `api/orders.php?action=upload_photo` (Baustelle-PWA,
|
||||||
|
JWT) und `mobile_upload.php` (Token-Link).
|
||||||
|
- Bei einem Treffer wird nichts abgelegt, die Antwort traegt `duplicate: true` — **und
|
||||||
|
trotzdem den `relpath` der vorhandenen Datei**. Das ist Pflicht: die PWA wertet eine
|
||||||
|
Antwort ohne `relpath` als „nicht bestaetigt" und haette das Foto sonst endlos
|
||||||
|
weiterversucht (lib/api.js).
|
||||||
|
|
||||||
|
Damit ist die Wiederholung idempotent statt duplikaterzeugend; die at-least-once-Regel bleibt
|
||||||
|
als Rueckfallebene bestehen.
|
||||||
|
|
||||||
## 1.5.1 — 2026-08-22
|
## 1.5.1 — 2026-08-22
|
||||||
|
|
||||||
Die API-Seite zur Baustelle-PWA holt nach, was der Editor mit 1.5.0 bekommen hat.
|
Die API-Seite zur Baustelle-PWA holt nach, was der Editor mit 1.5.0 bekommen hat.
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,20 @@ if ($action === 'upload_photo' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$upload_dir = $conf->commande->multidir_output[$cmd->entity].'/'.dol_sanitizeFileName($cmd->ref);
|
$upload_dir = $conf->commande->multidir_output[$cmd->entity].'/'.dol_sanitizeFileName($cmd->ref);
|
||||||
if (!is_dir($upload_dir)) dol_mkdir($upload_dir);
|
if (!is_dir($upload_dir)) dol_mkdir($upload_dir);
|
||||||
|
|
||||||
|
// Liegt dasselbe Foto schon im Ordner? Dann kein zweites Mal ablegen.
|
||||||
|
// Wichtig: trotzdem relpath zurueckgeben — die PWA wertet eine Antwort OHNE relpath
|
||||||
|
// als "nicht bestaetigt" (lib/api.js) und haette das Foto sonst ewig in der Queue.
|
||||||
|
$dup = bericht_find_identical_file($upload_dir, $_FILES['file']['tmp_name']);
|
||||||
|
if ($dup !== null) {
|
||||||
|
$existing = $upload_dir.'/'.$dup;
|
||||||
|
api_ok(array(
|
||||||
|
'filename' => $dup,
|
||||||
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $existing),
|
||||||
|
'size' => filesize($existing),
|
||||||
|
'duplicate' => true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
$filename = 'foto_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'_'.uniqid().'.'.$ext;
|
$filename = 'foto_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'_'.uniqid().'.'.$ext;
|
||||||
$target = $upload_dir.'/'.$filename;
|
$target = $upload_dir.'/'.$filename;
|
||||||
if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) api_fail('Upload fehlgeschlagen', 500);
|
if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) api_fail('Upload fehlgeschlagen', 500);
|
||||||
|
|
|
||||||
|
|
@ -1602,3 +1602,44 @@ function bericht_render_cover_internal($template_path, $bericht, $parent, $tempd
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht im Zielordner eine inhaltlich identische Datei zur hochgeladenen Datei.
|
||||||
|
*
|
||||||
|
* Hintergrund: Der Foto-Upload ist bewusst at-least-once (KB #840) — geht die Antwort
|
||||||
|
* nach dem Speichern verloren, wiederholt die PWA den Upload, und dasselbe Foto lag
|
||||||
|
* danach doppelt im Auftragsordner. Dasselbe passiert, wenn nach einem abgebrochenen
|
||||||
|
* Durchgang dieselbe Auswahl noch einmal hochgeladen wird (real am 28.08.2026 an
|
||||||
|
* Auftrag (PROV105): 34 Dateien, davon 11 identische Wiederholungen).
|
||||||
|
* Mit dem Inhalts-Vergleich wird der Upload idempotent, ohne die Queue-Semantik
|
||||||
|
* anzutasten: der Aufrufer meldet den Treffer als Erfolg zurueck.
|
||||||
|
*
|
||||||
|
* Vergleich zuerst ueber die Dateigroesse (billig), erst bei Gleichstand ueber md5.
|
||||||
|
*
|
||||||
|
* @param string $dir Zielverzeichnis (absoluter Pfad)
|
||||||
|
* @param string $tmpfile Hochgeladene temporaere Datei
|
||||||
|
* @return string|null Dateiname der bereits vorhandenen identischen Datei, sonst null
|
||||||
|
*/
|
||||||
|
function bericht_find_identical_file($dir, $tmpfile)
|
||||||
|
{
|
||||||
|
if (!is_dir($dir) || !is_readable($tmpfile)) return null;
|
||||||
|
|
||||||
|
$size = filesize($tmpfile);
|
||||||
|
if ($size === false || $size <= 0) return null;
|
||||||
|
|
||||||
|
$files = scandir($dir);
|
||||||
|
if ($files === false) return null;
|
||||||
|
|
||||||
|
$hash = null;
|
||||||
|
foreach ($files as $f) {
|
||||||
|
if ($f === '.' || $f === '..') continue;
|
||||||
|
$full = $dir.'/'.$f;
|
||||||
|
if (!is_file($full)) continue;
|
||||||
|
if (filesize($full) !== $size) continue; // andere Groesse = anderer Inhalt
|
||||||
|
if ($hash === null) $hash = md5_file($tmpfile); // erst hashen, wenn ein Kandidat da ist
|
||||||
|
if ($hash === false) return null;
|
||||||
|
if (md5_file($full) === $hash) return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ if (!$res) die("Include of main fails");
|
||||||
|
|
||||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||||
require_once __DIR__.'/class/upload_token.class.php';
|
require_once __DIR__.'/class/upload_token.class.php';
|
||||||
|
require_once __DIR__.'/lib/bericht.lib.php';
|
||||||
|
|
||||||
$token = (string) ($_REQUEST['token'] ?? '');
|
$token = (string) ($_REQUEST['token'] ?? '');
|
||||||
$tok = BerichtUploadToken::fetchValid($db, $token);
|
$tok = BerichtUploadToken::fetchValid($db, $token);
|
||||||
|
|
@ -62,6 +63,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_FILES['file']['tmp_name'])
|
||||||
|
|
||||||
if (!is_dir($upload_dir)) dol_mkdir($upload_dir);
|
if (!is_dir($upload_dir)) dol_mkdir($upload_dir);
|
||||||
|
|
||||||
|
// Dasselbe Foto schon da? Als Erfolg melden, aber nichts zusaetzlich ablegen
|
||||||
|
// (gleiche Regel wie in api/orders.php).
|
||||||
|
$dup = bericht_find_identical_file($upload_dir, $_FILES['file']['tmp_name']);
|
||||||
|
if ($dup !== null) {
|
||||||
|
echo json_encode(array('success' => true, 'filename' => $dup, 'duplicate' => true));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$filename = 'foto_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'_'.uniqid().'.'.$ext;
|
$filename = 'foto_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'_'.uniqid().'.'.$ext;
|
||||||
$target = $upload_dir.'/'.$filename;
|
$target = $upload_dir.'/'.$filename;
|
||||||
if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
|
if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue