All checks were successful
Deploy bericht / deploy (push) Successful in 2s
- Token-Tabelle: fk_bericht → fk_element + element_type (generisch)
- Migration: bestehende Tokens auf neue Spalten migrieren
- upload_photo API: Foto direkt nach commande/{ref}/, kein Bericht/BerichtPage mehr
- mobile_upload.php: Upload-Ziel über Token-Methode getUploadDir() ermitteln
- Token-Erstellung: element_id + element_type statt berichtid (abwärtskompatibel)
- QR-Modal: Token für Auftrag statt für Bericht; Polling auf Anhänge-Änderung [deploy]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
180 lines
6.7 KiB
PHP
180 lines
6.7 KiB
PHP
<?php
|
|
/* Upload-Token für Mobile-Foto-Upload zu einem Dolibarr-Objekt (Auftrag/Rechnung/Angebot).
|
|
* Token = 64 Hex-Zeichen, gültig 1h, max 100 Uploads.
|
|
* Fotos landen direkt im Dolibarr-Standard-Ordner des Objekts (z.B. commande/{ref}/).
|
|
*/
|
|
|
|
class BerichtUploadToken
|
|
{
|
|
public $db;
|
|
public $id;
|
|
public $token;
|
|
public $fk_element;
|
|
public $element_type;
|
|
public $fk_user_creat;
|
|
public $expires_at;
|
|
public $uploads_count = 0;
|
|
public $max_uploads = 100;
|
|
public $datec;
|
|
|
|
const DEFAULT_LIFETIME = 3600; // 1h
|
|
const DEFAULT_MAX_UPLOADS = 100;
|
|
|
|
public function __construct(DoliDB $db)
|
|
{
|
|
$this->db = $db;
|
|
}
|
|
|
|
/**
|
|
* Erstellt einen neuen Token für ein Dolibarr-Objekt.
|
|
* Räumt dabei abgelaufene Tokens gleich mit auf (kein Cronjob nötig).
|
|
* @param int $fk_element ID des Dolibarr-Objekts (z.B. Auftrags-ID)
|
|
* @param string $element_type Typ: 'order', 'invoice', 'propal'
|
|
* @param int $fk_user User-ID
|
|
* @param int $lifetime Gültigkeit in Sekunden (Standard: 3600)
|
|
* @param int $max_uploads Max. Uploads (Standard: 100)
|
|
* @return string|false Hex-Token bei Erfolg
|
|
*/
|
|
public function create($fk_element, $element_type, $fk_user, $lifetime = null, $max_uploads = null)
|
|
{
|
|
// Opportunistisches Cleanup: entferne abgelaufene Tokens bei jedem Insert
|
|
$this->db->query("DELETE FROM ".$this->db->prefix()."bericht_upload_token"
|
|
." WHERE expires_at < '".$this->db->idate(dol_now())."'", 1);
|
|
|
|
$this->token = bin2hex(random_bytes(32));
|
|
$this->fk_element = (int) $fk_element;
|
|
$this->element_type = $element_type;
|
|
$this->fk_user_creat = (int) $fk_user;
|
|
$this->datec = dol_now();
|
|
$this->expires_at = $this->datec + ($lifetime ?: self::DEFAULT_LIFETIME);
|
|
$this->max_uploads = $max_uploads ?: self::DEFAULT_MAX_UPLOADS;
|
|
$this->uploads_count = 0;
|
|
|
|
$sql = "INSERT INTO ".$this->db->prefix()."bericht_upload_token "
|
|
."(token, fk_element, element_type, fk_user_creat, expires_at, uploads_count, max_uploads, datec) VALUES ("
|
|
."'".$this->db->escape($this->token)."',"
|
|
.$this->fk_element.","
|
|
."'".$this->db->escape($this->element_type)."',"
|
|
.$this->fk_user_creat.","
|
|
."'".$this->db->idate($this->expires_at)."',"
|
|
."0,"
|
|
.$this->max_uploads.","
|
|
."'".$this->db->idate($this->datec)."'"
|
|
.")";
|
|
if (!$this->db->query($sql)) return false;
|
|
$this->id = $this->db->last_insert_id($this->db->prefix()."bericht_upload_token");
|
|
return $this->token;
|
|
}
|
|
|
|
/**
|
|
* Lädt einen Token und prüft Gültigkeit.
|
|
* @return BerichtUploadToken|null
|
|
*/
|
|
public static function fetchValid(DoliDB $db, $token)
|
|
{
|
|
if (!preg_match('/^[a-f0-9]{64}$/', $token)) return null;
|
|
$sql = "SELECT rowid, token, fk_element, element_type, fk_user_creat, expires_at, uploads_count, max_uploads, datec"
|
|
." FROM ".$db->prefix()."bericht_upload_token"
|
|
." WHERE token = '".$db->escape($token)."'"
|
|
." AND expires_at > '".$db->idate(dol_now())."'"
|
|
." AND uploads_count < max_uploads";
|
|
$res = $db->query($sql);
|
|
if (!$res || $db->num_rows($res) === 0) return null;
|
|
$obj = $db->fetch_object($res);
|
|
$t = new self($db);
|
|
$t->id = (int) $obj->rowid;
|
|
$t->token = $obj->token;
|
|
$t->fk_element = (int) $obj->fk_element;
|
|
$t->element_type = $obj->element_type;
|
|
$t->fk_user_creat = (int) $obj->fk_user_creat;
|
|
$t->expires_at = $db->jdate($obj->expires_at);
|
|
$t->uploads_count = (int) $obj->uploads_count;
|
|
$t->max_uploads = (int) $obj->max_uploads;
|
|
$t->datec = $db->jdate($obj->datec);
|
|
return $t;
|
|
}
|
|
|
|
/**
|
|
* Ermittelt den Upload-Ordner für dieses Token basierend auf element_type.
|
|
* @return string|false Absoluter Pfad zum Upload-Ordner, false bei Fehler
|
|
*/
|
|
public function getUploadDir()
|
|
{
|
|
global $conf;
|
|
$parent = $this->fetchParentObject();
|
|
if (!$parent) return false;
|
|
|
|
$ref = dol_sanitizeFileName($parent->ref);
|
|
switch ($this->element_type) {
|
|
case 'order':
|
|
return $conf->commande->multidir_output[$parent->entity].'/'.$ref;
|
|
case 'invoice':
|
|
return $conf->facture->multidir_output[$parent->entity].'/'.$ref;
|
|
case 'propal':
|
|
return $conf->propal->multidir_output[$parent->entity].'/'.$ref;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lädt das Dolibarr-Parent-Objekt (Auftrag/Rechnung/Angebot).
|
|
* @return CommonObject|false
|
|
*/
|
|
public function fetchParentObject()
|
|
{
|
|
switch ($this->element_type) {
|
|
case 'order':
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
$obj = new Commande($this->db);
|
|
break;
|
|
case 'invoice':
|
|
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
|
$obj = new Facture($this->db);
|
|
break;
|
|
case 'propal':
|
|
require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
|
|
$obj = new Propal($this->db);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if ($obj->fetch($this->fk_element) <= 0) return false;
|
|
return $obj;
|
|
}
|
|
|
|
public function incrementCount()
|
|
{
|
|
$this->uploads_count++;
|
|
return $this->db->query("UPDATE ".$this->db->prefix()."bericht_upload_token"
|
|
." SET uploads_count = uploads_count + 1"
|
|
." WHERE rowid = ".((int) $this->id));
|
|
}
|
|
|
|
/**
|
|
* Instanz-Methode für den Dolibarr-Cronjob.
|
|
* Räumt expired Tokens auf.
|
|
*
|
|
* @return int Anzahl gelöschter Tokens (>=0), -1 bei Fehler
|
|
*/
|
|
public function cleanupExpired()
|
|
{
|
|
$sql = "DELETE FROM ".$this->db->prefix()."bericht_upload_token"
|
|
." WHERE expires_at < '".$this->db->idate(dol_now())."'";
|
|
$res = $this->db->query($sql);
|
|
if (!$res) {
|
|
$this->error = $this->db->lasterror();
|
|
return -1;
|
|
}
|
|
return $this->db->affected_rows($res);
|
|
}
|
|
|
|
/**
|
|
* Statische Variante für Direktaufrufe außerhalb des Cronjobs.
|
|
*/
|
|
public static function cleanupExpiredStatic(DoliDB $db)
|
|
{
|
|
$db->query("DELETE FROM ".$db->prefix()."bericht_upload_token"
|
|
." WHERE expires_at < '".$db->idate(dol_now())."'");
|
|
}
|
|
}
|