'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_sender': 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 = collect(); $path = collect(); // Parse PATH if ($model->set_path->count()) $path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue); // Make sure our sender is first in the path if (! $path->contains($model->fftn_id)) { Log::alert(sprintf('%s:? Echomail adding sender to start of PATH [%s].',self::LOGKEY,$model->fftn_id)); $path->prepend($model->fftn_id); } // Make sure our pktsrc is last in the path if (isset($model->set_sender) && (! $path->contains($model->set_sender))) { Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set_sender)); $path->push($model->set_sender); } // 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(); // @todo move the parseAddress processing into Message::class, and our address to the seenby (and thus no need to add it when we export) // Parse SEEN-BY if ($model->set_seenby->count()) $seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue); // Make sure our sender is in the seenby if (! $seenby->contains($model->fftn_id)) { Log::alert(sprintf('%s:? Echomail adding sender to SEENBY [%s].',self::LOGKEY,$model->fftn_id)); $seenby->push($model->fftn_id); } // Make sure our pktsrc is in the seenby if (isset($model->set_sender) && (! $seenby->contains($model->set_sender))) { Log::alert(sprintf('%s:? Echomail adding pktsrc to SEENBY [%s].',self::LOGKEY,$model->set_sender)); $seenby->push($model->set_sender); } 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)) { if ($path->count()) { 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, ]); } else { Log::critical(sprintf('%s:! Wasnt able to set packet details for [%d] to [%s] to [%s], no path information',self::LOGKEY,$model->id,$model->set_pkt,$model->set_recvtime)); } } // 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') ->withPivot(['export_at','sent_at','sent_pkt']) ->FTN2DOrder(); } public function path() { return $this->belongsToMany(Address::class,'echomail_path') ->withPivot(['id','parent_id','recv_pkt','recv_at']); } /* 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)); $sysaddress = our_address($this->fftn->zone->domain,$this->fftn); if (! $sysaddress) throw new \Exception(sprintf('%s:! We dont have an address in this network? (%s)',self::LOGKEY,$this->fftn->zone->domain->name)); // @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted $o = new Message; $o->header = [ 'onode' => $sysaddress->node_id, 'dnode' => $ao->node_id, 'onet' => $sysaddress->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; $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(); } }