2021-06-29 10:43:29 +00:00
< ? php
namespace App\Classes\FTN ;
2021-07-15 14:54:23 +00:00
use Carbon\Carbon ;
2022-11-05 08:03:33 +00:00
use Carbon\Exceptions\InvalidFormatException ;
2021-06-29 10:43:29 +00:00
use Illuminate\Support\Arr ;
use Illuminate\Support\Collection ;
use Illuminate\Support\Facades\Log ;
use Illuminate\Support\Facades\Validator ;
use Illuminate\Validation\Validator as ValidatorResult ;
use App\Classes\FTN as FTNBase ;
2024-05-17 12:10:54 +00:00
use App\Exceptions\InvalidPacketException ;
use App\Models\ { Address , Domain , Echomail , Netmail , Zone };
2022-01-15 02:06:15 +00:00
use App\Rules\ { TwoByteInteger , TwoByteIntegerWithZero };
2023-11-27 04:56:28 +00:00
use App\Traits\ { EncodeUTF8 , ObjectIssetFix };
2021-06-29 10:43:29 +00:00
/**
* Class Message
2023-06-22 07:36:22 +00:00
* Represents the structure of a message in a packet
2021-06-29 10:43:29 +00:00
*
2024-05-17 12:10:54 +00:00
* @ note FTN packed echomail messages are ZONE agnostic .
2021-06-29 10:43:29 +00:00
* @ package App\Classes
*/
class Message extends FTNBase
{
2024-05-17 12:10:54 +00:00
use ObjectIssetFix ;
2021-08-11 13:45:30 +00:00
2021-09-12 13:06:17 +00:00
private const LOGKEY = 'FM-' ;
2024-05-17 12:10:54 +00:00
// Kludges handled here
private const kludges = [
'TZUTC:' => 'tzutc' ,
2021-06-29 10:43:29 +00:00
];
// Flags for messages
2023-07-20 13:12:26 +00:00
/** @var int Private message */
2023-06-27 07:39:11 +00:00
public const FLAG_PRIVATE = 1 << 0 ;
2023-07-20 13:12:26 +00:00
/** @var int Crash priority message (Crash + Hold = Direct) */
2023-06-27 07:39:11 +00:00
public const FLAG_CRASH = 1 << 1 ;
2023-07-20 13:12:26 +00:00
/** @var int Read by addressee */
2023-06-27 07:39:11 +00:00
public const FLAG_RECD = 1 << 2 ;
2023-07-20 13:12:26 +00:00
/** @var int Message has been sent */
2023-06-27 07:39:11 +00:00
public const FLAG_SENT = 1 << 3 ;
2023-07-20 13:12:26 +00:00
/** @var int File attached (filename in subject) */
2023-06-27 07:39:11 +00:00
public const FLAG_FILEATTACH = 1 << 4 ;
2023-07-20 13:12:26 +00:00
/** @var int Message in transit to another destination */
2023-06-27 07:39:11 +00:00
public const FLAG_INTRANSIT = 1 << 5 ;
2023-07-20 13:12:26 +00:00
/** @var int Unknown destination - node not in nodelist */
2023-06-27 07:39:11 +00:00
public const FLAG_ORPHAN = 1 << 6 ;
2023-07-20 13:12:26 +00:00
/** @var int Kill after mailing */
2023-06-27 07:39:11 +00:00
public const FLAG_KILLSENT = 1 << 7 ;
2023-07-20 13:12:26 +00:00
/** @var int Message originated here */
2023-06-27 07:39:11 +00:00
public const FLAG_LOCAL = 1 << 8 ;
2023-07-20 13:12:26 +00:00
/** @var int Hold message here to be collected (Crash + Hold = Direct) */
2023-06-27 07:39:11 +00:00
public const FLAG_HOLD = 1 << 9 ;
2023-07-20 13:12:26 +00:00
/** @var int Reserved for future use by FTS-0001 */
2023-06-27 07:39:11 +00:00
public const FLAG_UNUSED_10 = 1 << 10 ;
2023-07-20 13:12:26 +00:00
/** @var int Requesting a file (filename in subject) */
2023-06-27 07:39:11 +00:00
public const FLAG_FREQ = 1 << 11 ;
2023-07-20 13:12:26 +00:00
/** @var int Return Receipt requested */
public const FLAG_RETRECEIPT = 1 << 12 ; // (RRQ)
/** @var int Return Receipt message in response to an RRQ */
2023-06-27 07:39:11 +00:00
public const FLAG_ISRETRECEIPT = 1 << 13 ;
2023-07-20 13:12:26 +00:00
/** @var int Request audit trail */
public const FLAG_AUDITREQ = 1 << 14 ; // (ARQ)
/** @var int Requesting a file update (filename in subject) */
public const FLAG_FILEUPDATEREQ = 1 << 15 ; // (URQ)
2023-07-23 07:27:52 +00:00
/** @var int Echomail has been scanned out */
2021-06-29 10:43:29 +00:00
public const FLAG_ECHOMAIL = 1 << 16 ;
2023-07-23 07:27:52 +00:00
/** @var int Use packet password on the subject line for this message */
public const FLAG_PKTPASSWD = 1 << 17 ;
2021-06-29 10:43:29 +00:00
// FTS-0001.016 Message header 32 bytes node, net, flags, cost, date
2024-05-17 12:10:54 +00:00
private const HEADER = [ // Struct of message header
'onode' => [ 0x00 , 'v' , 2 ], // Originating Node
'dnode' => [ 0x02 , 'v' , 2 ], // Destination Node
'onet' => [ 0x04 , 'v' , 2 ], // Originating Net
'dnet' => [ 0x06 , 'v' , 2 ], // Destination Net
'flags' => [ 0x08 , 'v' , 2 ], // Message Flags
'cost' => [ 0x0a , 'v' , 2 ], // Send Cost
'datetime' => [ 0x0c , 'a20' , 20 ] // Message Date FTS-0001.016 Date: upto 20 chars null terminated
2021-06-29 10:43:29 +00:00
];
2024-05-17 12:10:54 +00:00
public const USER_FROM_LEN = 36 ; // FTS-0001.016 From Name: upto 36 chars null terminated
public const USER_TO_LEN = 36 ; // FTS-0001.016 To Name: upto 36 chars null terminated
public const SUBJECT_LEN = 71 ; // FTS-0001.016 Subject: upto 72 chars null terminated
public const AREATAG_LEN = 35 ; //
2021-06-29 10:43:29 +00:00
private array $header ; // Message Header
2024-05-17 12:10:54 +00:00
private int $tzutc = 0 ; // TZUTC that needs to be converted to be used by Carbon @see self::kludges
private Echomail | Netmail $mo ; // The object storing this packet message
2021-06-29 10:43:29 +00:00
2024-05-13 08:55:39 +00:00
/** @deprecated Not sure why this is needed? */
2021-08-16 12:30:34 +00:00
public bool $packed = FALSE ; // Has the message been packed successfully
2021-07-05 11:31:04 +00:00
// Convert characters into printable chars
// https://int10h.org/oldschool-pc-fonts/readme/#437_charset
private const CP437 = [
0x01 => 0x263a , 0x02 => 0x263b , 0x03 => 0x2665 , 0x04 => 0x2666 ,
0x05 => 0x2663 , 0x06 => 0x2660 , 0x07 => 0x2022 , 0x08 => 0x25d8 ,
0x09 => 0x25cb , 0x0a => 0x2509 , 0x0b => 0x2642 , 0x0c => 0x2640 ,
0x0d => 0x266a , 0x0e => 0x266b , 0x0f => 0x263c ,
0x10 => 0x25ba , 0x11 => 0x25ca , 0x12 => 0x2195 , 0x13 => 0x203c ,
0x14 => 0x00b6 , 0x15 => 0x00a7 , 0x16 => 0x25ac , 0x17 => 0x21a8 ,
0x18 => 0x2191 , 0x19 => 0x2193 , 0x1a => 0x2192 , 0x1b => 0x2190 ,
0x1c => 0x221f , 0x1d => 0x2194 , 0x1e => 0x25bc , 0x1f => 0x25bc ,
0x7f => 0x2302 ,
0x80 => 0x00c7 , 0x81 => 0x00fc , 0x82 => 0x00e9 , 0x83 => 0x00e2 ,
0x84 => 0x00e4 , 0x85 => 0x00e0 , 0x86 => 0x00e5 , 0x87 => 0x00e7 ,
0x88 => 0x00ea , 0x89 => 0x00eb , 0x8a => 0x00e8 , 0x8b => 0x00ef ,
0x8c => 0x00ee , 0x8d => 0x00ec , 0x8e => 0x00c4 , 0x8f => 0x00c5 ,
0x90 => 0x00c9 , 0x91 => 0x00e6 , 0x92 => 0x00c6 , 0x93 => 0x00f4 ,
0x94 => 0x00f6 , 0x95 => 0x00f2 , 0x96 => 0x00fb , 0x97 => 0x00f9 ,
0x98 => 0x00ff , 0x99 => 0x00d6 , 0x9a => 0x00dc , 0x9b => 0x00a2 ,
0x9c => 0x00a3 , 0x9d => 0x00a5 , 0x9e => 0x20a7 , 0x9f => 0x0192 ,
0xa0 => 0x00e1 , 0xa1 => 0x00ed , 0xa2 => 0x00f3 , 0xa3 => 0x00fa ,
0xa4 => 0x00f1 , 0xa5 => 0x00d1 , 0xa6 => 0x00aa , 0xa7 => 0x00ba ,
0xa8 => 0x00bf , 0xa9 => 0x2310 , 0xaa => 0x00ac , 0xab => 0x00bd ,
0xac => 0x00bc , 0xad => 0x00a1 , 0xae => 0x00ab , 0xaf => 0x00bb ,
0xb0 => 0x2591 , 0xb1 => 0x2592 , 0xb2 => 0x2593 , 0xb3 => 0x2502 ,
0xb4 => 0x2524 , 0xb5 => 0x2561 , 0xb6 => 0x2562 , 0xb7 => 0x2556 ,
0xb8 => 0x2555 , 0xb9 => 0x2563 , 0xba => 0x2551 , 0xbb => 0x2557 ,
0xbc => 0x255d , 0xbd => 0x255c , 0xbe => 0x255b , 0xbf => 0x2510 ,
0xc0 => 0x2514 , 0xc1 => 0x2534 , 0xc2 => 0x252c , 0xc3 => 0x251c ,
0xc4 => 0x2500 , 0xc5 => 0x253c , 0xc6 => 0x255e , 0xc7 => 0x255f ,
0xc8 => 0x255a , 0xc9 => 0x2554 , 0xca => 0x2569 , 0xcb => 0x2566 ,
0xcc => 0x2560 , 0xcd => 0x2550 , 0xce => 0x256c , 0xcf => 0x2567 ,
0xd0 => 0x2568 , 0xd1 => 0x2564 , 0xd2 => 0x2565 , 0xd3 => 0x2559 ,
0xd4 => 0x2558 , 0xd5 => 0x2552 , 0xd6 => 0x2553 , 0xd7 => 0x256b ,
0xd8 => 0x256a , 0xd9 => 0x2518 , 0xda => 0x250c , 0xdb => 0x2588 ,
0xdc => 0x2584 , 0xdd => 0x258c , 0xde => 0x2590 , 0xdf => 0x2580 ,
0xe0 => 0x03b1 , 0xe1 => 0x00df , 0xe2 => 0x0393 , 0xe3 => 0x03c0 ,
0xe4 => 0x03a3 , 0xe5 => 0x03c3 , 0xe6 => 0x00b5 , 0xe7 => 0x03c4 ,
0xe8 => 0x03a6 , 0xe9 => 0x0398 , 0xea => 0x03a9 , 0xeb => 0x03b4 ,
0xec => 0x221e , 0xed => 0x03c6 , 0xee => 0x03b5 , 0xef => 0x2229 ,
0xf0 => 0x2261 , 0xf1 => 0x00b1 , 0xf2 => 0x2265 , 0xf3 => 0x2264 ,
0xf4 => 0x2320 , 0xf5 => 0x2321 , 0xf6 => 0x00f7 , 0xf7 => 0x2248 ,
0xf8 => 0x00b0 , 0xf9 => 0x2219 , 0xfa => 0x00b7 , 0xfb => 0x221a ,
0xfc => 0x207f , 0xfd => 0x00b2 , 0xfe => 0x25a0 , 0xff => 0x00a0 ,
];
2024-05-17 12:10:54 +00:00
public static function header_len () : int
{
return collect ( static :: HEADER ) -> sum ( function ( $item ) { return Arr :: get ( $item , 2 ); });
}
/**
* Pack a message for rendering in a packet
*
* @ param Echomail | Netmail $o
* @ return self
*/
public static function packMessage ( Echomail | Netmail $o ) : self
{
$oo = new self ( $o -> fftn -> zone );
$oo -> mo = $o ;
return $oo ;
}
2024-05-13 08:55:39 +00:00
/**
* Parse a message from a packet
*
2024-05-17 12:10:54 +00:00
* Each message has 3 discreet structures
* + Pre kludge lines , each line starting with < soh > and completing with < cr >
* + Message content , ending with
* - tagline starting with ... ( optional )
* - tearline starting with --- ( optional )
* - origin line starting with ' * Origin ' ( optional for netmail )
* + Post kludge lines , which may or may not start with < soh >. There should be an < soh > if there wasnt an origin line .
* - [ < soh > ] Via ... ( netmail )
* - SEEN - BY : ... ( echomail )
* - PATH : ... ( echomail )
*
2024-05-13 08:55:39 +00:00
* @ param string $msg
2024-05-17 12:10:54 +00:00
* @ param Zone $zone
* @ return Echomail | Netmail
2024-05-13 08:55:39 +00:00
* @ throws \Exception
*/
2024-05-17 12:10:54 +00:00
public static function parseMessage ( string $msg , Zone $zone ) : Echomail | Netmail
2024-05-13 08:55:39 +00:00
{
2024-05-17 12:10:54 +00:00
Log :: info ( sprintf ( '%s:= Processing message [%d] bytes from zone [%d]' , self :: LOGKEY , strlen ( $msg ), $zone -> zone_id ));
2024-05-13 08:55:39 +00:00
2024-05-17 12:10:54 +00:00
$header_len = self :: header_len ();
2024-05-13 08:55:39 +00:00
$o = new self ( $zone );
try {
2024-05-17 12:10:54 +00:00
$o -> header = unpack ( self :: unpackheader ( self :: HEADER ), substr ( $msg , 0 , $header_len ));
2024-05-13 08:55:39 +00:00
} catch ( \Exception $e ) {
2024-05-17 12:10:54 +00:00
Log :: error ( sprintf ( '%s:! Error bad packet header' , self :: LOGKEY ),[ 'e' => $e -> getMessage (), 'header' => substr ( $msg , 0 , $header_len )]);
2024-05-13 08:55:39 +00:00
2024-05-17 12:10:54 +00:00
throw new InvalidPacketException ( $e -> getMessage ());
2024-05-13 08:55:39 +00:00
}
$ptr = 0 ;
// To User
2024-05-17 12:10:54 +00:00
$o -> header [ 'user_to' ] = strstr ( substr ( $msg , $header_len + $ptr ), " \x00 " , TRUE );
$ptr += strlen ( $o -> header [ 'user_to' ]) + 1 ;
2024-05-13 08:55:39 +00:00
// From User
2024-05-17 12:10:54 +00:00
$o -> header [ 'user_from' ] = strstr ( substr ( $msg , $header_len + $ptr ), " \x00 " , TRUE );
$ptr += strlen ( $o -> header [ 'user_from' ]) + 1 ;
2024-05-13 08:55:39 +00:00
// Subject
2024-05-17 12:10:54 +00:00
$o -> header [ 'subject' ] = strstr ( substr ( $msg , $header_len + $ptr ), " \x00 " , TRUE );
$ptr += strlen ( $o -> header [ 'subject' ]) + 1 ;
2024-05-13 08:55:39 +00:00
// Check if this is an Echomail
2024-05-17 12:10:54 +00:00
if ( ! strncmp ( substr ( $msg , $header_len + $ptr ), 'AREA:' , 5 )) {
$o -> header [ 'echoarea' ] = strtoupper ( substr ( $msg , $header_len + $ptr + 5 , strpos ( $msg , " \r " , $header_len + $ptr + 5 ) - ( $header_len + $ptr + 5 )));
$ptr += strlen ( $o -> header [ 'echoarea' ]) + 5 + 1 ;
$eao = $zone -> domain -> echoareas -> where ( 'name' , $o -> header [ 'echoarea' ]) -> pop ();
$o -> mo = new Echomail ;
if ( $eao )
$o -> mo -> echoarea_id = $eao -> id ;
else
$o -> mo -> set_echoarea = $o -> header [ 'echoarea' ];
} else {
$o -> mo = new Netmail ;
2024-05-13 08:55:39 +00:00
}
2024-05-17 12:10:54 +00:00
$o -> mo = $o -> unpackMessage ( substr ( $msg , $header_len + $ptr ), $o -> mo );
$o -> mo -> to = $o -> header [ 'user_to' ];
$o -> mo -> from = $o -> header [ 'user_from' ];
$o -> mo -> subject = $o -> header [ 'subject' ];
$o -> mo -> datetime = $o -> datetime ;
$o -> mo -> tzoffset = $o -> datetime -> utcOffset ();
$o -> mo -> flags = $o -> header [ 'flags' ];
$o -> mo -> cost = $o -> header [ 'cost' ];
$o -> mo -> msg_crc = md5 ( $o -> mo -> msg_src );
$o -> mo -> fftn_id = $o -> fftn ? -> id ;
switch ( get_class ( $o -> mo )) {
case Echomail :: class :
break ;
case Netmail :: class :
$o -> mo -> tftn_id = $o -> tftn ? -> id ;
break ;
default :
throw new InvalidPacketException ( 'Unknown message class: ' . get_class ( $o -> mo ));
}
2024-05-13 08:55:39 +00:00
if (( $x = $o -> validate ()) -> fails ()) {
2024-05-17 12:10:54 +00:00
$o -> mo -> errors = $x ;
Log :: debug ( sprintf ( '%s:! Message fails validation (%s@%s->%s@%s)' , self :: LOGKEY , $o -> mo -> from , $o -> fftn_t , $o -> mo -> to , $o -> tftn_t ),[ 'result' => $o -> mo -> errors -> errors ()]);
2024-05-13 08:55:39 +00:00
}
2024-05-17 12:10:54 +00:00
return $o -> mo ;
2024-05-13 08:55:39 +00:00
}
/**
* Translate the string into something printable via the web
*
* @ param string $string
* @ param array $skip
* @ return string
*/
public static function tr ( string $string , array $skip = [ 0x0a , 0x0d ]) : string
{
$tr = [];
foreach ( self :: CP437 as $k => $v ) {
if ( in_array ( $k , $skip ))
continue ;
$tr [ chr ( $k )] = '&#' . $v ;
}
return strtr ( $string , $tr );
}
/**
* Packets have no concept of a zone , so we add it for any calculations that could use it
2024-05-17 12:10:54 +00:00
* This is taken from the sender
2024-05-13 08:55:39 +00:00
*
2024-05-17 12:10:54 +00:00
* @ param Zone $zone
2024-05-13 08:55:39 +00:00
*/
2024-05-17 12:10:54 +00:00
public function __construct ( Zone $zone )
2021-06-29 10:43:29 +00:00
{
2021-08-29 13:58:12 +00:00
$this -> zone = $zone ;
2021-07-15 14:54:23 +00:00
}
2021-06-29 10:43:29 +00:00
public function __get ( $key )
{
2024-05-17 12:10:54 +00:00
// @todo Do we need all these key values?
Log :: debug ( sprintf ( '%s:/ Requesting key for Message::class [%s]' , self :: LOGKEY , $key ));
2021-06-29 10:43:29 +00:00
switch ( $key ) {
// From Addresses
2021-08-20 14:33:41 +00:00
case 'fz' : return Arr :: get ( $this -> src , 'z' );
2024-05-17 12:10:54 +00:00
case 'fn' : return ( $x = $this -> src ) ? Arr :: get ( $x , 'n' ) : Arr :: get ( $this -> header , 'onet' );
case 'ff' : return ( $x = $this -> src ) ? Arr :: get ( $x , 'f' ) : Arr :: get ( $this -> header , 'onode' );
case 'fp' : return $this -> mo -> kludges -> get ( 'FMPT:' ) ? : Arr :: get ( $this -> src , 'p' , Arr :: get ( $this -> header , 'opoint' , 0 ));
2021-09-11 13:32:10 +00:00
case 'fd' : return Arr :: get ( $this -> src , 'd' );
case 'fzone' :
// Use the zone if this class was called with it.
2023-06-27 07:39:11 +00:00
if ( $this -> zone && ( $this -> fz === $this -> zone -> zone_id ))
2021-09-11 13:32:10 +00:00
return $this -> zone ;
2022-12-03 05:00:38 +00:00
if ( $this -> fdomain ) {
2023-06-27 07:39:11 +00:00
if (( $x = $this -> fdomain -> zones -> search ( function ( $item ) { return $item -> zone_id === $this -> fz ; })) !== FALSE )
2021-09-11 13:32:10 +00:00
return $this -> fdomain -> zones -> get ( $x );
}
// No domain, so we'll use the default zone
return Zone :: where ( 'zone_id' , $this -> fz )
-> where ( 'default' , TRUE )
-> single ();
2024-05-17 12:10:54 +00:00
case 'fdomain' :
// We'll use the zone's domain if this method class was called with a zone
if ( $this -> zone && (( $this -> zone -> domain -> name === Arr :: get ( $this -> src , 'd' )) || ! Arr :: get ( $this -> src , 'd' )))
return $this -> zone -> domain ;
// If we get the domain from the packet, we'll find it
if ( $x = Arr :: get ( $this -> src , 'd' )) {
return Domain :: where ( 'name' , $x ) -> single ();
}
return NULL ;
// To Addresses
// Echomail doesnt have a zone, so we'll use the source zone
case 'tz' : return Arr :: get ( $this -> isEchomail () ? $this -> src : $this -> dst , 'z' );
case 'tn' : return Arr :: get ( $this -> header , 'dnet' );
case 'tf' : return Arr :: get ( $this -> header , 'dnode' );
case 'tp' : return $this -> mo -> kludges -> get ( 'TOPT:' ) ? : Arr :: get ( $this -> header , 'dpoint' , 0 );
2021-06-29 10:43:29 +00:00
2022-11-11 11:57:40 +00:00
case 'tzone' :
// Use the zone if this class was called with it.
2023-06-27 07:39:11 +00:00
if ( $this -> zone && ( $this -> tz === $this -> zone -> zone_id ))
2022-11-11 11:57:40 +00:00
return $this -> zone ;
2022-12-03 05:00:38 +00:00
if ( $this -> tdomain ) {
2023-06-27 07:39:11 +00:00
if (( $x = $this -> tdomain -> zones -> search ( function ( $item ) { return $item -> zone_id === $this -> tz ; })) !== FALSE )
2022-11-11 11:57:40 +00:00
return $this -> tdomain -> zones -> get ( $x );
}
// No domain, so we'll use the default zone
return Zone :: where ( 'zone_id' , $this -> tz )
-> where ( 'default' , TRUE )
-> single ();
2024-05-17 12:10:54 +00:00
case 'tdomain' :
// We'll use the zone's domain if this method class was called with a zone
if ( $this -> zone && (( $this -> zone -> domain -> name === Arr :: get ( $this -> dst , 'd' )) || ! Arr :: get ( $this -> dst , 'd' )))
return $this -> zone -> domain ;
2022-11-11 11:57:40 +00:00
2024-05-17 12:10:54 +00:00
// If we get the domain from the packet, we'll find it
if ( $x = Arr :: get ( $this -> dst , 'd' )) {
return Domain :: where ( 'name' , $x ) -> single ();
}
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// Otherwise we'll assume the same as the source domain
return $this -> fdomain ? : NULL ;
case 'fftn_t' :
2021-06-29 10:43:29 +00:00
case 'fftn' :
2024-05-17 12:10:54 +00:00
case 'tftn_t' :
2021-08-18 14:20:34 +00:00
case 'tftn' :
return parent :: __get ( $key );
2021-07-15 14:54:23 +00:00
2023-01-01 14:05:44 +00:00
// For 5D we need to include the domain
2024-05-17 12:10:54 +00:00
/* @deprecated - is this required? */
2021-08-20 14:33:41 +00:00
case 'fboss' :
2023-01-01 14:05:44 +00:00
return sprintf ( '%d:%d/%d' , $this -> fz , $this -> fn , $this -> ff ) . (( $x = $this -> fdomain ) ? '@' . $x -> name : '' );
2021-08-20 14:33:41 +00:00
case 'tboss' :
2023-01-01 14:05:44 +00:00
return sprintf ( '%d:%d/%d' , $this -> tz , $this -> tn , $this -> tf ) . (( $x = $this -> tdomain ) ? '@' . $x -> name : '' );
2021-08-20 14:33:41 +00:00
case 'fboss_o' :
return Address :: findFTN ( $this -> fboss );
case 'tboss_o' :
return Address :: findFTN ( $this -> tboss );
2024-05-17 12:10:54 +00:00
// Convert our message (header[datetime]) with our TZUTC into a Carbon date
case 'datetime' :
2021-08-27 13:16:39 +00:00
try {
2024-05-17 12:10:54 +00:00
if ( str_contains ( $x = rtrim ( Arr :: get ( $this -> header , $key ), " \x00 " ), " \x00 " ))
2022-11-05 08:03:33 +00:00
throw new \Exception ( 'Date contains null values.' );
2021-08-27 13:16:39 +00:00
return Carbon :: createFromFormat ( 'd M y H:i:s O' ,
2022-11-05 08:03:33 +00:00
sprintf ( '%s %s%04d' , $x ,( $this -> tzutc < 0 ) ? '-' : '+' , abs ( $this -> tzutc )));
2021-08-27 13:16:39 +00:00
2022-11-05 08:03:33 +00:00
} catch ( InvalidFormatException | \Exception $e ) {
2023-07-20 10:04:41 +00:00
Log :: error ( sprintf ( '%s:! Date doesnt parse [%s] (%s)' , self :: LOGKEY , $e -> getMessage (), Arr :: get ( $this -> header , $key )));
2023-01-11 02:08:59 +00:00
throw new \Exception ( sprintf ( '%s (%s)' , $e -> getMessage (), hex_dump ( Arr :: get ( $this -> header , $key ))));
2021-08-27 13:16:39 +00:00
}
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// The source of the message
case 'src' :
// If this is a netmail, then our 4D details are in the netmail
if ( $this -> mo instanceof Netmail ) {
/*
* The INTL control paragraph shall be used to give information about
* the zone numbers of the original sender and the ultimate addressee
* of a message .
*
* < SOH > " INTL " < destination address > " " < origin address >< CR >
*/
if ( $this -> mo -> kludges -> has ( 'INTL' )) {
// INTL kludge is in Netmail, so we'll do some validation:
list ( $dst , $src ) = explode ( ' ' , $this -> mo -> kludges -> get ( 'INTL' ));
$src = Address :: parseFTN ( $src );
// @todo move to validation
if (( $src [ 'n' ] !== Arr :: get ( $this -> header , 'onet' )) || ( $src [ 'f' ] !== Arr :: get ( $this -> header , 'onode' ))) {
Log :: error ( sprintf ( '%s:! INTL src address [%s] doesnt match packet' , self :: LOGKEY , $src ),[ 'src' => $src , 'fn' => Arr :: get ( $this -> header , 'onet' ), 'ff' => Arr :: get ( $this -> header , 'onode' )]);
}
return $src ;
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
} elseif ( $this -> mo instanceof Echomail ) {
// Work out our zone/point
// http://ftsc.org/docs/fsc-0068.001
// MSGID should be the basis of the source, if it cannot be obtained from the origin
// If the message was gated, we'll use the gateid
$m = [];
if ( $this -> mo -> origin && preg_match ( '#\(([0-9]+:[0-9]+/[0-9]+)?\.?([0-9]+)?@?([A-Za-z-_~]+)?\)$#' , $this -> mo -> origin , $m )) {
return Address :: parseFTN ( $m [ 1 ] . (( isset ( $m [ 2 ]) && $m [ 2 ] != '' ) ? '.' . $m [ 2 ] : '' ) . ( isset ( $m [ 3 ]) ? '@' . $m [ 3 ] : '' ));
} elseif (( $this -> mo -> msgid || $this -> mo -> gateid ) && preg_match ( '#([0-9]+:[0-9]+/[0-9]+)?\.?([0-9]+)?(\.[0-9]+)?@?([A-Za-z-_~]+)?\ +#' , $this -> mo -> gateid ? : $this -> mo -> msgid , $m )) {
try {
return Address :: parseFTN ( $m [ 1 ] . (( isset ( $m [ 2 ]) && $m [ 2 ] != '' ) ? '.' . $m [ 2 ] : '' ) . ( isset ( $m [ 4 ]) ? '@' . $m [ 4 ] : '' ));
} catch ( \Exception $e ) {
Log :: error ( sprintf ( '%s:! MSGID [%s] address is invalid [%s]' , self :: LOGKEY , $this -> mo -> msgid , $e -> getMessage ()));
}
// Otherwise get it from our zone object and packet header
} elseif ( $this -> zone ) {
return Address :: parseFTN ( sprintf ( '%d:%d/%d.%d@%s' , $this -> zone -> zone_id , $this -> fn , $this -> ff , $this -> fp , $this -> zone -> domain -> name ));
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
} else {
throw new InvalidPacketException ( 'Dont know what type of packet this is' );
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
break ;
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
case 'dst' :
// If this is a netmail, then our 4D details are in the netmail
if ( $this -> mo instanceof Netmail ) {
/*
* The INTL control paragraph shall be used to give information about
* the zone numbers of the original sender and the ultimate addressee
* of a message .
*
* < SOH > " INTL " < destination address > " " < origin address >< CR >
*/
if ( $this -> mo -> kludges -> has ( 'INTL' )) {
// INTL kludge is in Netmail, so we'll do some validation:
list ( $dst , $src ) = explode ( ' ' , $this -> mo -> kludges -> get ( 'INTL' ));
$dst = Address :: parseFTN ( $dst );
// @todo move to validation
if (( $dst [ 'n' ] !== $this -> tn ) || ( $dst [ 'f' ] !== $this -> tf )) {
Log :: error ( sprintf ( '%s:! INTL dst address [%s] doesnt match packet' , self :: LOGKEY , $dst ),[ 'dst' => $dst , 'tn' => $this -> tn , 'tf' => $this -> tf ]);
}
return $dst ;
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
} elseif ( $this -> mo instanceof Echomail ) {
// Echomail doesnt have DST addresses
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
} else {
throw new InvalidPacketException ( 'Dont know what type of packet this is' );
}
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
break ;
2023-07-15 12:10:05 +00:00
2021-07-15 14:54:23 +00:00
default :
throw new \Exception ( 'Unknown key: ' . $key );
}
}
2022-02-12 23:24:31 +00:00
/**
* When we serialise this object , we ' ll need to utf8_encode some values
*
* @ return array
*/
public function __serialize () : array
{
return $this -> encode ();
}
2021-06-29 10:43:29 +00:00
/**
* Export an FTN message , ready for sending .
*
* @ return string
2024-05-17 12:10:54 +00:00
* @ throws \Exception
2021-06-29 10:43:29 +00:00
*/
public function __toString () : string
{
2024-05-17 12:10:54 +00:00
$return = pack ( collect ( self :: HEADER ) -> pluck ( 1 ) -> join ( '' ),
$this -> mo -> fftn -> node_id , // Originating Node
$this -> mo -> tftn -> node_id , // Destination Node
$this -> mo -> fftn -> host_id , // Originating Net
$this -> mo -> tftn -> host_id , // Destination Net
$this -> mo -> flags &~ ( self :: FLAG_INTRANSIT | self :: FLAG_LOCAL ), // Turn off our local/intransit bits
$this -> mo -> cost ,
$this -> mo -> date -> format ( 'd M y H:i:s' ),
2021-06-29 10:43:29 +00:00
);
2024-05-17 12:10:54 +00:00
$return .= $this -> mo -> to . " \00 " ;
$return .= $this -> mo -> from . " \00 " ;
$return .= $this -> mo -> subject . " \00 " ;
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
if ( $this -> mo instanceof Echomail )
$return .= sprintf ( " AREA:%s \r " , strtoupper ( $this -> mo -> echoarea -> name ));
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
if ( $this -> mo instanceof Netmail ) {
// @todo If the message is local, then our kludges are not in the msg itself, we'll add them
if ( $this -> isFlagSet ( self :: FLAG_LOCAL ))
Log :: debug ( sprintf ( '%s:^ Local message, we may need to set our INTL/FMPT/TOPT' , self :: LOGKEY ));
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
$return .= sprintf ( " \01 INTL %s \r " , $this -> mo -> kludges -> get ( 'INTL' ));
2023-07-20 10:04:41 +00:00
2024-05-17 12:10:54 +00:00
if ( $this -> mo -> kludges -> has ( 'FMPT' ))
$return .= sprintf ( " \01 FMPT %d \r " , $this -> mo -> kludges -> get ( 'FMPT' ));
if ( $this -> mo -> kludges -> has ( 'TOPT' ))
$return .= sprintf ( " \01 TOPT %d \r " , $this -> mo -> kludges -> get ( 'TOPT' ));
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
// Add some kludges
$return .= sprintf ( " \01 TZUTC: %s \r " , str_replace ( '+' , '' , $this -> mo -> date -> getOffsetString ( '' )));
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
$return .= sprintf ( " \01 MSGID: %s \r " , $this -> mo -> msgid );
2022-02-11 23:21:46 +00:00
2024-05-17 12:10:54 +00:00
if ( $this -> mo -> replyid )
$return .= sprintf ( " \01 REPLY: %s \r " , $this -> mo -> replyid );
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
foreach ( $this -> mo -> kludges as $k => $v )
$return .= sprintf ( " \01 %s %s \r " , $k , $v );
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
$return .= $this -> mo -> content ;
2023-07-20 10:04:41 +00:00
2024-05-17 12:10:54 +00:00
if ( $this -> mo instanceof Netmail ) {
// @todo automatically include our address in the path
2021-07-15 14:54:23 +00:00
2024-05-17 12:10:54 +00:00
foreach ( $this -> mo -> path as $ao )
$return .= sprintf ( " \x01 Via %s \r " , $this -> mo -> via ( $ao ));
2021-07-30 14:35:52 +00:00
} else {
2024-05-17 12:10:54 +00:00
// FTS-0004.001/FSC-0068.001 The message SEEN-BY lines
// FTS-0004.001/FSC-0068.001 The message PATH lines
// @todo we need to include our rogue_seenbys
// @todo make sure a point is NOT in the seenby/path when exporting
// @todo automatically include our address in the seenby/path
$return .= $this -> aka_trim ( $this -> mo -> seenby , 'SEEN-BY:' ) . " \r " ;
$return .= " \x01 " . $this -> aka_trim ( $this -> mo -> path , 'PATH:' ) . " \r " ;
2021-06-29 10:43:29 +00:00
}
$return .= " \00 " ;
return $return ;
}
2022-02-12 23:24:31 +00:00
/**
* When we unserialize , we ' ll restore ( utf8_decode ) some values
*
* @ param array $values
*/
public function __unserialize ( array $values ) : void
{
$this -> decode ( $values );
}
2023-11-26 22:00:32 +00:00
/**
* Reduce our PATH / SEEN - BY for messages as per FSC - 006 8
*
* @ param Collection $path
* @ param string $prefix
* @ param int $len
* @ param string $delim
* @ return string
*/
2024-05-17 12:10:54 +00:00
private function aka_trim ( Collection $path , string $prefix , int $len = 79 , string $delim = " \r " ) : string
2023-11-26 22:00:32 +00:00
{
$cur = NULL ;
$result = $prefix ;
$c = strlen ( $prefix );
2024-05-17 12:10:54 +00:00
foreach ( $path as $ao ) {
[ $host , $node ] = explode ( '/' , $ao -> ftn2d );
2023-11-26 22:00:32 +00:00
if (( $c + strlen ( ' ' . $host )) > $len ) {
$result .= ( $x = $delim . $prefix );
$c = strlen ( $x );
$cur = NULL ;
}
if ( $host !== $cur ) {
$cur = $host ;
2024-05-17 12:10:54 +00:00
$result .= ( $x = ' ' . $ao -> ftn2d );
2023-11-26 22:00:32 +00:00
} else {
$result .= ( $x = ' ' . $node );
}
$c += strlen ( $x );
}
return $result ;
}
2024-05-17 12:10:54 +00:00
private function isEchomail () : bool
2021-07-15 14:54:23 +00:00
{
2024-05-17 12:10:54 +00:00
return $this -> mo instanceof Echomail ;
2021-07-15 14:54:23 +00:00
}
2021-06-29 10:43:29 +00:00
/**
2024-05-17 12:10:54 +00:00
* If this message doesnt have an AREATAG , then its a netmail .
2021-06-29 10:43:29 +00:00
*
2024-05-17 12:10:54 +00:00
* @ return bool
2021-06-29 10:43:29 +00:00
*/
2024-05-17 12:10:54 +00:00
public function isNetmail () : bool
2021-06-29 10:43:29 +00:00
{
2024-05-17 12:10:54 +00:00
return $this -> mo instanceof Netmail ;
2021-06-29 10:43:29 +00:00
}
/**
* Extract information out of the message text .
*
* @ param string $message
2024-05-17 12:10:54 +00:00
* @ param Echomail | Netmail $o
* @ return Echomail | Netmail
* @ throws InvalidPacketException
2021-06-29 10:43:29 +00:00
*/
2024-05-17 12:10:54 +00:00
private function unpackMessage ( string $message , Echomail | Netmail $o ) : Echomail | Netmail
2021-06-29 10:43:29 +00:00
{
// Remove DOS \n\r
$message = preg_replace ( " / \n \r / " , " \r " , $message );
2021-08-27 13:16:39 +00:00
$message = preg_replace ( " / \r \n / " , " \r " , $message );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// First find our kludge lines
$ptr_start = 0 ;
2021-11-28 03:02:50 +00:00
2024-05-17 12:10:54 +00:00
while ( substr ( $message , $ptr_start , 1 ) === " \x01 " ) {
$ptr_end = strpos ( $message , " \r " , $ptr_start );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
$m = [];
$kludge = substr ( $message , $ptr_start + 1 , $ptr_end - $ptr_start - 1 );
preg_match ( '/^([^\s]+:?)+\s+(.*)$/' , $kludge , $m );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// Catch any kludges we need to process here
if ( array_key_exists ( $m [ 1 ], self :: kludges ))
$this -> { self :: kludges [ $m [ 1 ]]} = $m [ 2 ];
else
$o -> kludges = [ $m [ 1 ], $m [ 2 ]];
2021-12-01 11:45:51 +00:00
2024-05-17 12:10:54 +00:00
$ptr_start = $ptr_end + 1 ;
}
2021-11-28 13:12:37 +00:00
2024-05-17 12:10:54 +00:00
// Next our message content ends with '\r * Origin: ... \r' or <soh>...
// FTS-0004.001
if ( $ptr_end = strpos ( $message , " \r * Origin: " , $ptr_start )) {
// Find the <cr>
$ptr_end = strpos ( $message , " \r " , $ptr_end + 1 );
2021-11-28 03:02:50 +00:00
2024-05-17 12:10:54 +00:00
if ( ! $ptr_end )
throw new InvalidPacketException ( 'Couldnt find the end of the origin' );
2021-11-28 03:02:50 +00:00
2024-05-17 12:10:54 +00:00
} elseif ( ! $ptr_end = strpos ( $message , " \r \x01 " , $ptr_start )) {
throw new InvalidPacketException ( 'Couldnt parse the end of the content' );
}
2021-07-30 14:35:52 +00:00
2024-05-17 12:10:54 +00:00
// Process the message content
if ( $content = substr ( $message , $ptr_start , $ptr_end - $ptr_start )) {
$o -> msg_src = $content ;
$ptr_content_start = 0 ;
2023-08-04 12:06:29 +00:00
2024-05-17 12:10:54 +00:00
// See if we have a tagline
if ( $ptr_content_end = strpos ( $content , " \r \r ... " , $ptr_content_start )) {
$o -> msg = substr ( $content , $ptr_content_start , $ptr_content_end );
$ptr_content_start = $ptr_content_end + 6 ;
2023-08-04 12:06:29 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_end = strpos ( $content , " \r " , $ptr_content_start );
2022-02-19 05:33:14 +00:00
2024-05-17 12:10:54 +00:00
// If there is no terminating "\r", then that's it
if ( ! $ptr_content_end )
$o -> set_tagline = substr ( $content , $ptr_content_start );
else
$o -> set_tagline = substr ( $content , $ptr_content_start , $ptr_content_end - $ptr_content_start );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_start = $ptr_content_end ;
2021-06-29 10:43:29 +00:00
}
2024-05-17 12:10:54 +00:00
// See if we have a tearline
if ( $ptr_content_end = strpos ( $content , " \r \r --- " , $ptr_content_start )) {
if ( ! $ptr_content_start )
$o -> msg = substr ( $content , $ptr_content_start , $ptr_content_end - 1 );
$ptr_content_start = $ptr_content_end + 6 ;
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_end = strpos ( $content , " \r " , $ptr_content_start );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// If there is no terminating "\r", then that's it
if ( ! $ptr_content_end )
$o -> set_tearline = substr ( $content , $ptr_content_start );
else
$o -> set_tearline = substr ( $content , $ptr_content_start , $ptr_content_end - $ptr_content_start );
2021-06-29 13:23:59 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_start = $ptr_content_end ;
2021-06-29 10:43:29 +00:00
}
2024-05-17 12:10:54 +00:00
// See if we have an origin
if ( $ptr_content_end = strpos ( $content , " \r * Origin: " , $ptr_content_start )) {
if ( ! $ptr_content_start )
$o -> msg = substr ( $content , $ptr_content_start , $ptr_content_end - 1 );
$ptr_content_start = $ptr_content_end + 12 ;
2021-07-15 14:54:23 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_end = strpos ( $content , " \r " , $ptr_content_start );
2022-11-11 11:57:40 +00:00
2024-05-17 12:10:54 +00:00
// If there is no terminating "\r", then that's it
if ( ! $ptr_content_end )
$o -> set_origin = substr ( $content , $ptr_content_start );
else
$o -> set_origin = substr ( $content , $ptr_content_start , $ptr_content_end - $ptr_content_start );
2022-02-11 23:21:46 +00:00
2024-05-17 12:10:54 +00:00
$ptr_content_start = $ptr_content_end ;
}
2021-09-13 13:02:39 +00:00
2024-05-17 12:10:54 +00:00
// Quick validation that we are done
if ( $ptr_content_start )
throw new InvalidPacketException ( 'There is more data in the message content?' );
}
2021-08-29 13:58:12 +00:00
2024-05-17 12:10:54 +00:00
$ptr_start = $ptr_end + 1 ;
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
// Finally work out control kludges
foreach ( collect ( explode ( " \r " , substr ( $message , $ptr_start ))) -> filter () as $line ) {
// If the line starts with <soh> ignore it
if ( substr ( $line , 0 , 1 ) === " \x01 " )
$line = ltrim ( $line , " \x01 " );
2021-06-29 10:43:29 +00:00
2024-05-17 12:10:54 +00:00
$m = [];
preg_match ( '/^([^\s]+:?)+\s+(.*)$/' , $line , $m );
$o -> kludges = [ $m [ 1 ], $m [ 2 ]];
2021-06-29 10:43:29 +00:00
}
2021-08-19 13:35:48 +00:00
2024-05-17 12:10:54 +00:00
return $o ;
2021-06-29 10:43:29 +00:00
}
/**
* Validate details about this message
*
* @ return \Illuminate\Contracts\Validation\Validator
*/
2021-08-29 13:58:12 +00:00
public function validate () : ValidatorResult
2021-06-29 10:43:29 +00:00
{
// Check lengths
2024-05-17 12:10:54 +00:00
$validator = Validator :: make (
array_merge ( $this -> mo -> toArray (),[
'echoarea' => $this -> isEchomail () ? ( $this -> mo -> set -> get ( 'set_echoarea' ) ? : $this -> mo -> echoarea -> name ) : NULL ,
'onode' => $this -> ff ,
'dnode' => $this -> tf ,
'onet' => $this -> fn ,
'dnet' => $this -> tn ,
'ozone' => $this -> fz ,
'dzone' => $this -> tz ,
])
,[
'from' => 'required|min:1|max:' . self :: USER_FROM_LEN ,
'to' => 'required|min:1|max:' . self :: USER_TO_LEN ,
'subject' => 'required|max:' . self :: SUBJECT_LEN ,
'datetime' => 'required|date' , // |after:30 days ago', // @todo within "x" days
'tzoffset' => 'present|numeric' ,
'flags' => 'required|numeric' ,
'cost' => 'required|numeric' ,
'msgid' => 'sometimes|min:1' ,
'replyid' => 'sometimes|min:1' ,
'msg' => 'required|min:1' , // @todo max message length?
'msg_crc' => 'required|size:32' ,
'tagline' => 'sometimes|min:1|max:255' ,
'tearline' => 'sometimes|min:1|max:255' ,
'origin' => 'sometimes|min:1|max:255' ,
'local' => 'sometimes|boolean' ,
'fftn_id' => 'required|exists:App\Models\Address,id' ,
'tftn_id' => $this -> isNetmail () ? 'required|exists:App\Models\Address,id' : 'prohibited' ,
'rogue_path' => $this -> isEchomail () ? 'sometimes|array' : 'prohibited' ,
'rogue_seenby' => $this -> isEchomail () ? 'sometimes|array' : 'prohibited' ,
'kludges' => 'present|array|min:1' , // @todo add in required KEYS like INTL for netmails
'onode' => [ 'required' , new TwoByteIntegerWithZero ],
'dnode' => [ 'required' , new TwoByteIntegerWithZero ],
'onet' => [ 'required' , new TwoByteInteger ],
'dnet' => [ 'required' , new TwoByteInteger ],
'ozone' => [ 'required' , new TwoByteInteger ],
'dzone' => [ 'required' , new TwoByteInteger ],
'echoarea_id' => $this -> isEchomail () ? 'sometimes|exists:App\Models\Echoarea,id' : 'prohibited' ,
'echoarea' => $this -> isEchomail () ? 'required|max:' . self :: AREATAG_LEN : 'prohibited' ,
]
);
2021-06-29 10:43:29 +00:00
2021-08-29 13:58:12 +00:00
$validator -> after ( function ( $validator ) {
2023-09-20 10:29:23 +00:00
if ( $this -> zone -> domain -> flatten ) {
if ( ! $this -> zone -> domain -> zones -> pluck ( 'zone_id' ) -> contains ( $this -> fz ))
$validator -> errors () -> add ( 'invalid-zone' , sprintf ( 'Message zone [%d] doesnt match any zone in domain for packet zone [%d].' , $this -> fz , $this -> zone -> zone_id ));
} else {
if ( $this -> zone -> zone_id !== $this -> fz )
$validator -> errors () -> add ( 'invalid-zone' , sprintf ( 'Message zone [%d] doesnt match packet zone [%d].' , $this -> fz , $this -> zone -> zone_id ));
}
2021-08-29 13:58:12 +00:00
if ( ! $this -> fboss_o )
$validator -> errors () -> add ( 'from' , sprintf ( 'Undefined Node [%s] sent message.' , $this -> fboss ));
if ( ! $this -> tboss_o )
$validator -> errors () -> add ( 'to' , sprintf ( 'Undefined Node [%s] for destination.' , $this -> tboss ));
});
2021-07-15 14:54:23 +00:00
2021-06-29 10:43:29 +00:00
if ( $validator -> fails ())
2024-05-17 12:10:54 +00:00
$this -> mo -> errors = $validator ;
2021-06-29 10:43:29 +00:00
return $validator ;
}
}