From 264747e2f34cc60e1e6c8b2ac7c8795c377f4c43 Mon Sep 17 00:00:00 2001 From: Deon George Date: Fri, 12 Jul 2019 10:42:01 +0700 Subject: [PATCH] Internal rework pending editframe --- app/Classes/Control.php | 27 +- app/Classes/Control/Register.php | 167 +++++----- app/Classes/Frame.php | 149 +++++---- app/Classes/Frame/Action/Login.php | 6 +- app/Classes/Frame/Ansi.php | 17 +- app/Classes/Frame/Videotex.php | 19 +- app/Classes/Parser.php | 26 +- app/Classes/Parser/Ansi.php | 33 +- app/Classes/Parser/Videotex.php | 10 +- app/Classes/Server.php | 481 +++++++++++++++++------------ app/Classes/Server/Ansi.php | 33 +- app/Classes/Server/Videotex.php | 13 +- app/Models/Mode.php | 56 +++- config/database.php | 2 +- 14 files changed, 611 insertions(+), 428 deletions(-) diff --git a/app/Classes/Control.php b/app/Classes/Control.php index db5531a..8bb3ed1 100644 --- a/app/Classes/Control.php +++ b/app/Classes/Control.php @@ -2,18 +2,36 @@ namespace App\Classes; +use App\Classes\Control\EditFrame; use App\Classes\Control\Register; use App\Classes\Control\Telnet; abstract class Control { + // Has this control class finished with input protected $complete = FALSE; + + // The server object that is running this control class protected $so = NULL; + + // The frame applicable for this control (not the current rendered frame, thats in $so) + protected $fo = NULL; + + /** + * What is the state of the server outside of this control. + * Should only contain + * + mode = Mode to follow outside of the control method + * + action = Action to run after leaving the control method + * + * @var array + */ public $state = []; - public function __construct(Server $so) { + public function __construct(Server $so,Frame $fo=NULL) { $this->so = $so; + $this->fo = $fo; + // Boot control, preparing anything before keyboard entry $this->boot(); } @@ -31,8 +49,11 @@ abstract class Control } // @todo Change to Dynamic Calls by the existence of files in App\Classes\Control - public static function factory(string $name, Server $so) { + public static function factory(string $name,Server $so,Frame $fo=NULL) { switch ($name) { + case 'editframe': + return new EditFrame($so,$fo); + case 'register': return new Register($so); @@ -44,5 +65,5 @@ abstract class Control } } - abstract public function handle(string $char); + abstract public function handle(string $read); } \ No newline at end of file diff --git a/app/Classes/Control/Register.php b/app/Classes/Control/Register.php index 74bf8da..6ceb09c 100644 --- a/app/Classes/Control/Register.php +++ b/app/Classes/Control/Register.php @@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Validator; * Class Register handles registration * * @todo REMOVE the force .WHITE at the end of each sendBaseline() - * * @package App\Classes\Control */ class Register extends Control @@ -21,7 +20,7 @@ class Register extends Control protected function boot() { - $this->so->sendBaseline($this->so->client(),GREEN.'Select User Name'.WHITE); + $this->so->sendBaseline($this->so->co,GREEN.'Select User Name'.WHITE); } /** @@ -37,124 +36,114 @@ class Register extends Control */ public function handle(string $read,array $current=[]) { - // Ignore CR - if ($read == CR) - return ''; - // If we got a # we'll be completing field input. if ($read == HASH OR $read == LF) { + // Does our field have data... + if (array_get($current['fielddata'],$current['fieldnum'])) { + switch ($current['fieldnum']) { + // Username + case 0: + // See if the requested username already exists + if (User::where('login', $current['fielddata'][$current['fieldnum']])->exists()) { + $this->so->sendBaseline($this->so->co,RED.'USER ALREADY EXISTS'.WHITE); - // Our registration page - // @todo get this from the DB - if ($current['page']['frame'] == '981') { + return ''; + } - // Does our field have data... - if (array_get($current['fielddata'],$current['fieldnum'])) { - switch ($current['fieldnum']) { - // Username - case 0: - // See if the requested username already exists - if (User::where('login', $current['fielddata'][$current['fieldnum']])->exists()) { - $this->so->sendBaseline($this->so->client(), RED . 'USER ALREADY EXISTS'.WHITE); + $this->data['user'] = $current['fielddata'][$current['fieldnum']]; + $this->so->sendBaseline($this->so->co,GREEN.'Enter Real Name'.WHITE); - return ''; - } + break; - $this->data['user'] = $current['fielddata'][$current['fieldnum']]; - $this->so->sendBaseline($this->so->client(), GREEN . 'Enter Real Name'.WHITE); + // Real Name + case 1: + $this->data['name'] = $current['fielddata'][$current['fieldnum']]; + $this->so->sendBaseline($this->so->co,GREEN.'Enter Email Address'.WHITE); - break; + break; - // Real Name - case 1: - $this->data['name'] = $current['fielddata'][$current['fieldnum']]; - $this->so->sendBaseline($this->so->client(), GREEN . 'Enter Email Address'.WHITE); + // Email Address + case 2: + if (Validator::make(['email'=>$current['fielddata'][$current['fieldnum']]],[ + 'email'=>'email', + ])->fails()) { + $this->so->sendBaseline($this->so->co,RED.'INVALID EMAIL ADDRESS'.WHITE); - break; + return ''; + }; - // Email Address - case 2: - if (Validator::make(['email'=>$current['fielddata'][$current['fieldnum']]],[ - 'email'=>'email', - ])->fails()) { - $this->so->sendBaseline($this->so->client(), RED . 'INVALID EMAIL ADDRESS'.WHITE); + // See if the requested email already exists + if (User::where('email', $current['fielddata'][$current['fieldnum']])->exists()) { + $this->so->sendBaseline($this->so->co,RED.'USER ALREADY EXISTS'.WHITE); - return ''; - }; + return ''; + } - // See if the requested email already exists - if (User::where('email', $current['fielddata'][$current['fieldnum']])->exists()) { - $this->so->sendBaseline($this->so->client(), RED . 'USER ALREADY EXISTS'.WHITE); + $this->data['email'] = $current['fielddata'][$current['fieldnum']]; + $this->data['token'] = sprintf('%06.0f',rand(0,999999)); - return ''; - } + $this->so->sendBaseline($this->so->co,YELLOW.'PROCESSING...'.WHITE); + Mail::to($this->data['email'])->sendNow(new SendToken($this->data['token'])); - $this->data['email'] = $current['fielddata'][$current['fieldnum']]; - $this->data['token'] = sprintf('%06.0f',rand(0,999999)); + if (Mail::failures()) { + dump('Failure?'); - $this->so->sendBaseline($this->so->client(), YELLOW . 'PROCESSING...'.WHITE); - Mail::to($this->data['email'])->sendNow(new SendToken($this->data['token'])); + dump(Mail::failures()); + } - if (Mail::failures()) { - dump('Failure?'); + $this->so->sendBaseline($this->so->co,GREEN.'Enter Password'.WHITE); - dump(Mail::failures()); - } + break; - $this->so->sendBaseline($this->so->client(), GREEN . 'Enter Password'.WHITE); + // Enter Password + case 3: + $this->data['password'] = $current['fielddata'][$current['fieldnum']]; + $this->so->sendBaseline($this->so->co,GREEN.'Confirm Password'.WHITE); - break; + break; - // Enter Password - case 3: - $this->data['password'] = $current['fielddata'][$current['fieldnum']]; - $this->so->sendBaseline($this->so->client(), GREEN . 'Confirm Password'.WHITE); + // Confirm Password + case 4: + if ($this->data['password'] !== $current['fielddata'][$current['fieldnum']]) { + $this->so->sendBaseline($this->so->co,RED.'PASSWORD DOESNT MATCH, *09 TO START AGAIN'.WHITE); - break; + return ''; + } - // Confirm Password - case 4: - if ($this->data['password'] !== $current['fielddata'][$current['fieldnum']]) { - $this->so->sendBaseline($this->so->client(), RED . 'PASSWORD DOESNT MATCH, *09 TO START AGAIN'.WHITE); + $this->so->sendBaseline($this->so->co,GREEN.'Enter Location'.WHITE); - return ''; - } + break; - $this->so->sendBaseline($this->so->client(), GREEN . 'Enter Location'.WHITE); + // Enter Location + case 5: + $this->data['location'] = $current['fielddata'][$current['fieldnum']]; + $this->so->sendBaseline($this->so->co,GREEN.'Enter TOKEN emailed to you'.WHITE); - break; + break; - // Enter Location - case 5: - $this->data['location'] = $current['fielddata'][$current['fieldnum']]; - $this->so->sendBaseline($this->so->client(), GREEN . 'Enter TOKEN emailed to you'.WHITE); + // Enter Token + case 6: + if ($this->data['token'] !== $current['fielddata'][$current['fieldnum']]) { + $this->so->sendBaseline($this->so->co,RED.'TOKEN DOESNT MATCH, *09 TO START AGAIN'.WHITE); - break; + return ''; + } - // Enter Token - case 6: - if ($this->data['token'] !== $current['fielddata'][$current['fieldnum']]) { - $this->so->sendBaseline($this->so->client(), RED . 'TOKEN DOESNT MATCH, *09 TO START AGAIN'.WHITE); + break; - return ''; - } + default: + $this->so->sendBaseline($this->so->co,RED.'HUH?'); + } - break; - - default: - $this->so->sendBaseline($this->so->client(), RED . 'HUH?'); - } + } else { + // If we are MODE_BL, we need to return the HASH, otherwise nothing. + if (in_array($this->state['mode'],[MODE_BL,MODE_SUBMITRF,MODE_RFNOTSENT])) { + return $read; } else { - // If we are MODE_BL, we need to return the HASH, otherwise nothing. - if (in_array($this->state['mode'],[MODE_BL,MODE_SUBMITRF,MODE_RFNOTSENT])) { - return $read; + $this->so->sendBaseline($this->so->co,RED.'FIELD REQUIRED...'.WHITE); - } else { - $this->so->sendBaseline($this->so->client(), RED . 'FIELD REQUIRED...'.WHITE); - - return ''; - } + return ''; } } } @@ -174,14 +163,14 @@ class Register extends Control $o->location = $this->data['location']; $o->save(); - $this->so->sendBaseline($this->so->client(), GREEN . 'ACCOUNT CREATED, PRESS '.HASH.' TO CONTINUE...'.WHITE); + $this->so->sendBaseline($this->so->co,GREEN.'ACCOUNT CREATED, PRESS '.HASH.' TO CONTINUE...'.WHITE); $this->state['action'] = ACTION_NEXT; // Add to CUG 0 $o->cugs()->attach(0); } catch (\Exception $e) { - $this->so->sendBaseline($this->so->client(), RED . 'SOMETHING WENT WRONG...'.WHITE); + $this->so->sendBaseline($this->so->co,RED.'SOMETHING WENT WRONG...'.WHITE); $this->so->log('error',$e->getMessage()); $this->state['action'] = ACTION_RELOAD; } diff --git a/app/Classes/Frame.php b/app/Classes/Frame.php index 19704c0..6d6acae 100644 --- a/app/Classes/Frame.php +++ b/app/Classes/Frame.php @@ -2,11 +2,12 @@ namespace App\Classes; -use App\Models\Mode; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use App\User; -use App\Models\CUG; +use App\Models\{CUG,Mode}; +use App\Models\Frame as FrameModel; /** * Handles all aspects of frame @@ -34,12 +35,14 @@ use App\Models\CUG; */ abstract class Frame { - protected $frame = NULL; - protected $output = ''; - protected $startline = 1; + // This holds the parser object for this frame. + protected $po = NULL; + + // This holds the frame object as retrieved from the DB + protected $fo = NULL; - // All this vars should be overridden in the child class /* + // All this vars should be overridden in the child class protected $frame_length = 22; protected $frame_width = 40; @@ -54,9 +57,8 @@ abstract class Frame const FRAMETYPE_LOGIN = 'l'; const FRAMETYPE_TERMINATE = 't'; - public $fields = NULL; // The fields in this frame. - // Fields that are editable + // @todo This needs rework. private $fieldoptions = [ 'p'=>['edit'=>TRUE,'mask'=>'*'], // Password 't'=>['edit'=>TRUE], // Text @@ -65,38 +67,44 @@ abstract class Frame // @todo Move this to the database private $header = RED.'T'.BLUE.'E'.GREEN.'S'.YELLOW.'T'.MAGENTA.'!'; - public function __construct(\App\Models\Frame $o) + public function __construct(FrameModel $o) { - $this->frame = $o; + $this->fo = $o; + $startline = 1; - $this->output = $this->frame->cls ? CLS : HOME; + if ($this->fo->exists) { + if (! $this->hasFlag('ip') AND (! $this->isCUG(0) OR $this->type() !== self::FRAMETYPE_LOGIN)) { + $startline = 2; - if (! $this->hasFlag('ip') AND (! $this->isCUG(0) OR $this->type() !== self::FRAMETYPE_LOGIN)) { - // Set the page header: CUG/Site Name | Page # | Cost - $this->output .= $this->render_header($this->header). - $this->render_page($this->frame->frame,$this->frame->index). - $this->render_cost($this->frame->cost); - - $this->startline = 2; - - } elseif ($this->isCUG(0) AND $this->type() === self::FRAMETYPE_LOGIN) { - $this->startline = 2; - $this->output .= str_repeat(DOWN,$this->startline-1); + } elseif ($this->isCUG(0) AND $this->type() === self::FRAMETYPE_LOGIN) { + $startline = 2; + } } - // Calculate fields and render output. - $this->fields = collect(); // Fields in this frame. - $this->fields($this->startline); + // Our parser object + $this->po = $this->parser($startline); } /** * Render the frame * * @return null|string + * @throws \Exception */ public function __toString() { - return $this->output; + $output = $this->fo->cls ? CLS : HOME; + + if (! $this->hasFlag('ip') AND (! $this->isCUG(0) OR $this->type() !== self::FRAMETYPE_LOGIN)) { + $output .= $this->render_header($this->header). + $this->render_page($this->fo->frame,$this->fo->index). + $this->render_cost($this->fo->cost); + + } elseif ($this->isCUG(0) AND $this->type() === self::FRAMETYPE_LOGIN) { + $output .= str_repeat(DOWN,1); + } + + return $output.(string)$this->po; } /** @@ -106,9 +114,9 @@ abstract class Frame */ public function alts(Mode $o) { - return \App\Models\Frame::where('frame',$this->frame()) + return FrameModel::where('frame',$this->fo->frame) ->where('index',$this->index()) - ->where('id','<>',$this->frame->id) + ->where('id','<>',$this->fo->id) ->where('mode_id',$o->id) ->where('access',1) ->limit(9); @@ -119,23 +127,33 @@ abstract class Frame */ public function created() { - return $this->frame->created_at; + return $this->fo->created_at; } /** - * Convert the frame from Binary to Output - * Look for fields within the frame. - * - * @param int $startline + * Return fields within the frame. */ - abstract public function fields($startline=0); + public function fields() + { + return $this->po->fields; + } /** * Returns the current frame. */ public function frame() { - return $this->frame->frame; + return $this->fo->frame; + } + + public function frame_length() + { + return static::$frame_length; + } + + public function frame_width() + { + return static::$frame_width; } /** @@ -149,7 +167,7 @@ abstract class Frame public function getCUG() { $co = NULL; - $frame = $this->frame->frame; + $frame = $this->fo->frame; while (! $co) { @@ -171,7 +189,7 @@ abstract class Frame */ public function getField(int $id) { - return $this->fields->get($id); + return $this->fields()->get($id); } /** @@ -183,7 +201,7 @@ abstract class Frame */ public function getFieldId($type='edit',$after=0) { - return $this->fields + return $this->fields() ->search(function($item,$key) use ($type,$after) { return $key >= $after AND $this->isFieldEditable($item->type); }); @@ -204,7 +222,7 @@ abstract class Frame */ public function hasFlag($flag) { - return $this->frame->hasFlag($flag); + return $this->fo->hasFlag($flag); } /** @@ -214,7 +232,7 @@ abstract class Frame */ public function id() { - return $this->frame->id; + return $this->fo->id; } /** @@ -224,7 +242,7 @@ abstract class Frame */ public function index() { - return $this->frame->index; + return $this->fo->index; } /** @@ -232,16 +250,25 @@ abstract class Frame */ public function index_next() { - return chr(ord($this->frame->index)+1); + return chr(ord($this->fo->index)+1); + } + + /** + * Return the previous index + */ + public function index_prev() + { + return $this->fo->index == 'a' ? 'a' : chr(ord($this->fo->index)-1); } public function isAccessible():bool { - return $this->frame->access ? TRUE : FALSE; + return $this->fo->access ? TRUE : FALSE; } /** * Determine if the frame is a particular CUG + * * @param int $cug * @return bool */ @@ -273,10 +300,10 @@ abstract class Frame */ public function isFramePublic(): bool { - return $this->frame->public ? TRUE : FALSE; + return $this->fo->public ? TRUE : FALSE; } - // @todo To implement + // @todo To implement public function isOwner(User $o):bool { return FALSE; @@ -287,7 +314,7 @@ abstract class Frame */ public function page(bool $as_array=FALSE) { - return $as_array ? ['frame'=>$this->frame->frame,'index'=>$this->frame->index] : $this->frame->page; + return $as_array ? ['frame'=>$this->fo->frame,'index'=>$this->fo->index] : $this->fo->page; } /** @@ -296,11 +323,19 @@ abstract class Frame * @param bool $as_array * @return mixed */ - public function pagenext(bool $as_array=FALSE) + public function page_next(bool $as_array=FALSE) { - return $as_array ? ['frame'=>$this->frame->frame,'index'=>$this->index_next()] : $this->frame->frame.$this->index_next(); + return $as_array ? ['frame'=>$this->fo->frame,'index'=>$this->index_next()] : $this->fo->frame.$this->index_next(); } + /** + * Load the parser + * + * @param int $startline + * @return Parser + */ + abstract protected function parser(int $startline): Parser; + /** * Render the cost of the frame * @@ -359,6 +394,8 @@ abstract class Frame * Get the route for the key press * * @param string $read + * @return string + * @throws \Exception */ public function route(string $read) { @@ -366,11 +403,11 @@ abstract class Frame throw new \Exception('Routes are single digit'); // If we dont have a route record... - if (! $this->frame->route) + if (! $this->fo->route) return '*'; $key = 'r'.$read; - return $this->frame->route->{$key}; + return $this->fo->route->{$key}; } /** @@ -381,12 +418,12 @@ abstract class Frame * @param $text * @return int */ - abstract function strlenv($text):int; + abstract public static function strlenv($text):int; - public static function testFrame(Server $so) + public static function testFrame() { // Simulate a DB load - $o = new \App\Models\Frame; + $o = new FrameModel; $content = ''; $o->flags = ['ip']; @@ -399,7 +436,7 @@ abstract class Frame // Header $sid = R_RED.'T'.R_BLUE.'E'.R_GREEN.'S'.R_YELLOW.'T'; - $content .= substr($sid.'-'.str_repeat('12345678901234567890',4),0,static::$header_length+(strlen($sid)-$so->strlenv($sid))). + $content .= substr($sid.'-'.str_repeat('12345678901234567890',4),0,static::$header_length+(strlen($sid)-static::strlenv($sid))). R_WHITE.str_repeat('9',static::$pagenum_length).'a'.R_RED.sprintf('%07.0f',999).'u'; $content .= R_WHITE.str_repeat('+-',static::$frame_width/2-3).' '.R_RED.'01'; @@ -410,7 +447,7 @@ abstract class Frame $o->content = $content; - return $o; + return new static($o); } /** @@ -418,6 +455,6 @@ abstract class Frame */ public function type() { - return $this->frame->type(); + return $this->fo->type(); } } \ No newline at end of file diff --git a/app/Classes/Frame/Action/Login.php b/app/Classes/Frame/Action/Login.php index 0288864..92a137b 100644 --- a/app/Classes/Frame/Action/Login.php +++ b/app/Classes/Frame/Action/Login.php @@ -24,7 +24,7 @@ class Login extends Action $this->mode = 2; // MODE_FIELD // $this->action = 2; // ACTION_GOTO - $this->so->sendBaseline($this->so->client(),RED.'INVALID DETAILS, TRY AGAIN *00'); + $this->so->sendBaseline($this->so->co,RED.'INVALID DETAILS, TRY AGAIN *00'); return FALSE; } @@ -33,7 +33,7 @@ class Login extends Action $this->uo = User::where('login',array_get($fielddata,0))->firstOrFail(); } catch (ModelNotFoundException $e) { - $this->so->sendBaseline($this->so->client(),RED.'USER NOT FOUND, TRY AGAIN *00'); + $this->so->sendBaseline($this->so->co,RED.'USER NOT FOUND, TRY AGAIN *00'); return FALSE; } @@ -41,7 +41,7 @@ class Login extends Action if ($this->uo->password != array_get($fielddata,1)) { $this->uo = new User; - $this->so->sendBaseline($this->so->client(),RED.'INVALID PASSWORD, TRY AGAIN *00'); + $this->so->sendBaseline($this->so->co,RED.'INVALID PASSWORD, TRY AGAIN *00'); return FALSE; } diff --git a/app/Classes/Frame/Ansi.php b/app/Classes/Frame/Ansi.php index 84bfdce..127137c 100644 --- a/app/Classes/Frame/Ansi.php +++ b/app/Classes/Frame/Ansi.php @@ -2,12 +2,14 @@ namespace App\Classes\Frame; +use App\Classes\Parser; use Illuminate\Support\Facades\Log; -use App\Classes\Frame as AbstractFrame; +use App\Classes\Frame; use App\Classes\Parser\Ansi as AnsiParser; +use App\Models\Frame as FrameModel; -class Ansi extends AbstractFrame +class Ansi extends Frame { public static $frame_length = 22; public static $frame_width = 80; @@ -19,7 +21,7 @@ class Ansi extends AbstractFrame public static $if_filler = '.'; - public function __construct(\App\Models\Frame $o,string $msg='') + public function __construct(FrameModel $o,string $msg='') { parent::__construct($o); @@ -28,15 +30,12 @@ class Ansi extends AbstractFrame $this->output .= ESC.'[24;0f'.$msg.HOME; } - public function fields($startline=1) + protected function parser(int $startline): Parser { - $o = new AnsiParser($this->frame->content,$startline); - $this->output .= (string)$o; - - $this->fields = $o->fields; + return new AnsiParser($this->fo->content,self::$frame_width,$startline); } - public function strlenv($text):int { + public static function strlenv($text):int { return strlen($text ? preg_replace('/'.ESC.'\[[0-9;?]+[a-zA-Z]/','',$text) : $text); } } \ No newline at end of file diff --git a/app/Classes/Frame/Videotex.php b/app/Classes/Frame/Videotex.php index 84abba6..9eb8e15 100644 --- a/app/Classes/Frame/Videotex.php +++ b/app/Classes/Frame/Videotex.php @@ -4,10 +4,12 @@ namespace App\Classes\Frame; use Illuminate\Support\Facades\Log; -use App\Classes\Frame as AbstractFrame; +use App\Classes\Frame; +use App\Classes\Parser; use App\Classes\Parser\Videotex as VideotexParser; +use App\Models\Frame as FrameModel; -class Videotex extends AbstractFrame +class Videotex extends Frame { public static $frame_length = 22; public static $frame_width = 40; @@ -19,7 +21,7 @@ class Videotex extends AbstractFrame public static $if_filler = '.'; - public function __construct(\App\Models\Frame $o,string $msg='') + public function __construct(FrameModel $o,string $msg='') { parent::__construct($o); @@ -28,17 +30,12 @@ class Videotex extends AbstractFrame $this->output .= HOME.UP.$msg.HOME; } - // @todo Change to use a Parser, like we do for ANSI - public function fields($startline=1) + protected function parser(int $startline): Parser { - - $o = new VideotexParser($this->frame->content,$startline); - $this->output .= (string)$o; - - $this->fields = $o->fields; + return new VideotexParser($this->fo->content,self::$frame_width,$startline); } - public function strlenv($text):int { + public static function strlenv($text):int { return strlen($text)-substr_count($text,ESC); } } \ No newline at end of file diff --git a/app/Classes/Parser.php b/app/Classes/Parser.php index 4a615ef..edd6191 100644 --- a/app/Classes/Parser.php +++ b/app/Classes/Parser.php @@ -2,11 +2,22 @@ namespace App\Classes; +/** + * The Frame Parser looks into frames for ESC codes that renders dynamic information + */ abstract class Parser { - protected $content = ''; - protected $startline = 0; - public $fields = NULL; + // Fields in the frame + public $fields = []; + + // Parsed frame, ready to send to client + public $output = ''; + + // Position array of frame control chars + protected $frame_data = []; + + // Position array of frame chars + protected $frame_content = []; // Magic Fields that are pre-filled protected $fieldmap = [ @@ -14,17 +25,16 @@ abstract class Parser 'd'=>'%date', ]; - public function __construct(string $content,int $startline=1) + public function __construct(string $content,int $width,int $startline=1) { - $this->content = $content; - $this->startline = $startline; $this->fields = collect(); + $this->output = $this->parse($startline,$content,$width); } public function __toString(): string { - return $this->parse($this->startline); + return $this->output; } - abstract protected function parse($startline): string; + abstract protected function parse(int $startline,string $content,int $width): string; } \ No newline at end of file diff --git a/app/Classes/Parser/Ansi.php b/app/Classes/Parser/Ansi.php index 833707e..5721704 100644 --- a/app/Classes/Parser/Ansi.php +++ b/app/Classes/Parser/Ansi.php @@ -16,11 +16,11 @@ class Ansi extends AbstractParser { * @param int $start * @return bool|int */ - private function findEOF(string $char,int $start) + private function findEOF(string $char,int $start,string $content) { - for ($c=$start;$c <= strlen($this->content);$c++) + for ($c=$start;$c <= strlen($content);$c++) { - if ($this->content{$c} != $char) + if ($content{$c} != $char) return $c-$start; } @@ -32,7 +32,7 @@ class Ansi extends AbstractParser { * @param int $offset * @return string */ - protected function parse($startline): string + protected function parse(int $startline,string $content,int $width): string { // Our starting coordinates $x = 1; @@ -40,10 +40,10 @@ class Ansi extends AbstractParser { $output = ''; // Scan the frame for a field start - for ($c=0; $c<=strlen($this->content); $c++) + for ($c=0; $c<=strlen($content); $c++) { // If the frame is not big enough, fill it with spaces. - $byte = isset($this->content{$c}) ? $this->content{$c} : ' '; + $byte = isset($content{$c}) ? $content{$c} : ' '; $advance = 0; switch ($byte) { @@ -58,7 +58,7 @@ class Ansi extends AbstractParser { case ESC: $advance = 1; // Is the next byte something we know about - $nextbyte = isset($this->content{$c+$advance}) ? $this->content{$c+$advance} : ' '; + $nextbyte = isset($content{$c+$advance}) ? $content{$c+$advance} : ' '; switch ($nextbyte) { case '[': @@ -68,7 +68,7 @@ class Ansi extends AbstractParser { // Find our end CSI param $matches = []; - $a = preg_match('/([0-9]+[;]?)+([a-zA-Z])/',$this->content,$matches,NULL,$c+$advance); + $a = preg_match('/([0-9]+[;]?)+([a-zA-Z])/',$content,$matches,NULL,$c+$advance); if (! $a) break; @@ -76,6 +76,13 @@ class Ansi extends AbstractParser { $advance += strlen($matches[0])-1; $chars .= $matches[0]; + if (! isset($this->frame_data[$y][$x])) + $this->frame_data[$y][$x] = ''; + else + $this->frame_data[$y][$x] .= '|'; + + $this->frame_data[$y][$x] .= $matches[0]; + switch ($matches[2]) { // We ignore 'm' they are color CSIs case 'm': break; @@ -96,10 +103,11 @@ class Ansi extends AbstractParser { break; default: - $c--; // Allow for the original ESC + // Allow for the original ESC + $c--; $advance++; - $fieldtype = ord($nextbyte)%128; // @todo Do we need the %128 for ANSI? - $fieldlength = $this->findEOF(chr($fieldtype),$c+2)+1; + $fieldtype = ord($nextbyte); + $fieldlength = $this->findEOF(chr($fieldtype),$c+2,$content)+1; $byte = ''; @@ -122,6 +130,7 @@ class Ansi extends AbstractParser { $x++; } + $this->frame_content[$y][$x] = $byte; $output .= $byte; if ($advance) { @@ -129,7 +138,7 @@ class Ansi extends AbstractParser { $c += $advance; } - if ($x > 80) { + if ($x > $width) { $x = 1; $y++; } diff --git a/app/Classes/Parser/Videotex.php b/app/Classes/Parser/Videotex.php index 1b7b9b5..ff8739e 100644 --- a/app/Classes/Parser/Videotex.php +++ b/app/Classes/Parser/Videotex.php @@ -10,7 +10,7 @@ use App\Classes\Frame\Videotex as VideotexFrame; class Videotex extends AbstractParser { - protected function parse($startline): string + protected function parse(int $startline,string $content,int $width): string { // Our starting coordinates $output = ''; @@ -32,13 +32,13 @@ class Videotex extends AbstractParser $posn = $y*VideotexFrame::$frame_width+$x; // If the frame is not big enough, fill it with spaces. - $byte = ord(isset($this->content{$posn}) ? $this->content{$posn} : ' ')%128; + $byte = ord(isset($content{$posn}) ? $content{$posn} : ' ')%128; // Check for start-of-field if ($byte == ord(ESC)) { // Esc designates start of field (Esc-K is end of edit) $infield = TRUE; $fieldlength = 1; - $fieldtype = ord(substr($this->content,$posn+1,1))%128; + $fieldtype = ord(substr($content,$posn+1,1))%128; $output .= VideotexFrame::$if_filler; } else { @@ -107,8 +107,8 @@ class Videotex extends AbstractParser } // truncate end of lines @todo havent validated this code or used it? - if (isset($pageflags['tru']) && substr($this->content,$posn,40-$x) === str_repeat(' ',40-$x)) { - $output .= CR . LF; + if (isset($pageflags['tru']) && substr($content,$posn,$width-$x) === str_repeat(' ',$width-$x)) { + $output .= CR.LF; break; } diff --git a/app/Classes/Server.php b/app/Classes/Server.php index 9234fce..575f75c 100644 --- a/app/Classes/Server.php +++ b/app/Classes/Server.php @@ -13,11 +13,23 @@ use App\Models\Frame as FrameModel; use App\Models\Mode; abstract class Server { - private $mo = NULL; // Our Mode object - private $co = NULL; - protected $blp = 0; // Size of Bottom Line Pollution - protected $baseline = ''; // Whats on the baseline currently - protected $pid = NULL; // Client PID + // Our Mode object, this determines if we are an ANSI or VIDEOTEX server + private $mo = NULL; + + // The client connection object of the currently connected client. + public $co = NULL; + + // The currently rendered Frame to the client of Frame/* + public $fo = NULL; + + // Size of Bottom Line Pollution + protected $blp = 0; + + // Whats on the baseline currently + protected $baseline = ''; + + // Client PID + protected $pid = NULL; public function __construct(Mode $o) { @@ -31,6 +43,7 @@ abstract class Server { define('MODE_RFSENT', 6); define('MODE_RFERROR', 7); define('MODE_RFNOTSENT', 8); + define('MODE_CONTROL', 9); // Do nothing, a method is controlling input define('ACTION_RELOAD', 1); define('ACTION_GOTO', 2); @@ -40,16 +53,11 @@ abstract class Server { define('ACTION_TERMINATE', 6); define('ACTION_SUBMITRF', 7); // Offer to submit a response frame define('ACTION_STAR', 8); + define('ACTION_EDIT', 9); // Edit current frame define('CONTROL_TELNET', 1); // Telnet session control define('CONTROL_METHOD', 2); // Send input to an external method - - // Keyboard presses - define('KEY_DELETE', chr(8)); - define('KEY_LEFT', chr(136)); - define('KEY_RIGHT', chr(137)); - define('KEY_DOWN', chr(138)); - define('KEY_UP', chr(139)); + define('CONTROL_EDIT', 3); // Controller to edit frame define('TCP_IAC', chr(255)); define('TCP_DONT', chr(254)); @@ -67,11 +75,12 @@ abstract class Server { define('TCP_OPT_WINDOWSIZE', chr(31)); define('TCP_OPT_LINEMODE', chr(34)); + // Status messages define('MSG_SENDORNOT', GREEN.'KEY 1 TO SEND, 2 NOT TO SEND'); define('MSG_SENT', GREEN.'MESSAGE SENT - KEY '.HASH.' TO CONTINUE'); define('MSG_NOTSENT', GREEN.'MESSAGE NOT SENT - KEY '.HASH.' TO CONTINUE'); - define('ERR_DATABASE', RED.'UNAVAILABLE AT PRESENT - PLSE TRY LATER'); + define('ERR_DATABASE', RED.'UNAVAILABLE AT PRESENT - PLS TRY LATER'); define('ERR_NOTSENT', WHITE.'MESSAGE NOT SENT DUE TO AN ERROR'); define('ERR_PRIVATE', WHITE.'PRIVATE PAGE'.GREEN.'- FOR EXPLANATION *37'.HASH.'..'); define('ERR_ROUTE', WHITE.'MISTAKE?'.GREEN.'TRY AGAIN OR TELL US ON *08'); @@ -84,11 +93,13 @@ abstract class Server { define('MSG_TIMEWARP', WHITE.'OTHER VERSIONS EXIST'.GREEN.'KEY *02 TO VIEW'); } - public function client() - { - return $this->co; - } - + /** + * Write something to the system log. + * + * @param string $mode + * @param string $message + * @param array $data + */ public function log(string $mode,string $message,array $data=[]) { Log::$mode(sprintf('%s: %s',$this->pid,$message),$data); @@ -111,7 +122,9 @@ abstract class Server { elseif ($pid) return; - $fo = NULL; + // The next page we will load - this should have frame=,index= + $next_page = NULL; + $this->co = $client; $this->pid = getmypid(); $this->log('info','Connection from: ',['client'=>$client->getAddress(),'server'=>$this->mo->name]); @@ -136,7 +149,6 @@ abstract class Server { $action = ACTION_GOTO; // Initial action. $control = FALSE; // Logic in control $mode = FALSE; // Current mode. - $save = FALSE; // $cmd = ''; // Current *command being typed in $user = new User; // The logged in user $method = collect(); // Method in control for CONTROL_METHOD @@ -147,19 +159,28 @@ abstract class Server { $current['fieldpos'] = 0; // For current field, position within. $current['prevmode'] = FALSE; // Previous mode - in case we need to go back to MODE_FIELD + $fielddata = []; + // @todo Get the login/start page, and if it is not available, throw the ERR_DATEBASE error. if (isset($config['loginpage'])) { - $page = ['frame'=>$config['loginpage']]; + $next_page = ['frame'=>$config['loginpage']]; } else if (!empty($service['start_page'])) { - $page = ['frame'=>$service['start_page']]; + $next_page = ['frame'=>$service['start_page']]; } else { - $page = ['frame'=>'980']; // next page + $next_page = ['frame'=>'980']; // Default Login Page } while ($action != ACTION_TERMINATE) { // Read a character from the client session + // @todo Add a timeout to read, and log user off if timeout exceeded. $read = $client->read(1); - printf(". Got: %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n",$read,ord($read),$mode,$action,$control); + echo "\n"; + printf(". Got: %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n", + (ord($read) < 32 ? '.' : $read), + ord($read), + serialize($mode), + serialize($action), + $control); // It appears that read will return '' instead of false when a disconnect has occurred. // We'll set it to NULL so its caught later @@ -174,17 +195,15 @@ abstract class Server { $control = CONTROL_TELNET; // Remember our Telnet Session Object - // @todo We might need to clear out the old mode/action states - if (! $session) { + if (! $session) $session = Control::factory('telnet',$this); - } $method->push($session); } } if ($control AND $method->count()) { - printf("= Control going to method: %s\n", get_class($method->last())); + printf("= Start CONTROL: Going to method: %s\n",get_class($method->last())); // Capture our state when we enter this method. if (! array_key_exists('control',$method->last()->state)) { @@ -200,7 +219,7 @@ abstract class Server { $mode = $method->last()->state['mode']; if ($method->last()->complete()) { - printf("- Control complete: %s\n",get_class($method->last())); + printf("- Complete CONTROL: %s\n",get_class($method->last())); $save = $method->pop(); if ($method->count()) { @@ -214,28 +233,34 @@ abstract class Server { dump(sprintf('End: Control is now: %s: Method Count: %s',is_object($control) ? get_class($control) : serialize($control),$method->count())); } + + printf("- End CONTROL: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n", + (ord($read) < 32 ? '.' : $read), + ord($read), + serialize($mode), + serialize($action), + $control); } - printf("- End Control: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n",$read,ord($read),$mode,$action,$control); - + printf("= Start MODE: %s\n",serialize($mode)); switch ($mode) { // Key presses during field input. case MODE_FIELD: $cmd = ''; $action = FALSE; - switch ($fo->type()) { + switch ($this->fo->type()) { // Login frame. case Frame::FRAMETYPE_LOGIN: switch ($read) { case HASH: // If we are the main login screen, see if it is a new user - if ($fo->isCUG(0)) + if ($this->fo->isCUG(0)) { if ($current['field']->type == 't' AND array_get($fielddata,$current['fieldnum']) == 'NEW') { $action = ACTION_GOTO; - $page = ['frame'=>'981']; // @todo This should be in the DB. + $next_page = ['frame'=>'981']; // @todo This should be in the DB. break 2; } @@ -254,13 +279,13 @@ abstract class Server { $current['fieldnum']++; $current['fieldpos'] = 0; - if ($current['fieldnum'] < $fo->fields->count()) { - $current['fieldnum'] = $fo->getFieldId('edit',$current['fieldnum']); + if ($current['fieldnum'] < $this->fo->fields()->count()) { + $current['fieldnum'] = $this->fo->getFieldId('edit',$current['fieldnum']); if ($current['fieldnum'] !== FALSE) { - $current['field'] = $fo->getField($current['fieldnum']); + $current['field'] = $this->fo->getField($current['fieldnum']); - $client->send($this->outputPosition($current['field']->x,$current['field']->y).CON); + $client->send($this->moveCursor($current['field']->x,$current['field']->y).CON); $mode = MODE_FIELD; // There were no (more) editable fields. @@ -288,7 +313,7 @@ abstract class Server { if ($current['fieldpos']) { $current['fieldpos']--; - $client->send(LEFT.$fo::$if_filler.LEFT); + $client->send(LEFT.$this->fo::$if_filler.LEFT); $fielddata[$current['fieldnum']] = substr($fielddata[$current['fieldnum']],0,-1); $current['fielddata'][$current['fieldnum']] = substr($current['fielddata'][$current['fieldnum']],0,-1); } @@ -332,7 +357,7 @@ abstract class Server { $current['fieldpos'] = (($current['fieldpos'] + $current['field']->x) % 40) * 40; } else { - $client->send($this->outputPosition($current['field']->x,$current['field']->y).CON); + $client->send($this->moveCursor($current['field']->x,$current['field']->y).CON); $current['fieldpos'] = 0; } @@ -354,7 +379,7 @@ abstract class Server { $current['fieldpos']++; - $client->send($fo->isFieldMasked($current['field']->type) ?: $read); + $client->send($this->fo->isFieldMasked($current['field']->type) ?: $read); } } @@ -364,7 +389,7 @@ abstract class Server { default: $client->close(); - throw new \Exception('Shouldnt get here', 500); + throw new \Exception('Shouldnt get here',500); } break; @@ -372,29 +397,32 @@ abstract class Server { // Form submission: 1 to send, 2 not to send. case MODE_SUBMITRF: switch ($read) { - // @todo Input received, process it. case '1': - $route = $fo->route(1); - // If we are in a control method, complete it if ($control AND $method->count()) { $method->last()->process(); - } elseif ($route == '*' OR is_numeric($route)) { + } elseif ($this->fo->route(1) == '*' OR is_numeric($this->fo->route(1))) { $this->sendBaseline($client,RED.'NO ACTION PERFORMED'); $mode = MODE_RFSENT; - } elseif ($ao = FrameClass\Action::factory($fo->route(1),$this,$user,$action,$mode)) { + } elseif ($ao = FrameClass\Action::factory($this->fo->route(1),$this,$user,$action,$mode)) { $ao->handle($fielddata); $mode = $ao->mode; $action = $ao->action; - $user = $ao->uo; + + // Is this a user logging in? + if ($ao->uo->exists AND $ao instanceof FrameClass\Action\Login) + { + $user = $ao->uo; + $history = collect(); + } if ($ao->page) - $page = $ao->page; + $next_page = $ao->page; } else { - $this->sendBaseline($client, RED.'NO method exists...'); + $this->sendBaseline($client,RED.'NO method exists...'); $mode = MODE_RFSENT; } @@ -434,18 +462,18 @@ abstract class Server { $client->send(COFF); if ($read == HASH) { - if ($route = $fo->route(2) AND $route !== '*' AND is_numeric($route)) { - $page = ['frame'=>$route]; + if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) { + $next_page = ['frame'=>$x]; - } elseif (FrameModel::where('frame',$fo->frame())->where('index',$fo->index_next())->exists()) { - $page = ['frame'=>$fo->frame(),'index'=>$fo->index_next()]; + } elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) { + $next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()]; - } elseif ($route = $fo->route(0) AND $route !== '*' AND is_numeric($route)) { - $page = ['frame'=>$route]; + } elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) { + $next_page = ['frame'=>$x]; // No further routes defined, go home. } else { - $page = ['frame'=>0]; + $next_page = ['frame'=>0]; } $action = ACTION_GOTO; @@ -461,23 +489,23 @@ abstract class Server { // Response form after NOT sending case MODE_RFNOTSENT: - // Response form ERROR + // Response form ERROR case MODE_RFERROR: $client->send(COFF); if ($read == HASH) { - if ($route = $fo->route(2) AND $route !== '*' AND is_numeric($route)) { - $page = ['frame'=>$route]; + if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) { + $next_page = ['frame'=>$x]; - } elseif (FrameModel::where('frame',$fo->frame())->where('index',$fo->index_next())->exists()) { - $page = ['frame'=>$fo->frame(),'index'=>$fo->index_next()]; + } elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) { + $next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()]; - } elseif ($route = $fo->route(0) AND $route !== '*' AND is_numeric($route)) { - $page = ['frame'=>$route]; + } elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) { + $next_page = ['frame'=>$x]; // No further routes defined, go home. } else { - $page = ['frame'=>0]; + $next_page = ['frame'=>0]; } $action = ACTION_GOTO; @@ -528,8 +556,8 @@ abstract class Server { case '7': case '8': case '9': - if (is_numeric($fo->route($read))) { - $page = ['frame'=>$fo->route($read),'index'=>'a']; + if (is_numeric($this->fo->route($read))) { + $next_page = ['frame'=>$this->fo->route($read)]; $action = ACTION_GOTO; @@ -548,10 +576,18 @@ abstract class Server { echo "- Waiting for Page Number\n"; // if it's a number, continue entry - if (strpos('0123456789', $read) !== FALSE) { - $cmd .= $read; + if (strpos('0123456789',$read) !== FALSE) { $client->send($read); $this->blp++; + $cmd .= $read; + } + + // If its a backspace, delete last input + if ($read === KEY_DELETE AND strlen($cmd)) + { + $client->send(BS.' '.BS); + $this->blp--; + $cmd = substr($cmd,0,-1); } // if we hit a special numeric command, deal with it. @@ -569,7 +605,7 @@ abstract class Server { if ($cmd === '01') { $client->send(COFF); $timewarp = !$timewarp; - $this->sendBaseline($client, ($timewarp ? MSG_TIMEWARP_ON : MSG_TIMEWARP_OFF)); + $this->sendBaseline($client,($timewarp ? MSG_TIMEWARP_ON : MSG_TIMEWARP_OFF)); $cmd = ''; $action = $mode = FALSE; @@ -586,9 +622,22 @@ abstract class Server { break; } + // Edit frame + // Catch if we are going to edit a child frame + if (preg_match('/^04/',$cmd) AND preg_match('/^[a-z]$/',$read)) + { + $client->send(COFF); + $next_page = ['frame'=>substr($cmd,2),'index'=>$read]; + $cmd = ''; + + $action = ACTION_EDIT; + + break; + } + // Bookmark page if ($cmd === '05') { - $this->sendBaseline($client, RED.'NOT IMPLEMENTED YET?'); + $this->sendBaseline($client,RED.'NOT IMPLEMENTED YET?'); $mode = FALSE; break; @@ -596,7 +645,7 @@ abstract class Server { // Report a problem if ($cmd === '08') { - $this->sendBaseline($client, RED.'NOT IMPLEMENTED YET?'); + $this->sendBaseline($client,RED.'NOT IMPLEMENTED YET?'); $mode = FALSE; break; @@ -607,6 +656,7 @@ abstract class Server { $client->send(COFF); $action = ACTION_GOTO; $cmd = ''; + $next_page = $this->fo->page(TRUE); break; } @@ -622,8 +672,8 @@ abstract class Server { $current['prevmode'] = FALSE; // @todo The cursor color could be wrong - $client->send($this->outputPosition($current['field']->x,$current['field']->y).CON); - $client->send(str_repeat($fo::$if_filler, $current['field']->length)); + $client->send($this->moveCursor($current['field']->x,$current['field']->y).CON); + $client->send(str_repeat($this->fo::$if_filler,$current['field']->length)); $current['fieldreset'] = TRUE; } else { @@ -638,18 +688,36 @@ abstract class Server { $client->send(COFF); $timewarpalt = FALSE; + // If input is in a control, terminate it + if ($control) + { + $method->pop(); + $control = FALSE; + + // Our method count should be zero + if ($method->count()) + dd($method); + } + // Nothing typed between * and # // *# means go back if ($cmd === '') { $action = ACTION_BACKUP; } elseif ($cmd === '0') { - $page = $user->exists ? ['frame'=>1,'index'=>'a'] : ['frame'=>980,'index'=>'a']; // @todo Get from DB. + $next_page = ['frame'=>$user->exists ? 1 : 980]; // @todo Get from DB. $action = ACTION_GOTO; + // Edit Frame + } elseif (preg_match('/^04/',$cmd)) { + $client->send(COFF); + $action = ACTION_EDIT; + $next_page = [ + 'frame' => substr($cmd,2) ?: $this->fo->frame(), + ]; + } else { - $page['frame'] = $cmd; - $page['index'] = 'a'; + $next_page = ['frame'=>$cmd]; $action = ACTION_GOTO; } @@ -661,17 +729,24 @@ abstract class Server { break; + // Control is taking input + case MODE_CONTROL: + break; + default: $this->log('debug','Not sure what we were doing?'); } - } - printf("- End Mode: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n",$read,ord($read),$mode,$action,$control); + printf("- End MODE: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n", + (ord($read) < 32 ? '.' : $read), + ord($read), + serialize($mode), + serialize($action), + $control); + } // This section performs some action if it is deemed necessary - if ($action) { - printf("+ Performing action: %s\n",$action); - } + printf("= Start ACTION: %s\n",serialize($action)); switch ($action) { case ACTION_STAR: @@ -679,7 +754,10 @@ abstract class Server { // If there is something on the baseline, lets preserve it if ($this->blp) + { + printf(". Preserving Baseline: %s\n",$this->baseline); $current['baseline'] = $this->baseline; + } $this->sendBaseline($client,GREEN.STAR,TRUE); $client->send(CON); @@ -696,6 +774,30 @@ abstract class Server { break; + // Edit Frame + case ACTION_EDIT: + $this->log('debug','Editing frame:',[$next_page]); + + $next_fo = NULL; + // If we are editing a different frame, load it + try { + $next_fo = $this->mo->framePage($next_page['frame'],array_get($next_page,'index','a'),$this); + + } catch (ModelNotFoundException $e) { + $next_fo = $this->mo->frameNew($this,$next_page['frame'],$next_page['index']); + } + + $control = CONTROL_EDIT; + $method->push(Control::factory('editframe',$this,$next_fo)); + $next_fo = NULL; + $method->last()->state['control'] = $control; + $method->last()->state['action'] = FALSE; + $method->last()->state['mode'] = MODE_FIELD; + $mode = MODE_CONTROL; + $action = FALSE; + + break; + // GO Backwards case ACTION_BACKUP: // Do we have anywhere to go, drop the current page from the history. @@ -716,116 +818,126 @@ abstract class Server { } } - $page = $history->last(); + $next_page = $history->last(); - $this->log('debug','Backing up to:',$page); + // If there is no next page, we'll refresh the current page. + if ($next_page) + $this->log('debug','Backing up to:',$next_page); // Go to next index frame. case ACTION_NEXT: // We need this extra test in case we come from ACTION_BACKUP if ($action == ACTION_NEXT) { - $current['page']['index'] = $fo->index(); - $page['index'] = $fo->index_next(); + $current['page']['index'] = $this->fo->index(); + $next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()]; } - // Look for requested page + // Look for requested page - charge for it to be loaded. case ACTION_GOTO: - $current['frame'] = $fo; - - // If we wanted a "Searching..." message, this is where to put it. - try { - $fo = $timewarpalt - ? $this->mo->frame(FrameModel::findOrFail($timewarpalt)) - : $this->mo->frameLoad($page['frame'],array_get($page,'index','a'),$this); - - $this->log('debug',sprintf('Fetched frame: %s (%s)',$fo->id(),$fo->page())); - - } catch (ModelNotFoundException $e) { - $this->sendBaseline($client,ERR_PAGE); - $mode = $action = FALSE; - - $fo = $current['frame'] ?: $this->mo->frame($this->testFrame()); - - break; - } - - // Is there a user logged in - if ($user) + if ($next_page OR $timewarpalt) { - if ($fo->isFramePublic() AND $fo->isAccessible()) - { - if ($fo->type() == Frame::FRAMETYPE_LOGIN AND $user->isMemberCUG($fo->getCUG())) - { - $this->sendBaseline($client,ERR_USER_ALREADYMEMBER); - $fo = $current['frame']; - $page = $history->last(); - $mode = $action = FALSE; - $this->log('debug',sprintf('Frame Denied - Already Member: %s (%s)',$fo->id(),$fo->page())); + // If we wanted a "Searching..." message, this is where to put it. + try { + // Store our next frame in a temporary var while we determine if it can be displayed + $fo = $timewarpalt + ? $this->mo->frameID($timewarpalt) + : $this->mo->framePage($next_page['frame'],array_get($next_page,'index','a'),$this); - break; - } + $this->log('debug',sprintf('Fetched frame: %s (%s)',$fo->id(),$fo->page())); - // If this is a login frame and the user is already a member. - } else { - - if (! $fo->isOwner($user)) - { - if (! $fo->isAccessible()) - { - $this->sendBaseline($client,ERR_PAGE); - $fo = $current['frame']; - $page = $history->last(); - $mode = $action = FALSE; - $this->log('debug',sprintf('Frame Denied - In Accessible: %s (%s)',$fo->id(),$fo->page())); - - break; - } - - if (! $user->isMemberCUG($fo->getCUG())) - { - $this->sendBaseline($client, ERR_PRIVATE); - $fo = $current['frame']; - $page = $history->last(); - $mode = $action = FALSE; - $this->log('debug',sprintf('Frame Denied - Not in CUG [%s]: %s (%s)',$fo->getCUG()->id,$fo->id(),$fo->page())); - - break; - } - } - } - - } else { - // Is this a public frame in CUG 0? - if (! $fo->isCUG(0) OR ! $fo->isFramePublic()) - { + } catch (ModelNotFoundException $e) { + // @todo Make sure parent frame exists, or display error $this->sendBaseline($client,ERR_PAGE); $mode = $action = FALSE; + if (! $fo) + $fo = $this->mo->frameTest($this); + break; } + + // Is there a user logged in + if ($user) + { + if ($fo->isFramePublic() AND $fo->isAccessible()) + { + if ($fo->type() == Frame::FRAMETYPE_LOGIN AND $user->isMemberCUG($fo->getCUG())) + { + $this->sendBaseline($client,ERR_USER_ALREADYMEMBER); + $next_page = $history->last(); + $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - Already Member: %s (%s)',$fo->id(),$fo->page())); + + break; + } + + + // If this is a login frame and the user is already a member. + } else { + if (! $fo->isOwner($user)) + { + if (! $fo->isAccessible()) + { + $this->sendBaseline($client,ERR_PAGE); + $next_page = $history->last(); + $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - In Accessible: %s (%s)',$fo->id(),$fo->page())); + + break; + } + + if (! $user->isMemberCUG($fo->getCUG())) + { + $this->sendBaseline($client,ERR_PRIVATE); + $next_page = $history->last(); + $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - Not in CUG [%s]: %s (%s)',$fo->getCUG()->id,$fo->id(),$fo->page())); + + break; + } + } + } + + } else { + // Is this a public frame in CUG 0? + if (! $fo->isCUG(0) OR ! $fo->isFramePublic()) + { + $this->sendBaseline($client,ERR_PAGE); + $next_page = $history->last(); + $mode = $action = FALSE; + + break; + } + } } - $current['page'] = $fo->page(TRUE); - $current['fieldpos'] = 0; - // Only if new location, not going backwards - if (($history->last() != $page) AND ($action == ACTION_GOTO || $action == ACTION_NEXT)) { - $history->push($page); + if (($history->last() != $next_page) AND ($action == ACTION_GOTO || $action == ACTION_NEXT)) { + $history->push($next_page); } + $current['fieldpos'] = 0; + $this->fo = $fo; + $fo = NULL; + $next_page = NULL; + $timewarpalt = NULL; + printf("+ Mode is: %s\n",$mode); // drop into case ACTION_RELOAD: + // Clear the baseline history $this->sendBaseline($client,''); - $output = (string)$fo; + $current['baseline'] = ''; + + $output = (string)$this->fo; if ($timewarpalt) { - $this->sendBaseline($client,sprintf(MSG_TIMEWARP_TO,$fo->created() ? $fo->created()->format('Y-m-d H:i:s') : 'UNKNOWN')); + $this->sendBaseline($client,sprintf(MSG_TIMEWARP_TO,$this->fo->created() ? $this->fo->created()->format('Y-m-d H:i:s') : 'UNKNOWN')); } - switch ($fo->type()) { + switch ($this->fo->type()) { default: // Standard Frame case Frame::FRAMETYPE_INFO: @@ -842,7 +954,7 @@ abstract class Server { // If this is the registration page // @todo Should be evaluated out of the DB - if ($fo->page() == '981a') { + if ($this->fo->page() == '981a') { $control = CONTROL_METHOD; $method->push(Control::factory('register',$this)); $method->last()->state['control'] = $control; @@ -857,10 +969,10 @@ abstract class Server { $fielddata = []; $current['fielddata'] = []; - if (count($fo->fields)) { + if ($this->fo->fields()->count()) { // Get our first editable field. - $current['fieldnum'] = $fo->getFieldId('edit',0); - $current['field'] = $fo->getField($current['fieldnum']); + $current['fieldnum'] = $this->fo->getFieldId('edit',0); + $current['field'] = $this->fo->getField($current['fieldnum']); $current['fieldreset'] = TRUE; if ($current['fieldnum'] !== FALSE) { @@ -896,17 +1008,17 @@ abstract class Server { $cmd = ''; $y = 0; - $output = $this->outputPosition(0, $y++) . WHITE . NEWBG . RED . 'TIMEWARP INFO FOR Pg.' . BLUE . $fo->page(). WHITE; - //$output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . BLUE . 'Service : ' . substr($service['service_name'] . str_repeat(' ', 27), 0, 27); - //$output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . BLUE . 'Varient : ' . substr($varient['varient_name'] . str_repeat(' ', 27), 0, 27); - $output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . BLUE . 'Dated : ' .substr(($fo->created() ? $fo->created()->format('j F Y') : 'Unknown').str_repeat(' ', 27), 0, 27); + $output = $this->moveCursor(0,$y++).WHITE.NEWBG.RED.'TIMEWARP INFO FOR Pg.'.BLUE.$this->fo->page().WHITE; + //$output .= $this->moveCursor(0,$y++).WHITE.NEWBG.BLUE.'Service : '.substr($service['service_name'].str_repeat(' ',27),0,27); + //$output .= $this->moveCursor(0,$y++).WHITE.NEWBG.BLUE.'Varient : '.substr($varient['varient_name'].str_repeat(' ',27),0,27); + $output .= $this->moveCursor(0,$y++).WHITE.NEWBG.BLUE.'Dated : ' .substr(($this->fo->created() ? $this->fo->created()->format('j F Y') : 'Unknown').str_repeat(' ',27),0,27); - $alts = $fo->alts($this->mo)->get(); + $alts = $this->fo->alts($this->mo)->get(); if (count($alts)) { $n = 1; - $output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . RED . 'ALTERNATIVE VERSIONS:' . str_repeat(' ', 16); + $output .= $this->moveCursor(0,$y++).WHITE.NEWBG.RED.'ALTERNATIVE VERSIONS:'.str_repeat(' ',16); foreach ($alts as $o) { $date = $o->created_at->format('d M Y'); @@ -919,7 +1031,7 @@ abstract class Server { $line .= BLUE.$date.' '.$o->note; - $output .= $this->outputPosition(0,$y++).$line.str_repeat(' ',40-$this->strlenv($line)); // @todo should use frame::page_length + $output .= $this->moveCursor(0,$y++).$line.str_repeat(' ',40-$this->fo->strlenv($line)); // @todo should use frame::page_length } if ($timewarp) { @@ -933,11 +1045,16 @@ abstract class Server { break; } - printf("- End Action: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n",$read,ord($read),$mode,$action,$control); + printf("- End ACTION: Read %s (%s): Mode: [%s], Action: [%s], Control: [%s]\n", + (ord($read) < 32 ? '.' : $read), + ord($read), + serialize($mode), + serialize($action), + $control); // We need to reposition the cursor to the current field. if ($current['fieldreset'] !== FALSE) { - $client->send($this->outputPosition($current['field']->x,$current['field']->y).CON); + $client->send($this->moveCursor($current['field']->x,$current['field']->y).CON); $current['fieldreset'] = FALSE; } @@ -962,7 +1079,7 @@ abstract class Server { /** * Move the cursor via the shortest path. */ - abstract function outputPosition($x,$y); + abstract function moveCursor($x,$y); /** * Send a message to the base line @@ -973,20 +1090,4 @@ abstract class Server { * @param bool $reposition */ abstract function sendBaseline($client,$text,$reposition=FALSE); - - /** - * Calculate the length of text - * - * ESC characters are two chars, and need to be counted as one. - * - * @param $text - * @return int - */ - abstract function strlenv($text):int; - - /** - * Return a test frame appropriate for this server. - * @return mixed - */ - abstract public function testFrame(); } \ No newline at end of file diff --git a/app/Classes/Server/Ansi.php b/app/Classes/Server/Ansi.php index 1e02394..da84bb3 100644 --- a/app/Classes/Server/Ansi.php +++ b/app/Classes/Server/Ansi.php @@ -2,8 +2,6 @@ namespace App\Classes\Server; -use Illuminate\Support\Facades\Log; - use App\Classes\Server as AbstractServer; use App\Models\Mode; @@ -22,6 +20,7 @@ class Ansi extends AbstractServer { define('UP', ESC.'[A'); // Move Cursor define('CR', chr(13)); define('LF', chr(10)); + define('BS', chr(8)); define('CLS', ESC.'[2J'); define('HASH', '#'); // Enter define('STAR', '*'); // Star Entry @@ -47,34 +46,32 @@ class Ansi extends AbstractServer { define('R_WHITE', WHITE); //define('FLASH',chr(8)); + // Keyboard presses + // @todo Check where these are used vs the keys defined above? + define('KEY_DELETE', chr(8)); + define('KEY_LEFT', chr(136)); + define('KEY_RIGHT', chr(137)); + define('KEY_DOWN', chr(138)); + define('KEY_UP', chr(139)); + parent::__construct($o); } - function outputPosition($x,$y) { + function moveCursor($x,$y) { + printf(". Move cursor %s,%s\n",$x,$y); return ESC.'['.$y.';'.$x.'f'; } // Abstract function public function sendBaseline($client,$text,$reposition=FALSE) { $client->send(CSAVE.ESC.'[24;0f'.$text. - ($this->blp > $this->strlenv($text) - ? str_repeat(' ',$this->blp-$this->strlenv($text)). - ($reposition ? ESC.'[24;0f'.str_repeat(RIGHT,$this->strlenv($text)) : CRESTORE) + ($this->blp > $this->fo->strlenv($text) + ? str_repeat(' ',$this->blp-$this->fo->strlenv($text)). + ($reposition ? ESC.'[24;0f'.str_repeat(RIGHT,$this->fo->strlenv($text)) : CRESTORE) : ($reposition ? '' : CRESTORE)) ); - $this->blp = $this->strlenv($text); + $this->blp = $this->fo->strlenv($text); $this->baseline = $text; } - - // Abstract function - public function strlenv($text):int { - return strlen($text ? preg_replace('/'.ESC.'\[[0-9;?]+[a-zA-Z]/','',$text) : $text); - } - - // Abstract function - public function testFrame() - { - return \App\Classes\Frame\Ansi::testFrame($this); - } } \ No newline at end of file diff --git a/app/Classes/Server/Videotex.php b/app/Classes/Server/Videotex.php index 7c5f97d..2069480 100644 --- a/app/Classes/Server/Videotex.php +++ b/app/Classes/Server/Videotex.php @@ -48,7 +48,7 @@ class Videotex extends AbstractServer { parent::__construct($o); } - public function outputPosition($x,$y) { + public function moveCursor($x,$y) { // Take the shortest path. if ($y < 12) { return HOME. @@ -74,15 +74,4 @@ class Videotex extends AbstractServer { $this->blp = $this->strlenv($text); } - - // Abstract function - public function strlenv($text):int { - return strlen($text)-substr_count($text,ESC); - } - - // Abstract function - public function testFrame() - { - return \App\Classes\Frame\Videotex::testFrame($this); - } } \ No newline at end of file diff --git a/app/Models/Mode.php b/app/Models/Mode.php index 0fdc7ca..6718884 100644 --- a/app/Models/Mode.php +++ b/app/Models/Mode.php @@ -20,6 +20,11 @@ class Mode extends Model return $this->hasMany(Frame::class); } + public function frameId(int $id) + { + return $this->frameLoad(Frame::findOrFail($id)); + } + /** * Return a frame class for the Model * @@ -27,7 +32,7 @@ class Mode extends Model * @return FrameClass * @throws \Exception */ - public function frame(Model $o): FrameClass + private function frameLoad(Model $o): FrameClass { switch (strtolower($this->name)) { case 'ansi': @@ -35,10 +40,28 @@ class Mode extends Model case 'videotex': return new VideotexFrame($o); default: - throw new \Exception('Unknown Frame type: '.$mo->name); + throw new \Exception('Unknown Frame type: '.$this->name); } } + public function frameNew(Server $so,int $frame,string $index='a'): FrameClass + { + $o = new Frame; + $o->frame = $frame; + $o->index = $index; + $o->mode_id = $this->id; + + // Make sure parent frame exists + // @todo make sure not trying to edit test frames + if ($o->index != 'a' AND ! FrameModel::where('frame',$fo->frame())->where('index',$fo->index_prev())->exists()) + { + $so->sendBaseline($so->co,ERR_ROUTE); + return new Frame; + } + + return $this->frameLoad($o); + } + /** * Fetch a specific frame from the DB * @@ -48,17 +71,28 @@ class Mode extends Model * @return FrameClass * @throws \Exception */ - public function frameLoad(int $frame,string $index,Server $so): FrameClass + public function framePage(int $frame,string $index='a',Server $so): FrameClass { - return $this->frame( + return ($frame == '999' and $index == 'a') // Return our internal test frame. - ($frame == '999' and $index == 'a') - ? $so->testFrame() - : $this->frames() - ->where('frame','=',$frame) - ->where('index','=',$index) - ->firstOrFail() - ); + ? $this->frameTest($so) + : $this->frameLoad($this->frames() + ->where('frame','=',$frame) + ->where('index','=',$index) + ->firstOrFail() + ); + } + + public function frameTest(Server $so): FrameClass + { + switch (strtolower($this->name)) { + case 'ansi': + return AnsiFrame::testFrame($so); + case 'videotex': + return VideotexFrame::testFrame($so); + default: + throw new \Exception('Unknown Frame type: '.$this->name); + } } /** diff --git a/config/database.php b/config/database.php index 5c23ae0..2322c75 100644 --- a/config/database.php +++ b/config/database.php @@ -98,7 +98,7 @@ return [ // otherwise you can them out of the configuration array #'sslcert' => env('DB_SSLCERT', 'client.crt'), #'sslkey' => env('DB_SSLKEY', 'client.key'), - #'sslrootcert' => env('DB_SSLROOTCERT', 'ca.crt'), + 'sslrootcert' => env('DB_SSLROOTCERT', 'ca.crt'), ], ],