287 lines
7.0 KiB
PHP
287 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
use App\Casts\{CollectionOrNull,CompressedStringOrNull,UTF8StringOrNull};
|
|
use App\Interfaces\Packet;
|
|
use App\Pivots\ViaPivot;
|
|
use App\Traits\{MessageAttributes,MsgID};
|
|
|
|
final class Netmail extends Model implements Packet
|
|
{
|
|
use SoftDeletes,MsgID,MessageAttributes;
|
|
|
|
private const LOGKEY = 'MN-';
|
|
private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';
|
|
|
|
/**
|
|
* Kludges that we absorb in this model
|
|
*/
|
|
private const kludges = [
|
|
'MSGID:'=>'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 '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))?->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
|
|
// <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [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 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);
|
|
}
|
|
} |