2021-06-20 13:03:20 +00:00
< ? php
namespace App\Models ;
2023-11-25 10:52:05 +00:00
use Carbon\Carbon ;
2021-07-30 14:35:52 +00:00
use Illuminate\Database\Eloquent\Collection ;
2021-06-20 13:03:20 +00:00
use Illuminate\Database\Eloquent\Model ;
2021-06-25 06:42:12 +00:00
use Illuminate\Database\Eloquent\SoftDeletes ;
2023-10-04 01:17:16 +00:00
use Illuminate\Database\QueryException ;
2021-09-06 13:39:32 +00:00
use Illuminate\Support\Facades\DB ;
2022-02-13 00:27:12 +00:00
use Illuminate\Support\Facades\Log ;
2021-06-20 13:03:20 +00:00
2023-07-17 06:36:53 +00:00
use App\Classes\FTN\ { Message , Packet };
2023-09-18 11:22:21 +00:00
use App\Exceptions\InvalidFTNException ;
2021-06-20 13:03:20 +00:00
use App\Http\Controllers\DomainController ;
2022-01-01 05:59:35 +00:00
use App\Traits\ScopeActive ;
2021-06-20 13:03:20 +00:00
class Address extends Model
{
2022-01-01 05:59:35 +00:00
use ScopeActive , SoftDeletes ;
2023-10-04 04:49:44 +00:00
private const LOGKEY = 'MA-' ;
2022-01-01 05:59:35 +00:00
protected $with = [ 'zone' ];
2021-06-24 10:16:37 +00:00
2022-11-01 05:39:58 +00:00
// http://ftsc.org/docs/frl-1028.002
2023-11-25 10:52:05 +00:00
public const ftn_regex = '(\d+):(\d+)/(\d+)(?:\.(\d+))?(?:@([a-zA-Z0-9\-_~]{0,8}))?' ;
2022-11-01 05:39:58 +00:00
2022-01-24 11:56:13 +00:00
public const NODE_ZC = 1 << 0 ; // Zone
public const NODE_RC = 1 << 1 ; // Region
public const NODE_NC = 1 << 2 ; // Host
public const NODE_HC = 1 << 3 ; // Hub
public const NODE_ACTIVE = 1 << 4 ; // Node
public const NODE_PVT = 1 << 5 ; // Pvt
public const NODE_HOLD = 1 << 6 ; // Hold
public const NODE_DOWN = 1 << 7 ; // Down
public const NODE_POINT = 1 << 8 ; // Point
public const NODE_UNKNOWN = 1 << 15 ; // Unknown
2023-12-12 21:41:15 +00:00
public const NODE_ALL = 0xFFF ; // Mask to catch all nodes
2022-01-24 11:56:13 +00:00
2022-11-11 12:04:28 +00:00
public static function boot ()
{
parent :: boot ();
// For indexes when deleting, we need to change active to FALSE
static :: softDeleted ( function ( $model ) {
Log :: debug ( sprintf ( '%s:Deleting [%d], updating active to FALSE' , self :: LOGKEY , $model -> id ));
$model -> active = FALSE ;
$model -> save ();
});
}
2023-07-29 03:17:36 +00:00
protected $visible = [ 'zone_id' , 'region_id' , 'host_id' , 'node_id' , 'point_id' , 'security' ];
2021-06-24 13:09:09 +00:00
/* SCOPES */
2023-07-06 05:50:46 +00:00
public function scopeActiveFTN ( $query )
2023-06-23 11:28:29 +00:00
{
return $query -> select ( $this -> getTable () . '.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
-> where ( 'addresses.active' , TRUE )
-> where ( 'zones.active' , TRUE )
-> where ( 'domains.active' , TRUE )
-> orderBy ( 'domains.name' )
-> FTNorder ();
}
2021-06-24 13:09:09 +00:00
public function scopeFTNOrder ( $query )
{
return $query
-> orderBy ( 'region_id' )
2021-06-25 06:42:12 +00:00
-> orderBy ( 'host_id' )
2021-06-24 13:09:09 +00:00
-> orderBy ( 'node_id' )
-> orderBy ( 'point_id' );
}
2023-09-19 07:28:25 +00:00
public function scopeFTN2DOrder ( $query )
{
return $query
-> orderBy ( 'host_id' )
-> orderBy ( 'node_id' )
-> orderBy ( 'point_id' );
}
2023-11-25 10:52:05 +00:00
public function scopeTrashed ( $query )
{
return $query -> select ( $this -> getTable () . '.*' )
-> withTrashed ()
-> FTNorder ();
}
/**
* Return a list of addresses and the amount of uncollected echomail
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedEchomail ( $query )
2023-12-16 12:22:23 +00:00
{
return $query
-> join ( 'echomail_seenby' ,[ 'echomail_seenby.address_id' => 'addresses.id' ])
-> join ( 'echomails' ,[ 'echomails.id' => 'echomail_seenby.echomail_id' ])
-> whereNotNull ( 'export_at' )
-> whereNull ( 'sent_at' )
-> whereNull ( 'echomails.deleted_at' )
-> groupBy ( 'addresses.id' );
}
public function scopeUncollectedEchomailTotal ( $query )
2023-11-25 10:52:05 +00:00
{
return $query
2023-12-08 04:16:49 +00:00
-> select ([ 'addresses.id' , 'zone_id' , 'host_id' , 'node_id' , 'point_id' , 'system_id' , DB :: raw ( 'count(*) as uncollected_echomail' ), DB :: raw ( '0 as uncollected_netmail' ), DB :: raw ( '0 as uncollected_files' )])
2023-12-16 12:22:23 +00:00
-> UncollectedEchomail ();
2023-11-25 10:52:05 +00:00
}
/**
* Return a list of addresses and the amount of uncollected files
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedFiles ( $query )
{
return $query
-> join ( 'file_seenby' ,[ 'file_seenby.address_id' => 'addresses.id' ])
-> join ( 'files' ,[ 'files.id' => 'file_seenby.file_id' ])
-> whereNotNull ( 'export_at' )
-> whereNull ( 'sent_at' )
-> whereNull ( 'files.deleted_at' )
-> groupBy ( 'addresses.id' );
}
2023-12-16 12:22:23 +00:00
public function scopeUncollectedFilesTotal ( $query )
{
return $query
-> select ([ 'addresses.id' , 'zone_id' , 'host_id' , 'node_id' , 'point_id' , 'system_id' , DB :: raw ( '0 as uncollected_echomail' ), DB :: raw ( '0 as uncollected_netmail' ), DB :: raw ( 'count(*) as uncollected_files' )])
-> UncollectedFiles ();
}
2023-11-25 10:52:05 +00:00
public function scopeUncollectedNetmail ( $query )
{
return $query
-> join ( 'netmails' ,[ 'netmails.tftn_id' => 'addresses.id' ])
-> where ( function ( $query ) {
return $query -> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_INTRANSIT ))
-> orWhereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_LOCAL ));
})
-> whereRaw ( sprintf ( '(flags & %d) = 0' , Message :: FLAG_SENT ))
2023-12-16 12:22:23 +00:00
-> whereNull ( 'sent_pkt' )
-> whereNull ( 'sent_at' )
2023-11-25 10:52:05 +00:00
-> whereNull ( 'netmails.deleted_at' )
-> groupBy ( 'addresses.id' );
}
2023-12-16 12:22:23 +00:00
/**
* Return a list of addresses and the amount of uncollected netmail
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedNetmailTotal ( $query )
{
return $query
-> select ([ 'addresses.id' , 'zone_id' , 'host_id' , 'node_id' , 'point_id' , 'system_id' , DB :: raw ( '0 as uncollected_echomail' ), DB :: raw ( 'count(*) as uncollected_netmail' ), DB :: raw ( '0 as uncollected_files' )])
-> UncollectedNetmail ();
}
2021-06-20 13:03:20 +00:00
/* RELATIONS */
2023-12-03 07:18:05 +00:00
public function dynamics ()
{
return $this -> hasMany ( Dynamic :: class );
}
2022-01-15 02:06:15 +00:00
/**
* Echoareas this address is subscribed to
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
2021-08-25 12:13:49 +00:00
public function echoareas ()
{
return $this -> belongsToMany ( Echoarea :: class )
-> withPivot ([ 'subscribed' ]);
}
2022-01-15 02:06:15 +00:00
/**
* Echomails that this address has seen
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function echomails ()
{
return $this -> belongsToMany ( Echomail :: class , 'echomail_seenby' )
2023-07-15 00:46:19 +00:00
-> withPivot ([ 'export_at' , 'sent_at' , 'sent_pkt' ]);
2022-11-01 11:24:36 +00:00
}
2022-11-25 10:44:03 +00:00
public function echomail_from ()
{
return $this -> hasMany ( Echomail :: class , 'fftn_id' , 'id' );
}
2022-11-01 11:24:36 +00:00
/**
* Files that this address has seen
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function files ()
{
return $this -> belongsToMany ( File :: class , 'file_seenby' )
-> withPivot ([ 'sent_at' , 'export_at' ]);
}
/**
2023-12-12 21:41:15 +00:00
* Fileareas this address is subscribed to
2022-11-01 11:24:36 +00:00
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function fileareas ()
{
return $this -> belongsToMany ( Filearea :: class )
-> withPivot ([ 'subscribed' ]);
2022-01-15 02:06:15 +00:00
}
2021-06-20 13:03:20 +00:00
public function system ()
{
return $this -> belongsTo ( System :: class );
}
public function zone ()
{
return $this -> belongsTo ( Zone :: class );
}
/* ATTRIBUTES */
2023-06-23 11:28:29 +00:00
/**
* Return if this address is active
*
* @ param bool $value
* @ return bool
*/
public function getActiveAttribute ( bool $value ) : bool
{
2023-07-29 03:17:36 +00:00
return $value && $this -> getActiveDomainAttribute ();
}
public function getActiveDomainAttribute () : bool
{
return $this -> zone -> active && $this -> zone -> domain -> active ;
2023-06-23 11:28:29 +00:00
}
2021-06-20 13:03:20 +00:00
/**
* Render the node name in full 5 D
*
* @ return string
*/
2021-07-15 14:54:23 +00:00
public function getFTNAttribute () : string
2021-06-20 13:03:20 +00:00
{
2021-06-26 01:48:55 +00:00
return sprintf ( '%s@%s' , $this -> getFTN4DAttribute (), $this -> zone -> domain -> name );
}
2021-08-29 01:48:27 +00:00
public function getFTN2DAttribute () : string
{
return sprintf ( '%d/%d' , $this -> host_id ? : $this -> region_id , $this -> node_id );
}
2021-07-15 14:54:23 +00:00
public function getFTN3DAttribute () : string
2021-06-26 01:48:55 +00:00
{
2023-07-17 06:36:53 +00:00
return sprintf ( '%d:%s' , $this -> zone -> zone_id , $this -> getFTN2DAttribute ());
2021-06-26 01:48:55 +00:00
}
2021-07-15 14:54:23 +00:00
public function getFTN4DAttribute () : string
2021-06-26 01:48:55 +00:00
{
return sprintf ( '%s.%d' , $this -> getFTN3DAttribute (), $this -> point_id );
2021-06-20 13:03:20 +00:00
}
2021-07-26 11:21:58 +00:00
public function getRoleNameAttribute () : string
2021-06-20 13:03:20 +00:00
{
2021-07-26 11:21:58 +00:00
switch ( $this -> role ) {
2022-01-24 11:56:13 +00:00
case self :: NODE_ZC :
2021-07-26 11:21:58 +00:00
return 'ZC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_RC :
2021-07-26 11:21:58 +00:00
return 'RC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_NC :
2021-07-26 11:21:58 +00:00
return 'NC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_HC :
2021-07-26 11:21:58 +00:00
return 'HUB' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_ACTIVE :
return 'NODE' ;
case self :: NODE_POINT :
2021-07-26 11:21:58 +00:00
return 'POINT' ;
2021-06-20 13:03:20 +00:00
default :
return '?' ;
}
}
2021-06-24 10:16:37 +00:00
2021-07-15 14:54:23 +00:00
/* METHODS */
2021-06-24 10:16:37 +00:00
2023-12-12 21:41:15 +00:00
/**
* Find children dependent on this record
*/
public function children () : Collection
{
// We have no session data for this address, by definition it has no children
if ( ! $this -> session ( 'sespass' ) && ( ! our_address () -> pluck ( 'id' ) -> contains ( $this -> id )))
return new Collection ;
// If this system is not marked to default route for this address
if ( ! $this -> session ( 'default' )) {
switch ( $this -> role ) {
case self :: NODE_ZC :
$children = self :: select ( 'addresses.*' )
-> where ( 'zone_id' , $this -> zone_id );
break ;
case self :: NODE_RC :
$children = self :: select ( 'addresses.*' )
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id );
break ;
case self :: NODE_NC :
$children = self :: select ( 'addresses.*' )
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id );
break ;
case self :: NODE_HC :
// Identify our children.
$children = self :: select ( 'addresses.*' )
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'hub_id' , $this -> id );
break ;
case self :: NODE_ACTIVE :
case self :: NODE_PVT :
case self :: NODE_HOLD :
case self :: NODE_DOWN :
case self :: NODE_UNKNOWN :
// Identify our children.
$children = self :: select ( 'addresses.*' )
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'node_id' , $this -> node_id )
-> where ( 'point_id' , '<>' , 0 );
break ;
case self :: NODE_POINT :
// Points dont have children, but must return a relationship instance
return new Collection ;
default :
throw new \Exception ( 'Unknown role: ' . serialize ( $this -> role ));
}
// We route everything for this domain
} else {
$children = self :: select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> where ( 'domain_id' , $this -> zone -> domain_id );
}
// I cant have myself as a child, and have a high role than me
$children = $children -> where ( 'addresses.id' , '<>' , $this -> id )
-> where ( 'role' , '>' , $this -> role )
-> FTNorder ()
-> active ()
-> get ();
// If there are no children
if ( ! $children -> count ())
return new Collection ;
// Exclude links and their children.
$exclude = collect ();
foreach ( our_nodes ( $this -> zone -> domain ) -> merge ( our_address ( $this -> zone -> domain )) as $o ) {
// If this address is in our list, remove it and it's children
if ( $children -> contains ( $o )) {
$exclude = $exclude -> merge ( $o -> children ());
$exclude -> push ( $o );
}
}
return $children -> filter ( function ( $item ) use ( $exclude ) { return ! $exclude -> pluck ( 'id' ) -> contains ( $item -> id );});
}
2023-09-15 12:57:32 +00:00
/**
* Create an FTN address associated with a system
*
* @ param string $address
* @ param System $so
2023-09-22 04:45:44 +00:00
* @ return Address | null
2023-09-15 12:57:32 +00:00
* @ throws \Exception
*/
public static function createFTN ( string $address , System $so ) : ? self
{
$ftn = self :: parseFTN ( $address );
if ( ! $ftn [ 'd' ]) {
// See if we have a default domain for this domain
if ( $ftn [ 'z' ]) {
$zo = Zone :: where ( 'zone_id' , $ftn [ 'z' ]) -> where ( 'default' , TRUE ) -> single ();
if ( $zo )
$ftn [ 'd' ] = $zo -> domain -> name ;
else {
Log :: alert ( sprintf ( '%s:! Refusing to create address [%s] no domain available' , self :: LOGKEY , $address ));
return NULL ;
}
}
}
Log :: debug ( sprintf ( '%s:- Creating AKA [%s] for [%s]' , self :: LOGKEY , $address , $so -> name ));
// Check Domain exists
Domain :: unguard ();
$do = Domain :: singleOrNew ([ 'name' => $ftn [ 'd' ]]);
Domain :: reguard ();
if ( ! $do -> exists ) {
$do -> public = TRUE ;
$do -> active = TRUE ;
$do -> notes = 'Auto created' ;
$do -> save ();
}
// If the zone is zero, see if this is a flatten domain, and if so, find an NC
if (( $ftn [ 'z' ] === 0 ) && $do -> flatten ) {
$nc = self :: findZone ( $do , $ftn [ 'n' ], 0 , 0 );
if ( $nc ) {
Log :: info ( sprintf ( '%s:- Setting zone to [%d]' , self :: LOGKEY , $nc -> zone -> zone_id ));
$ftn [ 'z' ] = $nc -> zone -> zone_id ;
}
}
// Create zone
Zone :: unguard ();
$zo = Zone :: singleOrNew ([ 'domain_id' => $do -> id , 'zone_id' => $ftn [ 'z' ]]);
Zone :: reguard ();
if ( ! $zo -> exists ) {
$zo -> active = TRUE ;
$zo -> notes = 'Auto created' ;
$zo -> system_id = System :: createUnknownSystem () -> id ;
$do -> zones () -> save ( $zo );
}
if ( ! $zo -> active || ! $do -> active ) {
Log :: alert ( sprintf ( '%s:! Refusing to create address [%s] in disabled zone or domain' , self :: LOGKEY , $address ));
return NULL ;
}
// Create Address, assigned to $so
$o = new self ;
$o -> active = TRUE ;
$o -> zone_id = $zo -> id ;
$o -> region_id = 0 ;
$o -> host_id = $ftn [ 'n' ];
$o -> node_id = $ftn [ 'f' ];
$o -> point_id = $ftn [ 'p' ];
$o -> role = $ftn [ 'p' ] ? self :: NODE_POINT : self :: NODE_UNKNOWN ;
try {
$so -> addresses () -> save ( $o );
2023-10-04 01:17:16 +00:00
} catch ( QueryException $e ) {
Log :: error ( sprintf ( '%s:! ERROR creating address [%s] (%s)' , self :: LOGKEY , $o -> ftn , get_class ( $e )));
2023-09-15 12:57:32 +00:00
return NULL ;
}
return $o ;
}
2021-06-24 10:16:37 +00:00
/**
* Find a record in the DB for a node string , eg : 10 : 1 / 1.0
*
2022-12-01 12:51:43 +00:00
* @ param string $address
2023-07-06 05:50:46 +00:00
* @ param bool $trashed
2021-06-29 10:43:29 +00:00
* @ return Address | null
2023-06-27 07:39:11 +00:00
* @ throws \Exception
2021-06-24 10:16:37 +00:00
*/
2023-09-15 12:57:32 +00:00
public static function findFTN ( string $address , bool $trashed = FALSE ) : ? self
2021-06-24 10:16:37 +00:00
{
2022-12-01 12:51:43 +00:00
$ftn = self :: parseFTN ( $address );
2023-09-15 12:57:32 +00:00
$o = NULL ;
2021-06-24 10:16:37 +00:00
2023-09-15 12:57:32 +00:00
$query = ( new self )
2023-07-06 05:50:46 +00:00
-> select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
2023-07-02 13:40:08 +00:00
-> when ( $trashed , function ( $query ) {
$query -> trashed ();
}, function ( $query ) {
$query -> active ();
})
2021-06-29 10:43:29 +00:00
-> where ( 'zones.zone_id' , $ftn [ 'z' ])
2021-07-26 11:21:58 +00:00
-> where ( 'node_id' , $ftn [ 'f' ])
-> where ( 'point_id' , $ftn [ 'p' ])
2021-06-29 10:43:29 +00:00
-> when ( $ftn [ 'd' ], function ( $query , $domain ) {
$query -> where ( 'domains.name' , $domain );
2023-09-15 12:57:32 +00:00
}, function ( $query ) {
2021-08-16 12:26:33 +00:00
$query -> where ( 'zones.default' , TRUE );
2023-09-15 12:57:32 +00:00
});
$q = $query -> clone ();
2021-06-24 10:16:37 +00:00
2023-09-15 12:57:32 +00:00
// Are we looking for a region address
if (( $ftn [ 'f' ] === 0 ) && ( $ftn [ 'p' ] === 0 ))
$o = $query
-> where ( 'region_id' , $ftn [ 'n' ])
-> where ( 'host_id' , $ftn [ 'n' ])
-> single ();
// Look for a normal address
if ( ! $o )
$o = $q
-> where ( function ( $q ) use ( $ftn ) {
return $q
-> where ( function ( $q ) use ( $ftn ) {
return $q
-> where ( 'region_id' , $ftn [ 'n' ])
-> where ( 'host_id' , $ftn [ 'n' ]);
})
-> orWhere ( 'host_id' , $ftn [ 'n' ]);
})
-> single ();
2023-09-15 04:28:38 +00:00
// Check and see if we are a flattened domain, our address might be available with a different zone.
2023-09-15 12:57:32 +00:00
if ( ! $o && ( $ftn [ 'p' ] === 0 )) {
2023-09-15 04:28:38 +00:00
if ( $ftn [ 'd' ])
$do = Domain :: where ([ 'name' => $ftn [ 'd' ]]) -> single ();
else {
$zo = Zone :: where ( 'zone_id' , $ftn [ 'z' ]) -> where ( 'default' , TRUE ) -> single ();
$do = $zo ? -> domain ;
}
2023-09-17 05:46:09 +00:00
if ( $do && $do -> flatten && (( $ftn [ 'z' ] === 0 ) || $do -> zones -> pluck ( 'zone_id' ) -> contains ( $ftn [ 'z' ])))
2023-09-15 04:28:38 +00:00
$o = self :: findZone ( $do , $ftn [ 'n' ], $ftn [ 'f' ], $ftn [ 'p' ], $trashed );
2022-12-01 12:51:43 +00:00
}
2021-06-24 10:16:37 +00:00
return ( $o && $o -> system -> active ) ? $o : NULL ;
}
2023-09-10 12:48:12 +00:00
/**
* This is to find an address for a domain ( like fidonet ), which is technically 2 D even though it uses multiple zones .
*
* This was implemented to identify seenby and path kludges
*
* @ param Domain $do
* @ param int $host
* @ param int $node
2023-09-22 04:45:44 +00:00
* @ param int $point
2023-09-10 12:48:12 +00:00
* @ param bool $trashed
* @ return self | null
* @ throws \Exception
*/
2023-09-15 04:28:38 +00:00
public static function findZone ( Domain $do , int $host , int $node , int $point , bool $trashed = FALSE ) : ? self
2023-09-10 12:48:12 +00:00
{
if ( ! $do -> flatten )
throw new \Exception ( sprintf ( 'Domain is not set with flatten: %d' , $do -> id ));
$zones = $do -> zones -> pluck ( 'zone_id' );
$o = ( new self )
-> select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> when ( $trashed , function ( $query ) {
$query -> trashed ();
}, function ( $query ) {
$query -> active ();
})
-> whereIN ( 'zones.zone_id' , $zones )
-> where ( function ( $q ) use ( $host ) {
return $q
-> where ( function ( $q ) use ( $host ) {
return $q -> where ( 'region_id' , $host )
-> where ( 'host_id' , $host );
})
-> orWhere ( 'host_id' , $host );
})
-> where ( 'node_id' , $node )
2023-09-15 04:28:38 +00:00
-> where ( 'point_id' , $point )
2023-09-10 12:48:12 +00:00
-> where ( 'zones.domain_id' , $do -> id )
-> single ();
return $o ;
}
2022-03-14 11:28:54 +00:00
/**
* Create an activation code for this address
*
* @ param User $uo
* @ return string
*/
public function set_activation ( User $uo ) : string
{
return sprintf ( '%x:%s' ,
$this -> id ,
substr ( md5 ( sprintf ( '%d:%x' , $uo -> id , timew ( $this -> updated_at ))), 0 , 10 )
);
}
/**
* Check the user ' s activation code for this address is correct
*
* @ param User $uo
* @ param string $code
* @ return bool
*/
public function check_activation ( User $uo , string $code ) : bool
{
try {
2023-02-11 12:06:13 +00:00
Log :: info ( sprintf ( '%s:Checking Activation code [%s] is valid for user [%d]' , self :: LOGKEY , $code , $uo -> id ));
2022-03-14 11:28:54 +00:00
2023-06-27 07:39:11 +00:00
return ( $code === $this -> set_activation ( $uo ));
2022-03-14 11:28:54 +00:00
} catch ( \Exception $e ) {
Log :: error ( sprintf ( '%s:! Activation code [%s] invalid for user [%d]' , self :: LOGKEY , $code , $uo -> id ));
return FALSE ;
}
}
2023-12-03 07:18:05 +00:00
/**
* Files waiting to be sent to this system
*
* @ return Collection
*/
public function dynamicWaiting () : Collection
{
return $this -> dynamics ()
-> where ( 'next_at' , '<=' , Carbon :: now ())
-> where ( 'active' , TRUE )
-> get ();
}
2022-01-01 05:59:35 +00:00
/**
2022-11-01 11:24:36 +00:00
* Echomail waiting to be sent to this system
2022-01-01 05:59:35 +00:00
*
* @ return Collection
*/
2023-12-16 12:22:23 +00:00
public function echomailWaiting ( int $max = NULL ) : Collection
{
$echomails = $this
-> UncollectedEchomail ()
-> select ( 'echomails.id' )
-> where ( 'addresses.id' , $this -> id )
-> when ( $max , function ( $query ) use ( $max ) { return $query -> limit ( $max ); })
-> groupBy ([ 'echomails.id' ])
2022-01-01 05:59:35 +00:00
-> get ();
2023-12-16 12:22:23 +00:00
return Echomail :: whereIn ( 'id' , $echomails -> pluck ( 'id' )) -> get ();
2022-01-01 05:59:35 +00:00
}
2022-11-01 11:24:36 +00:00
/**
* Files waiting to be sent to this system
*
* @ return Collection
*/
public function filesWaiting () : Collection
{
return $this -> files ()
2023-07-17 06:36:53 +00:00
-> whereNull ( 'sent_at' )
-> whereNotNull ( 'export_at' )
2022-11-01 11:24:36 +00:00
-> get ();
}
2021-07-30 14:35:52 +00:00
/**
* Get echomail for this node
*
2022-01-20 06:54:02 +00:00
* @ param bool $update
2022-02-13 00:27:12 +00:00
* @ param Collection | null $echomail
2021-07-30 14:35:52 +00:00
* @ return Packet | null
2023-11-23 01:18:20 +00:00
* @ todo If we export to uplink hubs without our address in the seenby , they should send the message back to
* us with their seenby ' s .
2021-07-30 14:35:52 +00:00
*/
2022-01-24 11:56:13 +00:00
public function getEchomail ( bool $update = TRUE , Collection $echomail = NULL ) : ? Packet
2021-07-30 14:35:52 +00:00
{
2021-09-06 13:39:32 +00:00
$pkt = NULL ;
2022-01-24 11:56:13 +00:00
if ( $echomail )
return $this -> getPacket ( $echomail );
2021-09-06 13:39:32 +00:00
2022-12-02 13:22:56 +00:00
$s = Setup :: findOrFail ( config ( 'app.id' ));
2023-12-16 12:22:23 +00:00
$num = self :: UncollectedEchomail ()
-> select ( 'echomails.id' )
-> where ( 'addresses.id' , $this -> id )
-> groupBy ([ 'echomails.id' ])
-> get ();
if ( $num -> count ()) {
2022-12-02 13:22:56 +00:00
// Limit to max messages
2023-12-16 12:22:23 +00:00
Log :: info ( sprintf ( '%s:= Got [%d] echomails for [%s] for sending' , self :: LOGKEY , $num -> count (), $this -> ftn ));
2022-12-02 13:22:56 +00:00
2023-12-16 12:22:23 +00:00
if ( $num -> count () > $s -> msgs_pkt )
Log :: notice ( sprintf ( '%s:= Only sending [%d] echomails for [%s]' , self :: LOGKEY , $s -> msgs_pkt , $this -> ftn ));
2022-02-13 00:27:12 +00:00
2023-12-16 12:22:23 +00:00
$x = $this -> echomailWaiting ( $s -> msgs_pkt );
2022-01-01 05:59:35 +00:00
$pkt = $this -> getPacket ( $x );
2021-09-06 13:39:32 +00:00
2022-01-20 06:54:02 +00:00
if ( $pkt && $pkt -> count () && $update )
DB :: table ( 'echomail_seenby' )
-> whereIn ( 'echomail_id' , $x -> pluck ( 'id' ))
-> where ( 'address_id' , $this -> id )
-> whereNull ( 'sent_at' )
2023-07-17 06:36:53 +00:00
-> whereNotNull ( 'export_at' )
2023-07-15 12:10:05 +00:00
-> update ([ 'sent_pkt' => $pkt -> name ]);
2021-07-30 14:35:52 +00:00
}
2021-09-06 13:39:32 +00:00
return $pkt ;
2021-07-30 14:35:52 +00:00
}
2023-06-22 07:36:22 +00:00
/**
* Get files for this node ( including it ' s children )
*
* @ param bool $update
* @ return Collection
2023-07-17 06:36:53 +00:00
* @ deprecated use filesWaiting () directly
2023-06-22 07:36:22 +00:00
*/
public function getFiles ( bool $update = TRUE ) : Collection
{
2023-07-17 06:36:53 +00:00
return $this -> filesWaiting ();
2023-06-22 07:36:22 +00:00
}
2021-07-15 14:54:23 +00:00
/**
* Get netmail for this node ( including it ' s children )
2021-07-17 05:48:07 +00:00
*
2022-01-20 06:54:02 +00:00
* @ param bool $update
2021-07-17 05:48:07 +00:00
* @ return Packet | null
2023-12-14 05:53:56 +00:00
* @ throws \Exception
2021-07-15 14:54:23 +00:00
*/
2023-07-15 12:10:05 +00:00
public function getNetmail ( bool $update = FALSE ) : ? Packet
2021-07-15 14:54:23 +00:00
{
2022-01-01 05:59:35 +00:00
$pkt = NULL ;
2023-07-15 12:10:05 +00:00
$s = Setup :: findOrFail ( config ( 'app.id' ));
2023-07-23 07:27:52 +00:00
if (( $x = $this -> netmailAlertWaiting ()) -> count ()) {
Log :: debug ( sprintf ( '%s:= Packaging [%d] netmail alerts to [%s]' , self :: LOGKEY , $x -> count (), $this -> ftn ));
$passpos = strpos ( $x -> last () -> subject , ':' );
if ( $passpos > 8 )
Log :: alert ( sprintf ( '%s:! Password would be greater than 8 chars? [%d]' , self :: LOGKEY , $passpos ));
$pkt = $this -> getPacket ( $x , substr ( $x -> last () -> subject , 0 , $passpos ));
if ( $pkt && $pkt -> count () && $update )
DB :: table ( 'netmails' )
-> whereIn ( 'id' , $x -> pluck ( 'id' ))
-> update ([ 'sent_pkt' => $pkt -> name ]);
return $pkt ;
}
2022-01-01 05:59:35 +00:00
if (( $x = $this -> netmailWaiting ())
2021-07-18 12:10:21 +00:00
-> count ())
{
2022-02-13 00:27:12 +00:00
Log :: debug ( sprintf ( '%s:= Got [%d] netmails for [%s] for sending' , self :: LOGKEY , $x -> count (), $this -> ftn ));
2023-07-15 12:10:05 +00:00
if ( $x -> count () > $s -> msgs_pkt ) {
$x = $x -> take ( $s -> msgs_pkt );
Log :: alert ( sprintf ( '%s:= Only sending [%d] netmails for [%s]' , self :: LOGKEY , $x -> count (), $this -> ftn ));
}
2022-01-01 05:59:35 +00:00
$pkt = $this -> getPacket ( $x );
2022-01-20 06:54:02 +00:00
if ( $pkt && $pkt -> count () && $update )
DB :: table ( 'netmails' )
-> whereIn ( 'id' , $x -> pluck ( 'id' ))
2023-07-15 12:10:05 +00:00
-> update ([ 'sent_pkt' => $pkt -> name ]);
2021-07-30 14:35:52 +00:00
}
2022-01-01 05:59:35 +00:00
return $pkt ;
2021-07-30 14:35:52 +00:00
}
2021-07-17 05:48:07 +00:00
2021-07-30 14:35:52 +00:00
/**
* Return a packet of mail
*
2022-12-02 13:22:56 +00:00
* @ param Collection $msgs of message models ( Echomail / Netmail )
2023-07-23 07:27:52 +00:00
* @ param string | null $passwd Override password used in packet
2022-01-20 12:25:47 +00:00
* @ return Packet | null
2021-07-30 14:35:52 +00:00
*/
2023-07-23 07:27:52 +00:00
public function getPacket ( Collection $msgs , string $passwd = NULL ) : ? Packet
2021-07-30 14:35:52 +00:00
{
2022-12-02 13:22:56 +00:00
$s = Setup :: findOrFail ( config ( 'app.id' ));
2023-12-14 05:53:56 +00:00
$ao = our_address ( $this -> zone -> domain , $this );
2022-01-20 06:54:02 +00:00
// If we dont match on the address, we cannot pack mail for that system
2023-10-04 04:49:44 +00:00
if ( ! $ao ) {
Log :: alert ( sprintf ( '%s:! We didnt match an address in zone [%d] for [%s]' , self :: LOGKEY , $this -> zone -> zone_id , $this -> ftn ));
2022-01-20 06:54:02 +00:00
return NULL ;
2023-10-04 04:49:44 +00:00
}
2022-01-20 06:54:02 +00:00
2023-06-26 09:19:42 +00:00
// Get packet type
2023-09-22 04:45:44 +00:00
$type = collect ( Packet :: PACKET_TYPES ) -> get ( $this -> system -> pkt_type ? : config ( 'fido.packet_default' ));
2023-06-26 09:19:42 +00:00
$o = new $type ;
2023-07-23 07:27:52 +00:00
$o -> addressHeader ( $ao , $this , $passwd );
2021-07-15 14:54:23 +00:00
2022-02-13 00:27:12 +00:00
// $oo = Netmail/Echomail Model
2022-12-02 13:22:56 +00:00
$c = 0 ;
foreach ( $msgs as $oo ) {
// Only bundle up to max messages
2023-07-05 12:42:59 +00:00
if ( ++ $c > $s -> msgs_pkt )
2022-12-02 13:22:56 +00:00
break ;
2023-07-23 07:27:52 +00:00
$o -> addMail ( $oo -> packet ( $this , $passwd ));
2022-12-02 13:22:56 +00:00
}
2021-07-15 14:54:23 +00:00
2021-07-30 14:35:52 +00:00
return $o ;
2021-07-15 14:54:23 +00:00
}
2022-01-01 05:59:35 +00:00
/**
* Netmail waiting to be sent to this system
*
* @ return Collection
2023-12-12 21:41:15 +00:00
* @ throws \Exception
2022-01-01 05:59:35 +00:00
*/
public function netmailWaiting () : Collection
{
2023-12-16 12:22:23 +00:00
$netmails = $this
-> UncollectedNetmail ()
-> select ( 'netmails.id' )
2023-12-17 22:26:50 +00:00
-> whereIn ( 'addresses.id' , $this -> children () -> add ( $this ) -> pluck ( 'id' ))
2023-12-16 12:22:23 +00:00
-> groupBy ([ 'netmails.id' ])
2022-01-01 05:59:35 +00:00
-> get ();
2023-12-16 12:22:23 +00:00
return Netmail :: whereIn ( 'id' , $netmails -> pluck ( 'id' )) -> get ();
2022-01-01 05:59:35 +00:00
}
2023-07-23 07:27:52 +00:00
/**
* Netmail alerts waiting to be sent to this system
*
* @ return Collection
*/
public function netmailAlertWaiting () : Collection
{
2023-12-16 12:22:23 +00:00
$netmails = $this
-> UncollectedNetmail ()
2023-07-23 07:27:52 +00:00
-> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_LOCAL ))
-> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_PKTPASSWD ))
-> whereRaw ( sprintf ( '(flags & %d) = 0' , Message :: FLAG_SENT ))
2023-12-16 12:22:23 +00:00
-> select ( 'netmails.id' )
2023-12-17 22:26:50 +00:00
-> whereIn ( 'addresses.id' , $this -> children () -> add ( $this ) -> pluck ( 'id' ))
2023-12-16 12:22:23 +00:00
-> groupBy ([ 'netmails.id' ])
2023-07-23 07:27:52 +00:00
-> get ();
2023-12-16 12:22:23 +00:00
return Netmail :: whereIn ( 'id' , $netmails -> pluck ( 'id' )) -> get ();
2023-07-23 07:27:52 +00:00
}
2023-12-12 21:41:15 +00:00
/**
* Who we send this systems mail to .
*
* @ return Address | null
* @ throws \Exception
*/
public function parent () : ? Address
{
// If we have session password, then we are the parent
if ( $this -> session ( 'sespass' ))
return $this ;
// If it is our address
if ( our_address () -> contains ( $this ))
return NULL ;
switch ( $this -> role ) {
// ZCs dont have parents, but we may have a default
case self :: NODE_ZC :
if (( $x = $this -> zone -> systems -> where ( 'pivot.default' , TRUE )) -> count ())
return $x -> first () -> match ( $this -> zone , 255 ) -> first ();
else
return NULL ;
// RC
case self :: NODE_RC :
$parent = self :: where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , 0 )
-> where ( 'host_id' , 0 )
-> where ( 'node_id' , 0 )
-> single ();
break ;
// Hosts
case self :: NODE_NC :
// See if we have an RC
$parent = self :: where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> region_id )
-> where ( 'node_id' , 0 )
-> single ();
if ( ! $parent ) {
// See if we have an ZC
$parent = self :: where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , 0 )
-> where ( 'host_id' , 0 )
-> where ( 'node_id' , 0 )
-> single ();
}
break ;
// Hubs
case self :: NODE_HC :
// Normal Nodes
case self :: NODE_ACTIVE :
case self :: NODE_PVT :
case self :: NODE_HOLD :
case self :: NODE_DOWN :
case self :: NODE_UNKNOWN :
// If we are a child of a hub, then check our hub
$parent = ( $this -> hub_id
? self :: where ( 'id' , $this -> hub_id )
: self :: where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'role' , '<' , self :: NODE_HC ))
-> active ()
-> single ();
break ;
case self :: NODE_POINT :
$parent = self :: where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'node_id' , $this -> node_id )
-> where ( 'point_id' , 0 )
-> active ()
-> single ();
break ;
default :
throw new \Exception ( sprintf ( 'Unknown role: %s (%d)' , serialize ( $this -> role ), $this -> id ));
}
return $parent ? -> parent ();
}
2021-06-29 10:43:29 +00:00
/**
2022-11-01 05:39:58 +00:00
*
2021-06-29 10:43:29 +00:00
* Parse a string and split it out as an FTN array
*
* @ param string $ftn
* @ return array
2023-06-27 07:39:11 +00:00
* @ throws \Exception
2021-06-29 10:43:29 +00:00
*/
public static function parseFTN ( string $ftn ) : array
{
2022-11-01 05:39:58 +00:00
if ( ! preg_match ( sprintf ( '#^%s$#' , self :: ftn_regex ), strtolower ( $ftn ), $matches ))
2023-11-23 01:18:20 +00:00
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - regex failed' , serialize ( $ftn )));
2021-06-29 10:43:29 +00:00
// Check our numbers are correct.
2023-11-23 01:18:20 +00:00
foreach ([ 1 , 2 , 3 ] as $i )
2021-07-01 11:56:55 +00:00
if (( ! is_numeric ( $matches [ $i ])) || ( $matches [ $i ] > DomainController :: NUMBER_MAX ))
2023-11-23 01:18:20 +00:00
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - zone, host, or node address invalid [%d]' , $ftn , $matches [ $i ]));
2021-06-29 10:43:29 +00:00
2023-11-23 01:18:20 +00:00
if (( ! empty ( $matches [ 4 ])) AND (( ! is_numeric ( $matches [ $i ])) || ( $matches [ 4 ] > DomainController :: NUMBER_MAX )))
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - point address invalid [%d]' , $ftn , $matches [ 4 ]));
2021-06-29 10:43:29 +00:00
return [
'z' => ( int ) $matches [ 1 ],
'n' => ( int ) $matches [ 2 ],
'f' => ( int ) $matches [ 3 ],
2023-11-23 01:18:20 +00:00
'p' => empty ( $matches [ 4 ]) ? 0 : ( int ) $matches [ 4 ],
'd' => $matches [ 5 ] ? ? NULL
2021-06-29 10:43:29 +00:00
];
}
2021-06-24 13:09:09 +00:00
/**
* Retrieve the address session details / passwords
*
* @ param string $type
* @ return string | null
*/
2021-06-24 10:16:37 +00:00
public function session ( string $type ) : ? string
{
2023-10-02 22:11:08 +00:00
return ( $this -> exists && ( $x = $this -> system -> sessions -> where ( 'id' , $this -> zone_id ) -> first ())) ? ( $x -> pivot -> { $type } ? : '' ) : NULL ;
2021-06-24 10:16:37 +00:00
}
2023-12-08 04:16:49 +00:00
}