<?php namespace App\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Rennokki\QueryCache\Traits\QueryCacheable; use App\Casts\{CollectionOrNull,CompressedString}; use App\Classes\FTN\Message; use App\Interfaces\Packet; use App\Traits\{EncodeUTF8,MsgID,ParseAddresses}; final class Echomail extends Model implements Packet { use SoftDeletes,EncodeUTF8,MsgID,QueryCacheable,ParseAddresses; private const LOGKEY = 'ME-'; private Collection $set_seenby; private Collection $set_path; private Carbon $set_recvtime; private string $set_pkt; private bool $no_export = FALSE; protected $casts = [ 'datetime' => 'datetime:Y-m-d H:i:s', 'kludges' => CollectionOrNull::class, 'msg' => CompressedString::class, 'msg_src' => CompressedString::class, 'rogue_seenby' => CollectionOrNull::class, 'rogue_path' => CollectionOrNull::class, ]; private const cast_utf8 = [ 'to', 'from', 'subject', 'msg', 'msg_src', 'origin', 'tearline', 'tagline', ]; public function __set($key,$value) { switch ($key) { case 'no_export': case 'set_path': case 'set_pkt': case 'set_recvtime': case 'set_seenby': $this->{$key} = $value; break; default: parent::__set($key,$value); } } public static function boot() { parent::boot(); // @todo if the message is updated with new SEEN-BY's from another route, we'll delete the pending export for systems (if there is one) static::created(function($model) { $rogue = collect(); $seenby = NULL; $path = []; // Parse PATH if ($model->set_path->count()) $path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue); // Save the Path $ppoid = NULL; foreach ($path as $aoid) { $po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[ $model->id, $aoid, $ppoid, ]); $ppoid = $po[0]->id; } $rogue = collect(); // Parse SEEN-BY if ($model->set_seenby->count()) $seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue); if (count($rogue)) { $model->rogue_seenby = $rogue; $model->save(); } if ($seenby) $model->seenby()->sync($seenby); // Our last node in the path is our sender if (isset($model->set_pkt) && isset($model->set_recvtime)) { DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[ $model->set_pkt, $model->set_recvtime, $path->last(), $model->id, ]); } // See if we need to export this message. if ($model->echoarea->sec_read) { $exportto = ($x=$model ->echoarea ->addresses ->filter(function($item) use ($model) { return $item->security >= $model->echoarea->sec_read; })) ->pluck('id') ->diff($seenby); if ($exportto->count()) { if ($model->no_export) { Log::alert(sprintf('%s:- NOT processing exporting of message by configuration [%s] to [%s]',self::LOGKEY,$model->id,$exportto->join(','))); return; } Log::info(sprintf('%s:- Exporting message [%s] to [%s]',self::LOGKEY,$model->id,$exportto->join(','))); // Save the seenby for the exported systems $model->seenby()->syncWithPivotValues($exportto,['export_at'=>Carbon::now()],FALSE); } } }); } /* RELATIONS */ public function echoarea() { return $this->belongsTo(Echoarea::class); } public function fftn() { return $this->belongsTo(Address::class) ->withTrashed(); } public function seenby() { return $this->belongsToMany(Address::class,'echomail_seenby') ->FTN2DOrder(); } public function path() { return $this->belongsToMany(Address::class,'echomail_path') ->withPivot(['id','parent_id']); } /* METHODS */ public function init(): void { $this->set_path = collect(); $this->set_seenby = collect(); } public function jsonSerialize(): array { return $this->encode(); } /** * Return this model as a packet */ public function packet(Address $ao): Message { Log::info(sprintf('%s:+ Bundling [%s]',self::LOGKEY,$this->id)); // @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted $o = new Message; $o->header = [ 'onode' => $this->fftn->node_id, 'dnode' => $ao->node_id, 'onet' => $this->fftn->host_id, 'dnet' => $ao->host_id, 'flags' => 0, 'cost' => 0, 'date'=>$this->datetime->format('d M y H:i:s'), ]; $o->tzutc = $this->datetime->utcOffset($this->tzoffset)->getOffsetString(''); $o->user_to = $this->to; $o->user_from = $this->from; $o->subject = $this->subject; $o->echoarea = $this->echoarea->name; $o->flags = $this->flags; if ($this->kludges) $o->kludge = collect($this->kludges); $o->kludge->put('dbid',$this->id); $o->msgid = $this->msgid; if ($this->replyid) $o->replyid = $this->replyid; $o->message = $this->msg; if ($this->tagline) $o->tagline = $this->tagline; if ($this->tearline) $o->tearline = $this->tearline; if ($this->origin) $o->origin = $this->origin; $sysaddress = Setup::findOrFail(config('app.id'))->system->match($this->fftn->zone)->first(); if (! $sysaddress) throw new \Exception(sprintf('%s:! We dont have an address in this network? (%s)',self::LOGKEY,$this->fftn->zone->domain->name)); $o->seenby = $this->seenby->push($sysaddress)->unique()->pluck('ftn2d'); // Add our address to the path and seenby $o->path = $this->pathorder()->merge($sysaddress->ftn2d); $o->packed = TRUE; return $o; } public function pathorder(string $display='ftn2d',int $start=NULL): Collection { $result = collect(); if ($x=$this->path->firstWhere('pivot.parent_id',$start)) { $result->push($x->$display); $result->push($this->pathorder($display,$x->pivot->id)); } return $result->flatten()->filter(); } }