'msgid', 'REPLY:'=>'replyid', 'Via' => 'set_path', ]; protected $casts = [ 'to' => UTF8StringOrNull::class, 'from' => UTF8StringOrNull::class, 'subject' => UTF8StringOrNull::class, 'datetime' => 'datetime:Y-m-d H:i:s', 'kludges' => CollectionOrNull::class, 'msg' => CompressedStringOrNull::class, 'msg_src' => CompressedStringOrNull::class, 'sent_at' => 'datetime:Y-m-d H:i:s', ]; public function __get($key) { switch ($key) { case 'get_fftn': return $this->set->get('set_fftn') ?: $this->fftn->ftn; case 'get_tftn': return $this->set->get('set_tftn') ?: $this->tftn->ftn; case 'set_fftn': case 'set_tftn': case 'set_path': case 'set_pkt': case 'set_recvtime': case 'set_seenby': case 'set_sender': case 'set_tagline': case 'set_tearline': case 'set_origin': return $this->set->get($key); default: return parent::__get($key); } } public function __set($key,$value) { switch ($key) { case 'kludges': if (! count($value)) return; if (array_key_exists($value[0],self::kludges)) { $this->{self::kludges[$value[0]]} = $value[1]; } else { $this->kludges->put($value[0],$value[1]); } break; case 'set_fftn': case 'set_tftn': // Values that we pass to boot() to record how we got this netmail case 'set_pkt': case 'set_recvtime': case 'set_sender': case 'set_tagline': case 'set_tearline': case 'set_origin': $this->set->put($key,$value); break; // The path the netmail went through to get here case 'set_path': if (! $this->set->has($key)) $this->set->put($key,collect()); $this->set->get($key)->push($value); break; default: parent::__set($key,$value); } } public static function boot() { parent::boot(); static::creating(function($model) { if (isset($model->errors) && $model->errors->count()) throw new \Exception('Cannot save, validation errors exist'); if ($model->set->has('set_tagline')) { $x = Tagline::where('value',utf8_encode($model->set_tagline))->single(); if (! $x) { $x = new Tagline; $x->value = $model->set_tagline; $x->save(); } $model->tagline_id = $x->id; } if ($model->set->has('set_tearline')) { $x = Tearline::where('value',utf8_encode($model->set_tearline))->single(); if (! $x) { $x = new Tearline; $x->value = $model->set_tearline; $x->save(); } $model->tearline_id = $x->id; } if ($model->set->has('set_origin')) { // Make sure our origin contains our FTN $m = []; if ((preg_match('#^(.*)\s+\(([0-9]+:[0-9]+/[0-9]+.*)\)+\s*$#',$model->set_origin,$m)) && (Address::findFTN(sprintf('%s@%s',$m[2],$model->fftn->domain->name),TRUE,TRUE)?->id === $model->fftn_id)) { $x = Origin::where('value',utf8_encode($m[1]))->single(); if (! $x) { $x = new Origin; $x->value = $m[1]; $x->save(); } $model->origin_id = $x->id; } } // If we can rebuild the message content, then we can do away with msg_src if (md5($model->rebuildMessage()) === $model->msg_crc) { Log::debug(sprintf('%s:- Pruning message source, since we can rebuild the message [%s]',self::LOGKEY,$model->msgid)); $model->msg_src = NULL; } }); static::created(function($model) { $nodes = collect(); // Parse PATH // @YYYYMMDD.HHMMSS[.Precise][.Time Zone] [Serial Number] if ($model->set->has('set_path')) { foreach ($model->set->get('set_path') as $line) { $m = []; if (preg_match(self::PATH_REGEX,$line,$m)) { // Address // @todo Do we need to add a domain here, since the path line may not include one $ao = Address::findFTN($m[1]); // Time $t = []; $datetime = ''; if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t)) Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3])); else $datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? ''); if (! $ao) { Log::alert(sprintf('%s:! Undefined Node [%s] in netmail path.',self::LOGKEY,$m[1])); //$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]); } else { $nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]); } } } // If there are no details (Mystic), we'll create a blank } elseif ($model->set->has('set_sender')) { $nodes->push(['node'=>$model->set->get('set_sender'),'datetime'=>Carbon::now(),'program'=>'Unknown']); } // Save the Path $ppoid = NULL; foreach ($nodes as $path) { $po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[ $model->id, $path['node']->id, $ppoid, (string)$path['datetime'], $path['program'], ]); $ppoid = $po[0]->id; } // Our last node in the path is our sender if ($nodes->count() && $model->set->has('set_pkt') && $model->set->has('set_sender') && $model->set->has('set_recvtime')) { DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[ $model->set->get('set_pkt'), $model->set->get('set_recvtime'), $model->set->get('set_sender')->id, Arr::get($nodes->last(),'node')->id, $model->id, ]); } $model->save(); }); } /* RELATIONS */ public function path() { return $this->belongsToMany(Address::class,'netmail_path') ->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id']) ->orderBy('netmail_path.id') ->using(ViaPivot::class); } public function sent() { return $this->belongsTo(Address::class); } public function tftn() { return $this ->belongsTo(Address::class) ->withTrashed(); } /* ATTRIBUTES */ /** * Enable rendering the path even if the model hasnt been saved * * @return Collection */ public function getPathAttribute(): Collection { return ((! $this->exists) && $this->set->has('set_path')) ? $this->set->get('set_path')->map(function($item) { $m = []; preg_match(self::PATH_REGEX,$item,$m); return $m[1]; }) : $this->getRelationValue('path'); } /* METHODS */ /** * Render the via line * * @param Address $ao * @return string * @throws \Exception */ public function via(Address $ao): string { if (! $ao->pivot) throw new \Exception('Cannot render the via line without an address record without a path pivot'); return sprintf('%s @%s.UTC %s', $ao->ftn3d, $ao->pivot->datetime->format('Ymd.His'), $ao->pivot->program); } }