bericht/class/upload_token.class.php
Eduard Wisch 1705744809
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
fix: Cronjob entfernt, Cleanup opportunistisch beim Token-Create
Der Dolibarr-Cron-Scheduler ist auf Prod nicht aktiv, deshalb hing
der tägliche Cleanup-Job 'Expired Upload-Tokens bereinigen' auf
'Geplant' und GlobalNotify meldete ihn als hängenden Job.

Lösung: Cronjob komplett aus dem Modul entfernt. Das Cleanup läuft
jetzt opportunistisch bei jedem neuen Token-Insert:

BerichtUploadToken::create() führt vor dem INSERT ein
DELETE FROM llx_bericht_upload_token WHERE expires_at < NOW()
aus. Das ist minimal teurer (1 Query extra pro Token-Create,
~0ms bei leerer Tabelle), aber vollständig cron-unabhängig.

WICHTIG für Prod: Nach Deploy muss der bestehende Cron-Eintrag
manuell aus llx_cronjob gelöscht werden (oder das Modul einmal
deaktiviert + reaktiviert werden, dann wird er mit remove() bzw.
init() neu gesetzt — ohne Eintrag). GlobalNotify ist danach ruhig.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:58:39 +02:00

121 lines
4.3 KiB
PHP

<?php
/* Upload-Token für Mobile-Upload eines Berichts.
* Token = 64 Hex-Zeichen, gültig 1h, max 100 Uploads.
*/
class BerichtUploadToken
{
public $db;
public $id;
public $token;
public $fk_bericht;
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 einen Bericht.
* Räumt dabei abgelaufene Tokens gleich mit auf (kein Cronjob nötig).
* @return string|false Hex-Token bei Erfolg
*/
public function create($fk_bericht, $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_bericht = (int) $fk_bericht;
$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_bericht, fk_user_creat, expires_at, uploads_count, max_uploads, datec) VALUES ("
."'".$this->db->escape($this->token)."',"
.$this->fk_bericht.","
.$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_bericht, 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_bericht = (int) $obj->fk_bericht;
$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;
}
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())."'");
}
}