2019-04-27 13:57:39 +00:00
< ? php
namespace App\Models ;
2021-09-06 13:39:32 +00:00
use Carbon\Carbon ;
2022-01-01 05:59:35 +00:00
use Illuminate\Database\Eloquent\Model ;
use Illuminate\Database\Eloquent\SoftDeletes ;
2022-01-22 12:08:46 +00:00
use Illuminate\Support\Collection ;
2021-08-29 14:44:20 +00:00
use Illuminate\Support\Facades\DB ;
2021-09-06 13:39:32 +00:00
use Illuminate\Support\Facades\Log ;
2019-04-27 13:57:39 +00:00
2024-05-23 10:11:32 +00:00
use App\Classes\FTN\Message ;
2024-06-11 04:17:03 +00:00
use App\Events\Echomail as EchomailEvent ;
2021-07-30 14:35:52 +00:00
use App\Interfaces\Packet ;
2024-11-04 07:25:49 +00:00
use App\Models\Casts\ { CompressedStringOrNull , CollectionOrNull , UTF8StringOrNull };
2024-11-27 07:41:27 +00:00
use App\Traits\ { MessageAttributes , MsgID , ParseAddresses };
2021-07-30 14:35:52 +00:00
2021-09-06 13:39:32 +00:00
final class Echomail extends Model implements Packet
2019-04-27 13:57:39 +00:00
{
2024-11-27 07:41:27 +00:00
use SoftDeletes , MessageAttributes , MsgID , ParseAddresses ;
2021-08-11 13:45:30 +00:00
2021-09-06 13:39:32 +00:00
private const LOGKEY = 'ME-' ;
2024-06-06 22:42:10 +00:00
public const UPDATED_AT = NULL ;
2022-01-04 22:25:36 +00:00
private bool $no_export = FALSE ;
2022-01-01 05:59:35 +00:00
2024-05-17 12:10:54 +00:00
private const kludges = [
'MSGID:' => 'msgid' ,
'PATH:' => 'set_path' ,
'REPLY:' => 'replyid' ,
'SEEN-BY:' => 'set_seenby' ,
];
// When generating a packet for this echomail, the packet recipient is our tftn
public Address $tftn ;
2022-01-01 05:59:35 +00:00
protected $casts = [
2024-06-01 00:46:02 +00:00
'to' => UTF8StringOrNull :: class ,
'from' => UTF8StringOrNull :: class ,
'subject' => UTF8StringOrNull :: class ,
2023-06-26 00:32:38 +00:00
'datetime' => 'datetime:Y-m-d H:i:s' ,
2022-10-30 12:42:30 +00:00
'kludges' => CollectionOrNull :: class ,
2024-06-01 00:46:02 +00:00
'msg' => CompressedStringOrNull :: class ,
'msg_src' => CompressedStringOrNull :: class ,
2023-06-26 00:32:38 +00:00
'rogue_seenby' => CollectionOrNull :: class ,
2024-05-17 12:10:54 +00:00
'rogue_path' => CollectionOrNull :: class , // @deprecated?
2021-08-11 13:45:30 +00:00
];
2019-04-27 13:57:39 +00:00
2024-06-03 09:08:40 +00:00
public function __get ( $key )
{
switch ( $key ) {
case 'set_echoarea' :
case 'set_fftn' :
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 );
}
}
2022-11-01 11:24:36 +00:00
public function __set ( $key , $value )
2022-01-01 05:59:35 +00:00
{
switch ( $key ) {
2024-05-17 12:10:54 +00:00
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 ;
2022-01-04 22:25:36 +00:00
case 'no_export' :
2024-05-17 12:10:54 +00:00
$this -> { $key } = $value ;
break ;
2024-05-20 11:31:21 +00:00
case 'set_fftn' :
2024-05-17 12:10:54 +00:00
// Values that we pass to boot() to record how we got this echomail
2023-07-15 12:10:05 +00:00
case 'set_pkt' :
2023-08-05 11:32:45 +00:00
case 'set_recvtime' :
2024-05-17 12:10:54 +00:00
case 'set_sender' :
2024-06-03 09:08:40 +00:00
2024-05-17 12:10:54 +00:00
case 'set_tagline' :
case 'set_tearline' :
case 'set_origin' :
// For us to record the echoarea the message is for, if the area isnt defined (eg: packet dump)
case 'set_echoarea' :
$this -> set -> put ( $key , $value );
break ;
// The path and seenby the echomail went through to get here
case 'set_path' :
2022-01-01 05:59:35 +00:00
case 'set_seenby' :
2024-05-17 12:10:54 +00:00
if ( ! $this -> set -> has ( $key ))
$this -> set -> put ( $key , collect ());
$this -> set -> get ( $key ) -> push ( $value );
2022-01-01 05:59:35 +00:00
break ;
default :
parent :: __set ( $key , $value );
}
}
2021-09-06 13:39:32 +00:00
public static function boot ()
{
parent :: boot ();
2024-05-17 12:10:54 +00:00
static :: creating ( function ( $model ) {
2024-05-21 13:30:48 +00:00
if ( isset ( $model -> errors ) && $model -> errors -> count ())
2024-05-17 12:10:54 +00:00
throw new \Exception ( 'Cannot save, validation errors exist' );
2024-06-03 09:08:40 +00:00
2024-06-05 11:57:16 +00:00
if ( $model -> set -> has ( 'set_tagline' )) {
$x = Tagline :: where ( 'value' , utf8_encode ( $model -> set_tagline )) -> single ();
2024-06-03 09:08:40 +00:00
2024-06-05 11:57:16 +00:00
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 ;
}
2024-06-03 09:08:40 +00:00
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 ))
2024-07-11 13:33:17 +00:00
&& ( Address :: findFTN ( sprintf ( '%s@%s' , $m [ 2 ], $model -> fftn -> domain -> name ), TRUE , TRUE ) ? -> id === $model -> fftn_id ))
2024-06-05 11:57:16 +00:00
{
$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 ;
}
2024-06-03 09:08:40 +00:00
}
// 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 ;
}
2024-05-17 12:10:54 +00:00
});
2021-11-29 10:12:44 +00:00
// @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)
2021-09-06 13:39:32 +00:00
static :: created ( function ( $model ) {
2023-09-20 10:29:23 +00:00
$rogue = collect ();
2023-11-22 02:14:21 +00:00
$seenby = collect ();
2023-11-17 01:18:55 +00:00
$path = collect ();
2021-09-06 13:39:32 +00:00
2023-09-20 10:29:23 +00:00
// Parse PATH
2024-05-17 12:10:54 +00:00
if ( $model -> set -> has ( 'set_path' ))
$path = self :: parseAddresses ( 'path' , $model -> set -> get ( 'set_path' ), $model -> fftn -> zone , $rogue );
Log :: debug ( sprintf ( '%s:^ Message [%d] from point address is [%d]' , self :: LOGKEY , $model -> id , $model -> fftn -> point_id ));
2021-09-06 13:39:32 +00:00
2023-11-22 02:14:21 +00:00
// Make sure our sender is first in the path
2024-07-11 13:33:17 +00:00
if (( $model -> fftn -> point_id === 0 ) && ( ! $model -> isFlagSet ( Message :: FLAG_LOCAL )) && ( ! $path -> contains ( $model -> fftn_id ))) {
2023-11-22 02:14:21 +00:00
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
2024-07-11 13:33:17 +00:00
if ( $model -> set -> has ( 'set_sender' ) && ( ! $path -> contains ( $model -> set -> get ( 'set_sender' ) -> id )) && ( $model -> set -> get ( 'set_sender' ) -> point_id === 0 )) {
2024-05-17 12:10:54 +00:00
Log :: alert ( sprintf ( '%s:? Echomail adding pktsrc to end of PATH [%s].' , self :: LOGKEY , $model -> set -> get ( 'set_sender' ) -> ftn ));
$path -> push ( $model -> set -> get ( 'set_sender' ) -> id );
2023-11-22 02:14:21 +00:00
}
2022-01-01 05:59:35 +00:00
// Save the Path
$ppoid = NULL ;
2023-09-20 10:29:23 +00:00
foreach ( $path as $aoid ) {
2022-01-01 05:59:35 +00:00
$po = DB :: select ( 'INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id' ,[
$model -> id ,
$aoid ,
$ppoid ,
2021-09-06 13:39:32 +00:00
]);
2022-01-01 05:59:35 +00:00
$ppoid = $po [ 0 ] -> id ;
2021-09-06 13:39:32 +00:00
}
2023-09-20 10:29:23 +00:00
$rogue = collect ();
2023-12-01 07:14:51 +00:00
// @todo move the parseAddress processing into Message::class, and our address to the seenby (and thus no need to add it when we export)
2023-09-20 10:29:23 +00:00
// Parse SEEN-BY
2024-05-17 12:10:54 +00:00
if ( $model -> set -> has ( 'set_seenby' ))
$seenby = self :: parseAddresses ( 'seenby' , $model -> set -> get ( 'set_seenby' ), $model -> fftn -> zone , $rogue );
2023-09-20 10:29:23 +00:00
2023-11-22 02:35:37 +00:00
// Make sure our sender is in the seenby
2024-07-11 13:33:17 +00:00
if (( $model -> fftn -> point_id === 0 ) && ( ! $model -> isFlagSet ( Message :: FLAG_LOCAL )) && ( ! $seenby -> contains ( $model -> fftn_id ))) {
2023-11-22 02:14:21 +00:00
Log :: alert ( sprintf ( '%s:? Echomail adding sender to SEENBY [%s].' , self :: LOGKEY , $model -> fftn_id ));
$seenby -> push ( $model -> fftn_id );
}
2023-11-22 02:35:37 +00:00
// Make sure our pktsrc is in the seenby
2024-07-11 13:33:17 +00:00
if ( $model -> set -> has ( 'set_sender' ) && ( ! $seenby -> contains ( $model -> set -> get ( 'set_sender' ) -> id )) && ( $model -> set -> get ( 'set_sender' ) -> point_id === 0 )) {
2024-05-17 12:10:54 +00:00
Log :: alert ( sprintf ( '%s:? Echomail adding pktsrc to SEENBY [%s].' , self :: LOGKEY , $model -> set -> get ( 'set_sender' ) -> ftn ));
$seenby -> push ( $model -> set -> get ( 'set_sender' ) -> id );
2023-11-22 02:14:21 +00:00
}
2023-09-20 10:29:23 +00:00
if ( count ( $rogue )) {
$model -> rogue_seenby = $rogue ;
$model -> save ();
}
if ( $seenby )
$model -> seenby () -> sync ( $seenby );
2023-07-15 12:10:05 +00:00
// Our last node in the path is our sender
2024-05-17 12:10:54 +00:00
if ( $model -> set -> has ( 'set_pkt' ) && $model -> set -> has ( 'set_recvtime' )) {
2023-11-17 01:18:55 +00:00
if ( $path -> count ()) {
DB :: update ( 'UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?' ,[
2024-05-17 12:10:54 +00:00
$model -> set -> get ( 'set_pkt' ),
$model -> set -> get ( 'set_recvtime' ),
2023-11-17 01:18:55 +00:00
$path -> last (),
$model -> id ,
]);
} else {
2024-05-17 12:10:54 +00:00
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 -> get ( 'set_pkt' ), $model -> set -> get ( 'set_recvtime' )));
2023-11-17 01:18:55 +00:00
}
2023-07-15 12:10:05 +00:00
}
2022-01-01 05:59:35 +00:00
// See if we need to export this message.
2024-11-04 07:25:49 +00:00
// @todo We need to limit exporting if address/system is not active
2023-07-29 03:17:36 +00:00
if ( $model -> echoarea -> sec_read ) {
2024-05-17 12:10:54 +00:00
$exportto = $model
2023-07-29 03:17:36 +00:00
-> echoarea
-> addresses
2024-05-17 12:10:54 +00:00
-> filter ( function ( $item ) use ( $model ) { return $model -> echoarea -> can_read ( $item -> security ); })
2023-07-29 03:17:36 +00:00
-> pluck ( 'id' )
2024-05-26 02:08:01 +00:00
-> diff ( our_address ( $model -> fftn -> zone -> domain ) -> pluck ( 'id' ))
2023-09-20 10:29:23 +00:00
-> diff ( $seenby );
2023-07-29 03:17:36 +00:00
if ( $exportto -> count ()) {
if ( $model -> no_export ) {
2023-09-08 11:11:53 +00:00
Log :: alert ( sprintf ( '%s:- NOT processing exporting of message by configuration [%s] to [%s]' , self :: LOGKEY , $model -> id , $exportto -> join ( ',' )));
2023-07-29 03:17:36 +00:00
return ;
}
2023-09-08 11:11:53 +00:00
Log :: info ( sprintf ( '%s:- Exporting message [%s] to [%s]' , self :: LOGKEY , $model -> id , $exportto -> join ( ',' )));
2023-07-29 03:17:36 +00:00
// Save the seenby for the exported systems
$model -> seenby () -> syncWithPivotValues ( $exportto ,[ 'export_at' => Carbon :: now ()], FALSE );
2022-01-04 22:25:36 +00:00
}
2022-01-01 05:59:35 +00:00
}
2024-06-11 04:17:03 +00:00
event ( new EchomailEvent ( $model -> withoutRelations ()));
2021-09-06 13:39:32 +00:00
});
}
2021-07-30 14:35:52 +00:00
/* RELATIONS */
2019-04-27 13:57:39 +00:00
2021-09-06 13:39:32 +00:00
public function echoarea ()
{
2024-06-17 09:03:48 +00:00
return $this -> belongsTo ( Echoarea :: class )
-> select ( 'id' , 'active' , 'name' , 'domain_id' , 'security' , 'automsgs' )
-> with ([ 'domain:id,name' ]);
2021-09-06 13:39:32 +00:00
}
2022-01-01 05:59:35 +00:00
public function seenby ()
2021-08-29 01:48:27 +00:00
{
2022-01-01 05:59:35 +00:00
return $this -> belongsToMany ( Address :: class , 'echomail_seenby' )
2024-11-25 02:46:16 +00:00
-> select ([ 'addresses.id' , 'zone_id' , 'host_id' , 'node_id' ])
2023-12-01 07:14:51 +00:00
-> withPivot ([ 'export_at' , 'sent_at' , 'sent_pkt' ])
2023-09-19 07:28:25 +00:00
-> FTN2DOrder ();
2021-08-29 01:48:27 +00:00
}
2022-01-01 05:59:35 +00:00
public function path ()
2021-08-29 01:48:27 +00:00
{
2022-01-22 12:08:46 +00:00
return $this -> belongsToMany ( Address :: class , 'echomail_path' )
2024-06-17 09:03:48 +00:00
-> select ([ 'addresses.id' , 'zone_id' , 'host_id' , 'node_id' ])
2024-05-23 05:16:47 +00:00
-> withPivot ([ 'id' , 'parent_id' , 'recv_pkt' , 'recv_at' ])
-> orderBy ( 'id' , 'DESC' );
2021-08-29 01:48:27 +00:00
}
2024-05-17 12:10:54 +00:00
/* ATTRIBUTES */
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
public function getSeenByAttribute () : Collection
2022-11-04 12:14:47 +00:00
{
2024-05-17 12:10:54 +00:00
return (( ! $this -> exists ) && $this -> set -> has ( 'set_seenby' ))
? $this -> set -> get ( 'set_seenby' )
: $this -> getRelationValue ( 'seenby' );
2022-11-04 12:14:47 +00:00
}
2024-05-17 12:10:54 +00:00
public function getPathAttribute () : Collection
2021-08-11 13:45:30 +00:00
{
2024-05-17 12:10:54 +00:00
return (( ! $this -> exists ) && $this -> set -> has ( 'set_path' ))
? $this -> set -> get ( 'set_path' )
: $this -> getRelationValue ( 'path' );
2022-01-22 12:08:46 +00:00
}
2019-04-27 13:57:39 +00:00
}