mirror of https://github.com/YGGverse/Yoda.git
yggverse
5 months ago
8 changed files with 652 additions and 350 deletions
@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Abstract\Model; |
||||
|
||||
abstract class Connection implements \Yggverse\Yoda\Interface\Model\Connection |
||||
{ |
||||
// Status |
||||
protected bool $_completed = false; |
||||
|
||||
// Response |
||||
protected ?string $_title = null; |
||||
protected ?string $_subtitle = null; |
||||
protected ?string $_tooltip = null; |
||||
protected ?string $_mime = null; |
||||
protected ?string $_data = null; |
||||
protected ?string $_redirect = null; |
||||
protected ?array $_request = null; |
||||
|
||||
public function isCompleted(): bool |
||||
{ |
||||
return $this->_completed; |
||||
} |
||||
|
||||
public function setCompleted( |
||||
bool $completed |
||||
): void |
||||
{ |
||||
$this->_completed = $completed; |
||||
} |
||||
|
||||
public function getTitle(): ?string |
||||
{ |
||||
return $this->_title; |
||||
} |
||||
|
||||
public function setTitle( |
||||
?string $title = null |
||||
): void |
||||
{ |
||||
$this->_title = $title; |
||||
} |
||||
|
||||
public function getSubtitle(): ?string |
||||
{ |
||||
return $this->_subtitle; |
||||
} |
||||
|
||||
public function setSubtitle( |
||||
?string $subtitle = null |
||||
): void |
||||
{ |
||||
$this->_subtitle = $subtitle; |
||||
} |
||||
|
||||
public function getTooltip(): ?string |
||||
{ |
||||
return $this->_tooltip; |
||||
} |
||||
|
||||
public function setTooltip( |
||||
?string $tooltip = null |
||||
): void |
||||
{ |
||||
$this->_tooltip = $tooltip; |
||||
} |
||||
|
||||
public function getMime(): ?string |
||||
{ |
||||
return $this->_mime; |
||||
} |
||||
|
||||
public function setMime( |
||||
?string $mime = null |
||||
): void |
||||
{ |
||||
$this->_mime = $mime; |
||||
} |
||||
|
||||
public function getData(): ?string |
||||
{ |
||||
return $this->_data; |
||||
} |
||||
|
||||
public function setData( |
||||
?string $data = null |
||||
): void |
||||
{ |
||||
$this->_data = $data; |
||||
} |
||||
|
||||
public function getRedirect(): ?string |
||||
{ |
||||
return $this->_redirect; |
||||
} |
||||
|
||||
public function setRedirect( |
||||
?string $redirect = null |
||||
): void |
||||
{ |
||||
$this->_redirect = $redirect; |
||||
} |
||||
|
||||
public function getRequest(): ?array |
||||
{ |
||||
return $this->_request; |
||||
} |
||||
|
||||
public function setRequest( |
||||
?array $request = null |
||||
): void |
||||
{ |
||||
$this->_request = $request; // @TODO |
||||
} |
||||
|
||||
public function getLength(): ?int |
||||
{ |
||||
return mb_strlen( |
||||
$this->_data |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Interface\Model; |
||||
|
||||
/* |
||||
* Single API for multiple protocols |
||||
* |
||||
*/ |
||||
interface Connection |
||||
{ |
||||
public function request( |
||||
string $request, |
||||
int $timeout = 5 |
||||
): void; |
||||
|
||||
public const MIME_TEXT_GEMINI = 'text/gemini'; |
||||
public const MIME_TEXT_PLAIN = 'text/plain'; |
||||
|
||||
public function isCompleted(): bool; |
||||
|
||||
public function setCompleted( |
||||
bool $completed |
||||
): void; |
||||
|
||||
public function getTitle(): ?string; |
||||
|
||||
public function setTitle( |
||||
?string $title = null |
||||
): void; |
||||
|
||||
public function getSubtitle(): ?string; |
||||
|
||||
public function setSubtitle( |
||||
?string $subtitle = null |
||||
): void; |
||||
|
||||
public function getTooltip(): ?string; |
||||
|
||||
public function setTooltip( |
||||
?string $tooltip = null |
||||
): void; |
||||
|
||||
public function getMime(): ?string; |
||||
|
||||
public function setMime( |
||||
?string $mime = null |
||||
): void; |
||||
|
||||
public function getData(): ?string; |
||||
|
||||
public function setData( |
||||
?string $data = null |
||||
): void; |
||||
|
||||
public function getRedirect(): ?string; |
||||
|
||||
public function setRedirect( |
||||
?string $redirect = null |
||||
): void; |
||||
|
||||
public function getRequest(): ?array; |
||||
|
||||
public function setRequest( |
||||
?array $request = null |
||||
): void; |
||||
|
||||
public function getLength(): ?int; |
||||
} |
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Model; |
||||
|
||||
use \Yggverse\Net\Address; |
||||
|
||||
use \Yggverse\Yoda\Model\Connection\File; |
||||
use \Yggverse\Yoda\Model\Connection\Gemini; |
||||
use \Yggverse\Yoda\Model\Connection\Nex; |
||||
|
||||
class Connection extends \Yggverse\Yoda\Abstract\Model\Connection |
||||
{ |
||||
public function request( |
||||
string $request, |
||||
int $timeout = 5 |
||||
): void |
||||
{ |
||||
// Build address instance |
||||
$address = new Address( |
||||
$request |
||||
); |
||||
|
||||
// Detect protocol |
||||
switch ($address->getScheme()) |
||||
{ |
||||
case 'file': |
||||
|
||||
(new File($this))->sync( |
||||
$address |
||||
); |
||||
|
||||
break; |
||||
|
||||
case 'gemini': |
||||
|
||||
(new Gemini($this))->sync( |
||||
$address, |
||||
$timeout |
||||
); |
||||
|
||||
break; |
||||
|
||||
case 'nex': |
||||
|
||||
(new Nex($this))->sync( |
||||
$address, |
||||
$timeout |
||||
); |
||||
|
||||
break; |
||||
|
||||
case null: // no scheme provided |
||||
|
||||
// Use gemini protocol by default |
||||
$redirect = new Address( |
||||
sprintf( |
||||
'gemini://%s', |
||||
$address->get() |
||||
) |
||||
); |
||||
|
||||
// Hostname valid |
||||
if (filter_var( |
||||
$redirect->getHost(), |
||||
FILTER_VALIDATE_DOMAIN, |
||||
FILTER_FLAG_HOSTNAME |
||||
) |
||||
) { |
||||
// Redirect |
||||
$this->setRedirect( |
||||
$redirect->get() |
||||
); |
||||
} |
||||
|
||||
// Redirect to default search provider |
||||
else |
||||
{ |
||||
// @TODO custom providers |
||||
$this->setRedirect( |
||||
sprintf( |
||||
'gemini://tlgs.one/search?%s', |
||||
urlencode( |
||||
$request |
||||
) |
||||
) |
||||
); |
||||
} |
||||
|
||||
return; |
||||
|
||||
default: |
||||
|
||||
throw new \Exception( |
||||
_('Protocol not supported') |
||||
); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Model\Connection; |
||||
|
||||
use \Yggverse\Net\Address; |
||||
|
||||
use \Yggverse\Yoda\Model\Connection; |
||||
use \Yggverse\Yoda\Model\Filesystem; |
||||
|
||||
class File |
||||
{ |
||||
private Connection $_connection; |
||||
|
||||
public function __construct( |
||||
Connection $connection |
||||
) { |
||||
$this->_connection = $connection; |
||||
} |
||||
|
||||
public function sync( |
||||
Address $address |
||||
): void |
||||
{ |
||||
$this->_connection->setTitle( |
||||
basename( |
||||
$address->getPath() |
||||
) |
||||
); |
||||
|
||||
$this->_connection->setSubtitle( |
||||
$address->getPath() |
||||
); |
||||
|
||||
$this->_connection->setTooltip( |
||||
$address->getPath() |
||||
); |
||||
|
||||
switch (true) |
||||
{ |
||||
case ( // is directory |
||||
$list = Filesystem::getList( |
||||
$address->getPath() |
||||
) |
||||
): |
||||
$tree = []; |
||||
|
||||
foreach ($list as $item) |
||||
{ |
||||
$tree[] = trim( |
||||
sprintf( |
||||
'=> file://%s %s', |
||||
$item['path'], |
||||
$item['name'] . ( |
||||
// append slash indicator |
||||
$item['file'] ? null : '/' |
||||
) |
||||
) |
||||
); |
||||
} |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
implode( |
||||
PHP_EOL, |
||||
$tree |
||||
) . PHP_EOL |
||||
); |
||||
|
||||
break; |
||||
|
||||
case file_exists( // is file |
||||
$address->getPath() |
||||
) && is_readable( |
||||
$address->getPath() |
||||
): |
||||
$this->_connection->setData( |
||||
strval( |
||||
file_get_contents( |
||||
$address->getPath() |
||||
) |
||||
) |
||||
); |
||||
|
||||
$this->_connection->setMime( |
||||
strval( |
||||
mime_content_type( |
||||
$address->getPath() |
||||
) |
||||
) |
||||
); |
||||
|
||||
if ($this->_connection::MIME_TEXT_PLAIN == $this->_connection->getMime()) |
||||
{ |
||||
$extension = pathinfo( |
||||
strval( |
||||
$address->getPath() |
||||
), |
||||
PATHINFO_EXTENSION |
||||
); |
||||
|
||||
if (in_array($extension, ['gmi', 'gemini'])) |
||||
{ |
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
} |
||||
} |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_connection->setTitle( |
||||
_('Failure') |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
_('Could not open location') |
||||
); |
||||
} |
||||
|
||||
$this->_connection->setCompleted( |
||||
true |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,144 @@
@@ -0,0 +1,144 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Model\Connection; |
||||
|
||||
use \Yggverse\Gemini\Client\Request; |
||||
use \Yggverse\Gemini\Client\Response; |
||||
use \Yggverse\Net\Address; |
||||
|
||||
use \Yggverse\Yoda\Model\Connection; |
||||
|
||||
class Gemini |
||||
{ |
||||
private Connection $_connection; |
||||
|
||||
public function __construct( |
||||
Connection $connection |
||||
) { |
||||
$this->_connection = $connection; |
||||
} |
||||
|
||||
public function sync( |
||||
Address $address, |
||||
int $timeout = 5 |
||||
): void |
||||
{ |
||||
$request = new Request( |
||||
$address->get() |
||||
); |
||||
|
||||
$response = new Response( |
||||
$request->getResponse( |
||||
$timeout |
||||
) |
||||
); |
||||
|
||||
// Route status code |
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes |
||||
switch ($response->getCode()) |
||||
{ |
||||
case 10: // response expected |
||||
case 11: // sensitive input |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
|
||||
$this->_connection->setRequest( |
||||
[ |
||||
'placeholder' => $response->getMeta(), |
||||
'visible' => 11 !== $response->getCode() |
||||
] |
||||
); |
||||
|
||||
break; |
||||
|
||||
case 20: // ok |
||||
|
||||
$this->_connection->setData( |
||||
$response->getBody() |
||||
); |
||||
|
||||
switch (true) |
||||
{ |
||||
case str_contains( |
||||
$response->getMeta(), |
||||
self::MIME_TEXT_GEMINI |
||||
): |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
|
||||
break; |
||||
|
||||
case str_contains( |
||||
$response->getMeta(), |
||||
self::MIME_TEXT_PLAIN |
||||
): |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_PLAIN |
||||
); |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
throw new \Exception( |
||||
sprintf( |
||||
_('MIME type not implemented for %s'), |
||||
$response->getMeta() |
||||
) |
||||
); |
||||
} |
||||
|
||||
break; |
||||
|
||||
case 31: // redirect |
||||
// show link, no follow |
||||
|
||||
$this->_connection->setTitle( |
||||
_('Redirect!') |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
sprintf( |
||||
'=> %s', |
||||
$response->getMeta() |
||||
) |
||||
); |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_connection->setTitle( |
||||
_('Oops!') |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
sprintf( |
||||
'Could not open request (code: %d)', |
||||
intval( |
||||
$response->getCode() |
||||
) |
||||
) |
||||
); |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
} |
||||
|
||||
$this->_connection->setCompleted( |
||||
true |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Model\Connection; |
||||
|
||||
use \Yggverse\Net\Address; |
||||
use \Yggverse\Nex\Client; |
||||
|
||||
use \Yggverse\Yoda\Model\Connection; |
||||
|
||||
class Nex |
||||
{ |
||||
private Connection $_connection; |
||||
|
||||
public function __construct( |
||||
Connection $connection |
||||
) { |
||||
$this->_connection = $connection; |
||||
} |
||||
|
||||
// @TODO |
||||
public function sync( |
||||
Address $address, |
||||
int $timeout = 5 |
||||
): void |
||||
{ |
||||
$response = (new \Yggverse\Nex\Client)->request( |
||||
$address->get(), |
||||
$timeout |
||||
); |
||||
|
||||
if ($response) |
||||
{ |
||||
$this->_connection->setTitle( |
||||
strval( |
||||
$address->getHost() |
||||
) |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
$response |
||||
); |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_PLAIN |
||||
); |
||||
} |
||||
|
||||
else |
||||
{ |
||||
$this->_connection->setTitle( |
||||
_('Oops!') |
||||
); |
||||
|
||||
$this->_connection->setData( |
||||
_('Could not open request') |
||||
); |
||||
|
||||
$this->_connection->setMime( |
||||
$this->_connection::MIME_TEXT_GEMINI |
||||
); |
||||
} |
||||
|
||||
$this->_connection->setCompleted( |
||||
true |
||||
); |
||||
} |
||||
} |
@ -1,338 +0,0 @@
@@ -1,338 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Model; |
||||
|
||||
/* |
||||
* Single response API for multiple protocol providers |
||||
* |
||||
*/ |
||||
class Response |
||||
{ |
||||
// Subject |
||||
private \Yggverse\Net\Address $_address; |
||||
|
||||
// Async status |
||||
private bool $_completed = false; |
||||
|
||||
// Response |
||||
private ?string $_title = null; |
||||
private ?string $_subtitle = null; |
||||
private ?string $_tooltip = null; |
||||
private ?string $_mime = null; |
||||
private ?string $_data = null; |
||||
private ?string $_redirect = null; |
||||
private ?array $_request = null; |
||||
|
||||
// Config |
||||
public const MIME_TEXT_GEMINI = 'text/gemini'; |
||||
public const MIME_TEXT_PLAIN = 'text/plain'; |
||||
|
||||
public function __construct( |
||||
string $request, |
||||
int $timeout = 5 |
||||
) { |
||||
// Build address instance |
||||
$this->_address = new \Yggverse\Net\Address( |
||||
$request |
||||
); |
||||
|
||||
// Detect protocol |
||||
switch ($this->_address->getScheme()) |
||||
{ |
||||
case 'file': |
||||
|
||||
$this->_title = basename( |
||||
$this->_address->getPath() |
||||
); |
||||
|
||||
$this->_subtitle = $this->_address->getPath(); |
||||
$this->_tooltip = $this->_address->getPath(); |
||||
|
||||
switch (true) |
||||
{ |
||||
case ( |
||||
$list = \Yggverse\Yoda\Model\Filesystem::getList( |
||||
$this->_address->getPath() |
||||
) |
||||
): // directory |
||||
$tree = []; |
||||
|
||||
foreach ($list as $item) |
||||
{ |
||||
$tree[] = trim( |
||||
sprintf( |
||||
'=> file://%s %s', |
||||
$item['path'], |
||||
$item['name'] . ( |
||||
$item['file'] ? null : '/' |
||||
) |
||||
) |
||||
); |
||||
} |
||||
|
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
|
||||
$this->_data = implode( |
||||
PHP_EOL, |
||||
$tree |
||||
) . PHP_EOL; |
||||
|
||||
break; |
||||
|
||||
case file_exists( |
||||
$this->_address->getPath() |
||||
) && is_readable( |
||||
$this->_address->getPath() |
||||
): |
||||
$this->_data = strval( |
||||
file_get_contents( |
||||
$this->_address->getPath() |
||||
) |
||||
); |
||||
|
||||
$this->_mime = strval( |
||||
mime_content_type( |
||||
$this->_address->getPath() |
||||
) |
||||
); |
||||
|
||||
if ($this->_mime == self::MIME_TEXT_PLAIN) |
||||
{ |
||||
$extension = pathinfo( |
||||
strval( |
||||
$this->_address->getPath() |
||||
), |
||||
PATHINFO_EXTENSION |
||||
); |
||||
|
||||
if (in_array($extension, ['gmi', 'gemini'])) |
||||
{ |
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
} |
||||
} |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_title = _( |
||||
'Failure' |
||||
); |
||||
|
||||
$this->_data = _( |
||||
'Could not open location' |
||||
); |
||||
} |
||||
|
||||
$this->_completed = true; |
||||
|
||||
break; |
||||
|
||||
case 'gemini': // @TODO async |
||||
|
||||
$request = new \Yggverse\Gemini\Client\Request( |
||||
$this->_address->get() |
||||
); |
||||
|
||||
$response = new \Yggverse\Gemini\Client\Response( |
||||
$request->getResponse( |
||||
$timeout |
||||
) |
||||
); |
||||
|
||||
// Route status code |
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes |
||||
switch ($response->getCode()) |
||||
{ |
||||
case 10: // response expected |
||||
case 11: // sensitive input |
||||
|
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
|
||||
$this->_request = |
||||
[ |
||||
'placeholder' => $response->getMeta(), |
||||
'visible' => 11 !== $response->getCode() |
||||
]; |
||||
|
||||
break; |
||||
|
||||
case 20: // ok |
||||
|
||||
$this->_data = $response->getBody(); |
||||
|
||||
switch (true) |
||||
{ |
||||
case str_contains( |
||||
$response->getMeta(), |
||||
self::MIME_TEXT_GEMINI |
||||
): |
||||
|
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
|
||||
break; |
||||
|
||||
case str_contains( |
||||
$response->getMeta(), |
||||
self::MIME_TEXT_PLAIN |
||||
): |
||||
|
||||
$this->_mime = self::MIME_TEXT_PLAIN; |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
throw new \Exception( |
||||
sprintf( |
||||
_('MIME type not implemented for %s'), |
||||
$response->getMeta() |
||||
) |
||||
); |
||||
} |
||||
|
||||
$this->_completed = true; |
||||
|
||||
break; |
||||
|
||||
case 31: // redirect |
||||
// show link, no follow |
||||
|
||||
$this->_data = sprintf( |
||||
'=> %s', |
||||
$response->getMeta() |
||||
); |
||||
|
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
|
||||
$this->_completed = true; |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_title = _( |
||||
'Oops!' |
||||
); |
||||
|
||||
$this->_data = sprintf( |
||||
'Could not open request (code: %d)', |
||||
intval( |
||||
$response->getCode() |
||||
) |
||||
); |
||||
|
||||
$this->_mime = self::MIME_TEXT_GEMINI; |
||||
|
||||
$this->_completed = true; |
||||
} |
||||
|
||||
break; |
||||
|
||||
case 'nex': // @TODO async |
||||
|
||||
$this->_data = ( |
||||
new \Yggverse\Nex\Client |
||||
)->request( |
||||
$this->_address->get(), |
||||
$timeout |
||||
); |
||||
|
||||
$this->_mime = self::MIME_TEXT_PLAIN; // @TODO |
||||
|
||||
$this->_completed = true; |
||||
|
||||
break; |
||||
|
||||
case null: |
||||
|
||||
// Build gemini protocol address |
||||
$address = new \Yggverse\Net\Address( |
||||
sprintf( |
||||
'gemini://%s', |
||||
$this->_address->get() |
||||
) |
||||
); |
||||
|
||||
// Validate hostname |
||||
if (filter_var( |
||||
$address->getHost(), |
||||
FILTER_VALIDATE_DOMAIN, |
||||
FILTER_FLAG_HOSTNAME |
||||
) |
||||
) { |
||||
// Request redirect |
||||
$this->_redirect = $address->get(); |
||||
} |
||||
|
||||
// Request redirect to search provider |
||||
else |
||||
{ |
||||
// @TODO custom providers |
||||
$this->_redirect = sprintf( |
||||
'gemini://tlgs.one/search?%s', |
||||
urlencode( |
||||
$request |
||||
) |
||||
); |
||||
} |
||||
|
||||
return; |
||||
|
||||
default: |
||||
|
||||
throw new \Exception( |
||||
_('Protocol not supported') |
||||
); |
||||
} |
||||
} |
||||
|
||||
public function isCompleted(): bool |
||||
{ |
||||
return $this->_completed; |
||||
} |
||||
|
||||
public function getTitle(): ?string |
||||
{ |
||||
return $this->_title; |
||||
} |
||||
|
||||
public function getSubtitle(): ?string |
||||
{ |
||||
return $this->_subtitle; |
||||
} |
||||
|
||||
public function getTooltip(): ?string |
||||
{ |
||||
return $this->_tooltip; |
||||
} |
||||
|
||||
public function getMime(): ?string |
||||
{ |
||||
return $this->_mime; |
||||
} |
||||
|
||||
public function getData(): ?string |
||||
{ |
||||
return $this->_data; |
||||
} |
||||
|
||||
public function getRedirect(): ?string |
||||
{ |
||||
return $this->_redirect; |
||||
} |
||||
|
||||
public function getRequest(): ?array |
||||
{ |
||||
return $this->_request; |
||||
} |
||||
|
||||
public function getLength(): ?int |
||||
{ |
||||
return mb_strlen( |
||||
$this->_data |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue