251 lines
7.4 KiB
PHP
251 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace Picea\Ui\Method;
|
|
|
|
use Psr\Http\Message\ServerRequestInterface,
|
|
Psr\Http\Message\ResponseInterface;
|
|
|
|
class FormContext implements FormContextInterface
|
|
{
|
|
public string $formContextMethod;
|
|
|
|
public bool $formSent = false;
|
|
|
|
public bool $formExecuted = false;
|
|
|
|
public mixed $formExecutionStatus = null;
|
|
|
|
protected string $formContextValuesRoot;
|
|
|
|
public array $formContextValues = [];
|
|
|
|
public array $formContextFiles = [];
|
|
|
|
public array $formContextMessages = [];
|
|
|
|
public bool $formSkipCsrf = false;
|
|
|
|
protected array $formContextDefinedProperties = [];
|
|
|
|
protected array $formContextCatchedMethods = [ 'POST', 'PUT', 'PATCH', 'DELETE', ];
|
|
|
|
protected array $formConstructorProperties = [];
|
|
|
|
public ? ResponseInterface $formContextResponse = null;
|
|
|
|
public function __construct(
|
|
public ServerRequestInterface $formContextRequest,
|
|
public readonly ? string $formName = null
|
|
) {
|
|
# From constructor
|
|
$reflection = new \ReflectionClass($this);
|
|
|
|
$this->formConstructorProperties = array_map(fn($e) => strtolower($e->getName()), array_filter($reflection->getConstructor()->getParameters(), fn($e) => $e->isPromoted()));
|
|
|
|
$this->formContextValues = $formContextRequest->getParsedBody() ?: [];
|
|
|
|
if ( ! $this->formContextValues ) {
|
|
$content = mb_convert_encoding((string) $formContextRequest->getBody(), 'UTF-8');
|
|
|
|
if ( $content && ( $json = json_decode($content, true) ) ) {
|
|
$this->formContextValues = $json;
|
|
}
|
|
}
|
|
|
|
if ($this->formContextValuesRoot ?? false) {
|
|
$this->formContextValues = $this->formContextValues[$this->formContextValuesRoot] ?? [];
|
|
}
|
|
|
|
$this->fillValues();
|
|
|
|
$this->formContextFiles = $formContextRequest->getUploadedFiles() ?: [];
|
|
|
|
$this->initialize();
|
|
}
|
|
|
|
public function initialize() : void {}
|
|
|
|
public function valid() : bool
|
|
{
|
|
foreach($this->formContextMessages as $message) {
|
|
if ( $message->isError() ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function executed() : bool
|
|
{
|
|
return $this->formExecutionStatus === false ? false : $this->formExecuted;
|
|
}
|
|
|
|
public function formSent() : bool
|
|
{
|
|
$valid = in_array($this->requestMethod(), $this->formContextCatchedMethods);
|
|
|
|
if ( (! $this->formSkipCsrf) && ! empty($this->formName) ) {
|
|
$token = $this->get('picea-ui-form')[$this->formName] ?? false;
|
|
|
|
if ( $token ) {
|
|
if (! $this->formSkipCsrf) {
|
|
$valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []);
|
|
}
|
|
else {
|
|
$valid = (bool) $token;
|
|
}
|
|
}
|
|
else {
|
|
$valid = false;
|
|
}
|
|
}
|
|
|
|
return $this->formSent = $valid;
|
|
}
|
|
|
|
public function getExecutionStatus() : mixed
|
|
{
|
|
return $this->formExecutionStatus;
|
|
}
|
|
|
|
public function getRequest() : ServerRequestInterface
|
|
{
|
|
return $this->formContextRequest;
|
|
}
|
|
|
|
public function requestMethod() : string
|
|
{
|
|
return $this->formContextRequest->getMethod();
|
|
}
|
|
|
|
public function __set($key, $value)
|
|
{
|
|
return $this->set($key, $value);
|
|
}
|
|
|
|
public function __get($key)
|
|
{
|
|
return $this->get($key);
|
|
}
|
|
|
|
public function __isset($key)
|
|
{
|
|
return array_key_exists($key, $this->formContextValues);
|
|
}
|
|
|
|
public function __unset($key)
|
|
{
|
|
$this->delete($key);
|
|
}
|
|
|
|
public function get(string $key, mixed $default = null)
|
|
{
|
|
return $this->has($key) ? $this->formContextValues[$key] : $default;
|
|
}
|
|
|
|
public function set(string $key, mixed $value)
|
|
{
|
|
|
|
if ($this->canWriteProperty($key)) {
|
|
$this->$key = $this->castValue($key, $value);
|
|
}
|
|
|
|
return $this->formContextValues[$key] = $value;
|
|
}
|
|
|
|
public function sets(array $values) : static
|
|
{
|
|
foreach($values as $key =>$value) {
|
|
$this->set($key, $value);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
|
|
public function delete(string $key) : void
|
|
{
|
|
unset($this->formContextValues[$key]);
|
|
}
|
|
|
|
public function has(string $key) : bool
|
|
{
|
|
return array_key_exists($key, $this->formContextValues);
|
|
}
|
|
|
|
public function pushMessage(FormMessage $message) : void
|
|
{
|
|
if ($message->isError() /* && $message->isSuccess() @TODO next major v. */ ) {
|
|
$this->formContextMessages = array_merge([ $message ], $this->formContextMessages);
|
|
}
|
|
else {
|
|
$this->formContextMessages[] = $message;
|
|
}
|
|
}
|
|
|
|
public function messages() : array
|
|
{
|
|
return $this->formContextMessages;
|
|
}
|
|
|
|
protected function fillValues() : void
|
|
{
|
|
foreach($this->formContextValues as $property => $value) {
|
|
if ($this->canWriteProperty($property)) {
|
|
$this->$property = $this->castValue($property, $value);
|
|
$this->formContextDefinedProperties[$property] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function canWriteProperty(string $property) : bool
|
|
{
|
|
# Skipping overrides of this particular class vars as a security measure
|
|
static $skipping = array_keys(array_change_key_case(get_class_vars(FormContext::class), CASE_LOWER));
|
|
|
|
return ! in_array(strtolower($property), array_merge($skipping, $this->formConstructorProperties)) && property_exists($this, $property);
|
|
}
|
|
|
|
protected function definedProperty(string $property) : bool
|
|
{
|
|
return $this->formContextDefinedProperties[$property] ?? false;
|
|
}
|
|
|
|
protected function castValue(string $property, mixed $value) : mixed
|
|
{
|
|
$cast = fn($e) => match(true) {
|
|
is_a($e, Context\FormContextCastableInterface::class, true) => $e::instance($value),
|
|
$e === "array" => (function(array|string $value) use ($property) {
|
|
if (is_string($value)) {
|
|
return json_validate($value) ? json_decode($value, true) ?? [] : throw new \InvalidArgumentException("Field '$property' is awaiting JSON encoded content.");
|
|
}
|
|
|
|
return $value;
|
|
})($value),
|
|
is_null($value) => null,
|
|
enum_exists($e) => ( $value instanceof \BackedEnum ? $value : $e::tryFrom($value) ) ?? throw new \InvalidArgumentException(sprintf("Field '\$$property' awaiting values : '%s'.", implode("', '", array_map(fn($e) => $e->value, $e::cases())))),
|
|
function_exists($e) => $e($value),
|
|
class_exists($e) => new $e(...$value),
|
|
interface_exists($e) => throw new \InvalidArgumentException("There is no way provided to match an interface object yet."),
|
|
default => $value
|
|
};
|
|
|
|
$types = ( new \ReflectionProperty($this, $property) )->getType();
|
|
|
|
if ($types instanceof \ReflectionNamedType) {
|
|
$value = $cast($types->getName());
|
|
}
|
|
elseif ($types instanceof \ReflectionIntersectionType || $types instanceof \ReflectionUnionType) {
|
|
foreach($types->getTypes() as $type) {
|
|
return $cast($type->getName());
|
|
}
|
|
}
|
|
elseif ($types->isBuiltin()) {
|
|
return $cast($types->getName());
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
}
|