Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e5a0380a | ||
|
|
e093495909 | ||
|
|
9ddb93b847 | ||
|
|
363da77669 | ||
|
|
94903d6f05 | ||
|
|
3d412d6a46 | ||
|
|
ca2d71e3ed | ||
|
|
7e2d4c7dc2 | ||
|
|
2e3788a295 | ||
|
|
6e7a8f12fb | ||
|
|
a1c51bed10 | ||
|
|
1044e35946 | ||
|
|
6ccf07825a | ||
|
|
23f24fef50 | ||
|
|
33e77b5129 |
No files matched your search
@@ -4,7 +4,7 @@ use function DI\autowire, DI\create, DI\get;
|
|||||||
|
|
||||||
use Picea\{ Method\Request };
|
use Picea\{ Method\Request };
|
||||||
|
|
||||||
use Picea\Extension\{ LanguageExtension, TitleExtension, NumberExtension, UrlExtension };
|
use Picea\Extension\{ LanguageExtension, TitleExtension, NumberExtension, UrlExtension, DiExtension };
|
||||||
|
|
||||||
use Picea\Ui\{ Method, Ui };
|
use Picea\Ui\{ Method, Ui };
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ return [
|
|||||||
$c->get(Method\Form::class),
|
$c->get(Method\Form::class),
|
||||||
$c->get(Method\Pagination::class),
|
$c->get(Method\Pagination::class),
|
||||||
$c->get(Request::class),
|
$c->get(Request::class),
|
||||||
|
$c->get(DiExtension::class),
|
||||||
|
|
||||||
], class_exists(\Taxus\Picea\Extension::class) ? [ $c->get(\Taxus\Picea\Extension::class) ] : [],
|
], class_exists(\Taxus\Picea\Extension::class) ? [ $c->get(\Taxus\Picea\Extension::class) ] : [],
|
||||||
array_map(fn($class) => $c->get($class), $c->get(Lean\Lean::class)->getPiceaExtensions())
|
array_map(fn($class) => $c->get($class), $c->get(Lean\Lean::class)->getPiceaExtensions())
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-2
@@ -11,7 +11,7 @@ class UiForm extends UiElement implements Extension {
|
|||||||
|
|
||||||
public string $defaultMethod = "get";
|
public string $defaultMethod = "get";
|
||||||
|
|
||||||
public array $token = [ "ui:form", "ui:endform" ];
|
public array $token = [ "ui:form", "ui:endform", "ui:/form" ];
|
||||||
|
|
||||||
public array $attributes = [
|
public array $attributes = [
|
||||||
'class' => 'ui-form',
|
'class' => 'ui-form',
|
||||||
@@ -30,6 +30,7 @@ class UiForm extends UiElement implements Extension {
|
|||||||
switch($token) {
|
switch($token) {
|
||||||
case 'ui.endform': # bw compat
|
case 'ui.endform': # bw compat
|
||||||
case 'ui:endform':
|
case 'ui:endform':
|
||||||
|
case 'ui:/form':
|
||||||
return "</form>";
|
return "</form>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ class UiForm extends UiElement implements Extension {
|
|||||||
|
|
||||||
if ($this->csrf) {
|
if ($this->csrf) {
|
||||||
$token = md5($name . microtime());
|
$token = md5($name . microtime());
|
||||||
$key = "picea-ui:form:{$name}";
|
$key = "picea-ui.form:{$name}";
|
||||||
|
|
||||||
if (count($_SESSION[$key] ?? []) > 100) {
|
if (count($_SESSION[$key] ?? []) > 100) {
|
||||||
array_shift($_SESSION[$key]);
|
array_shift($_SESSION[$key]);
|
||||||
|
|||||||
+12
-2
@@ -79,13 +79,23 @@ class UiSelect extends UiElement implements Extension {
|
|||||||
return $option;
|
return $option;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isSelected($check, $value, bool $strict = true) : bool
|
protected function isSelected($check, mixed $value, bool $strict = true) : bool
|
||||||
{
|
{
|
||||||
if ( is_array($value) ) {
|
if ( is_array($value) ) {
|
||||||
return in_array($check, $value, $strict);
|
return in_array($check, array_map(fn($e) => $this->normalizeValue($e), $value), $strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$value = $this->normalizeValue($value);
|
||||||
|
|
||||||
return $strict ? $check === $value : $check == $value;
|
return $strict ? $check === $value : $check == $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function normalizeValue(mixed $value) : mixed
|
||||||
|
{
|
||||||
|
if ($value instanceof \BackedEnum) {
|
||||||
|
return $value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Picea\Ui\Method\Context;
|
||||||
|
|
||||||
|
interface FormContextCastableInterface
|
||||||
|
{
|
||||||
|
public static function instance(mixed $value);
|
||||||
|
}
|
||||||
+1
-1
@@ -36,6 +36,6 @@ class Form implements Extension, FunctionExtension {
|
|||||||
|
|
||||||
public function formClass(FormInterface $form, ? FormContext $formContext = null) : FormHandler
|
public function formClass(FormInterface $form, ? FormContext $formContext = null) : FormHandler
|
||||||
{
|
{
|
||||||
return new FormHandler($formContext ? $formContext->request : $this->request, $form, $formContext);
|
return new FormHandler($formContext ? $formContext->getRequest() : $this->request, $form, $formContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+130
-43
@@ -7,9 +7,7 @@ use Psr\Http\Message\ServerRequestInterface,
|
|||||||
|
|
||||||
class FormContext implements FormContextInterface
|
class FormContext implements FormContextInterface
|
||||||
{
|
{
|
||||||
public string $method;
|
public string $formContextMethod;
|
||||||
|
|
||||||
public string $formName;
|
|
||||||
|
|
||||||
public bool $formSent = false;
|
public bool $formSent = false;
|
||||||
|
|
||||||
@@ -17,43 +15,50 @@ class FormContext implements FormContextInterface
|
|||||||
|
|
||||||
public mixed $formExecutionStatus = null;
|
public mixed $formExecutionStatus = null;
|
||||||
|
|
||||||
public array $values = [];
|
protected string $formContextValuesRoot;
|
||||||
|
|
||||||
public array $files = [];
|
public array $formContextValues = [];
|
||||||
|
|
||||||
public array $messages = [];
|
public array $formContextFiles = [];
|
||||||
|
|
||||||
public bool $skipCsrf = false;
|
public array $formContextMessages = [];
|
||||||
|
|
||||||
protected array $catchedMethods = [ 'POST', 'PUT', 'PATCH', 'DELETE', ];
|
public bool $formSkipCsrf = false;
|
||||||
|
|
||||||
public ServerRequestInterface $request;
|
protected array $formContextDefinedProperties = [];
|
||||||
|
|
||||||
public ? ResponseInterface $response = null;
|
|
||||||
|
|
||||||
public function __construct(ServerRequestInterface $request, ? string $formName = null)
|
protected array $formContextCatchedMethods = [ 'POST', 'PUT', 'PATCH', 'DELETE', ];
|
||||||
{
|
|
||||||
$this->request = $request;
|
|
||||||
|
|
||||||
if ( $formName ) {
|
protected array $formConstructorProperties = [];
|
||||||
$this->formName = $formName;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->values = $request->getParsedBody() ?: [];
|
public ? ResponseInterface $formContextResponse = null;
|
||||||
|
|
||||||
$this->method = $this->request->getMethod();
|
public function __construct(
|
||||||
|
public ServerRequestInterface $formContextRequest,
|
||||||
|
public readonly ? string $formName = null
|
||||||
|
) {
|
||||||
|
# From constructor
|
||||||
|
$reflection = new \ReflectionClass($this);
|
||||||
|
|
||||||
if ( ! $this->values ) {
|
$this->formConstructorProperties = array_map(fn($e) => strtolower($e->getName()), array_filter($reflection->getConstructor()->getParameters(), fn($e) => $e->isPromoted()));
|
||||||
$content = mb_convert_encoding((string) $request->getBody(), 'UTF-8');
|
|
||||||
|
$this->formContextValues = $formContextRequest->getParsedBody() ?: [];
|
||||||
|
|
||||||
|
if ( ! $this->formContextValues ) {
|
||||||
|
$content = mb_convert_encoding((string) $formContextRequest->getBody(), 'UTF-8');
|
||||||
|
|
||||||
if ( $content && ( $json = json_decode($content, true) ) ) {
|
if ( $content && ( $json = json_decode($content, true) ) ) {
|
||||||
$this->values = $json;
|
$this->formContextValues = $json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->formContextValuesRoot ?? false) {
|
||||||
|
$this->formContextValues = $this->formContextValues[$this->formContextValuesRoot] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
$this->fillValues();
|
$this->fillValues();
|
||||||
|
|
||||||
$this->files = $request->getUploadedFiles() ?: [];
|
$this->formContextFiles = $formContextRequest->getUploadedFiles() ?: [];
|
||||||
|
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,7 @@ class FormContext implements FormContextInterface
|
|||||||
|
|
||||||
public function valid() : bool
|
public function valid() : bool
|
||||||
{
|
{
|
||||||
foreach($this->messages as $message) {
|
foreach($this->formContextMessages as $message) {
|
||||||
if ( $message->isError() ) {
|
if ( $message->isError() ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -78,13 +83,13 @@ class FormContext implements FormContextInterface
|
|||||||
|
|
||||||
public function formSent() : bool
|
public function formSent() : bool
|
||||||
{
|
{
|
||||||
$valid = in_array($this->method, $this->catchedMethods);
|
$valid = in_array($this->requestMethod(), $this->formContextCatchedMethods);
|
||||||
|
|
||||||
if ( ! $this->skipCsrf && ($this->formName ?? false) ) {
|
if ( (! $this->formSkipCsrf) && ! empty($this->formName) ) {
|
||||||
$token = $this->get('picea-ui-form')[$this->formName] ?? false;
|
$token = $this->get('picea-ui-form')[$this->formName] ?? false;
|
||||||
|
|
||||||
if ( $token ) {
|
if ( $token ) {
|
||||||
if ($this->validateCsrfToken) {
|
if (! $this->formSkipCsrf) {
|
||||||
$valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []);
|
$valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -99,9 +104,19 @@ class FormContext implements FormContextInterface
|
|||||||
return $this->formSent = $valid;
|
return $this->formSent = $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getExecutionStatus() : mixed
|
||||||
|
{
|
||||||
|
return $this->formExecutionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest() : ServerRequestInterface
|
||||||
|
{
|
||||||
|
return $this->formContextRequest;
|
||||||
|
}
|
||||||
|
|
||||||
public function requestMethod() : string
|
public function requestMethod() : string
|
||||||
{
|
{
|
||||||
return $this->method;
|
return $this->formContextRequest->getMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __set($key, $value)
|
public function __set($key, $value)
|
||||||
@@ -116,7 +131,7 @@ class FormContext implements FormContextInterface
|
|||||||
|
|
||||||
public function __isset($key)
|
public function __isset($key)
|
||||||
{
|
{
|
||||||
return array_key_exists($key, $this->values);
|
return array_key_exists($key, $this->formContextValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __unset($key)
|
public function __unset($key)
|
||||||
@@ -124,40 +139,112 @@ class FormContext implements FormContextInterface
|
|||||||
$this->delete($key);
|
$this->delete($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get(string $key, $default = null)
|
public function get(string $key, mixed $default = null)
|
||||||
{
|
{
|
||||||
return $this->has($key) ? $this->values[$key] : $default;
|
return $this->has($key) ? $this->formContextValues[$key] : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, $value)
|
public function set(string $key, mixed $value)
|
||||||
{
|
{
|
||||||
return $this->values[$key] = $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
|
public function delete(string $key) : void
|
||||||
{
|
{
|
||||||
unset($this->values[$key]);
|
unset($this->formContextValues[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function has(string $key) : bool
|
public function has(string $key) : bool
|
||||||
{
|
{
|
||||||
return array_key_exists($key, $this->values);
|
return array_key_exists($key, $this->formContextValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pushMessage(FormMessage $message) : void
|
public function pushMessage(FormMessage $message) : void
|
||||||
{
|
{
|
||||||
$this->messages[] = $message;
|
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
|
protected function fillValues() : void
|
||||||
{
|
{
|
||||||
# Skipping overrides of this particular class vars as a security measure
|
foreach($this->formContextValues as $property => $value) {
|
||||||
$skipping = array_keys(array_change_key_case(get_class_vars(FormContext::class), CASE_LOWER));
|
if ($this->canWriteProperty($property)) {
|
||||||
|
$this->$property = $this->castValue($property, $value);
|
||||||
foreach($this->values as $property => $value) {
|
$this->formContextDefinedProperties[$property] = true;
|
||||||
if ( ! in_array(strtolower($property), $skipping) && property_exists($this, $property)) {
|
|
||||||
$this->$property = $value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
namespace Picea\Ui\Method;
|
namespace Picea\Ui\Method;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
interface FormContextInterface {
|
interface FormContextInterface {
|
||||||
public function valid() : bool;
|
public function valid() : bool;
|
||||||
|
|
||||||
public function formSent() : bool;
|
public function formSent() : bool;
|
||||||
|
|
||||||
public function requestMethod() : string;
|
public function requestMethod() : string;
|
||||||
|
|
||||||
|
public function getRequest() : ServerRequestInterface;
|
||||||
|
|
||||||
|
public function getExecutionStatus() : mixed;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Picea\Ui\Method;
|
namespace Picea\Ui\Method;
|
||||||
|
|
||||||
interface FormMessage {
|
interface FormMessage {
|
||||||
public function isError() : bool;
|
public function isError() : bool;
|
||||||
|
# public function isSuccess() : bool; # Should be added in next major version
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user